finishing porting omni watcher to work with light sync; split into full, light,
and shared directories and refactor as much into shared; finish lightSync omni watcher tests
This commit is contained in:
parent
58b34548f3
commit
e02b33547d
@ -9,10 +9,10 @@
|
|||||||
Vulcanize DB is a set of tools that make it easier for developers to write application-specific indexes and caches for dapps built on Ethereum.
|
Vulcanize DB is a set of tools that make it easier for developers to write application-specific indexes and caches for dapps built on Ethereum.
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
- Go 1.9+
|
- Go 1.11+
|
||||||
- Postgres 10
|
- Postgres 10
|
||||||
- Ethereum Node
|
- Ethereum Node
|
||||||
- [Go Ethereum](https://ethereum.github.io/go-ethereum/downloads/) (1.8+)
|
- [Go Ethereum](https://ethereum.github.io/go-ethereum/downloads/) (1.8.18+)
|
||||||
- [Parity 1.8.11+](https://github.com/paritytech/parity/releases)
|
- [Parity 1.8.11+](https://github.com/paritytech/parity/releases)
|
||||||
|
|
||||||
## Project Setup
|
## Project Setup
|
||||||
|
@ -17,11 +17,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -59,29 +56,6 @@ func lightOmniWatcher() {
|
|||||||
log.Fatal("Contract address required")
|
log.Fatal("Contract address required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(contractEvents) == 0 || len(contractMethods) == 0 {
|
|
||||||
var str string
|
|
||||||
for str != "y" {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
if len(contractEvents) == 0 && len(contractMethods) == 0 {
|
|
||||||
fmt.Print("Warning: no events or methods specified.\n Proceed to watch every event and poll no methods? (Y/n)\n> ")
|
|
||||||
} else if len(contractEvents) == 0 {
|
|
||||||
fmt.Print("Warning: no events specified.\n Proceed to watch every event? (Y/n)\n> ")
|
|
||||||
} else {
|
|
||||||
fmt.Print("Warning: no methods specified.\n Proceed to poll no methods? (Y/n)\n> ")
|
|
||||||
}
|
|
||||||
resp, err := reader.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
str = strings.ToLower(string(resp))
|
|
||||||
if str == "n" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@ -114,13 +88,13 @@ func lightOmniWatcher() {
|
|||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(lightOmniWatcherCmd)
|
rootCmd.AddCommand(lightOmniWatcherCmd)
|
||||||
|
|
||||||
omniWatcherCmd.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")
|
||||||
omniWatcherCmd.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")
|
||||||
omniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "contract-events", "e", []string{}, "Subset of events to watch; by default all events are watched")
|
lightOmniWatcherCmd.Flags().StringArrayVarP(&contractEvents, "contract-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")
|
lightOmniWatcherCmd.Flags().StringArrayVarP(&contractMethods, "contract-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")
|
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")
|
||||||
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")
|
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")
|
||||||
omniWatcherCmd.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"`)
|
||||||
omniWatcherCmd.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")
|
||||||
omniWatcherCmd.Flags().Int64VarP(&startingBlockNumber, "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")
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,8 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -59,29 +56,6 @@ func omniWatcher() {
|
|||||||
log.Fatal("Contract address required")
|
log.Fatal("Contract address required")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(contractEvents) == 0 || len(contractMethods) == 0 {
|
|
||||||
var str string
|
|
||||||
for str != "y" {
|
|
||||||
reader := bufio.NewReader(os.Stdin)
|
|
||||||
if len(contractEvents) == 0 && len(contractMethods) == 0 {
|
|
||||||
fmt.Print("Warning: no events or methods specified.\n Proceed to watch every event and poll no methods? (Y/n)\n> ")
|
|
||||||
} else if len(contractEvents) == 0 {
|
|
||||||
fmt.Print("Warning: no events specified.\n Proceed to watch every event? (Y/n)\n> ")
|
|
||||||
} else {
|
|
||||||
fmt.Print("Warning: no methods specified.\n Proceed to poll no methods? (Y/n)\n> ")
|
|
||||||
}
|
|
||||||
resp, err := reader.ReadBytes('\n')
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
str = strings.ToLower(string(resp))
|
|
||||||
if str == "n" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ticker := time.NewTicker(5 * time.Second)
|
ticker := time.NewTicker(5 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@ -122,5 +96,5 @@ func init() {
|
|||||||
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(&methodAddrs, "method-filter-addresses", "g", []string{}, "Account addresses to poll methods with; default is to poll with all found token holder addresses")
|
||||||
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(&startingBlockNumber, "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")
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
CREATE TABLE public.checked_headers (
|
CREATE TABLE public.checked_headers (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
header_id INTEGER UNIQUE NOT NULL REFERENCES headers (id) ON DELETE CASCADE,
|
header_id INTEGER UNIQUE NOT NULL REFERENCES headers (id) ON DELETE CASCADE
|
||||||
price_feeds_checked BOOLEAN NOT NULL DEFAULT FALSE
|
|
||||||
);
|
);
|
BIN
pkg/omni/.DS_Store
vendored
BIN
pkg/omni/.DS_Store
vendored
Binary file not shown.
@ -74,22 +74,25 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
|
|||||||
// Postgres cannot handle custom types, resolve to strings
|
// Postgres cannot handle custom types, resolve to strings
|
||||||
switch input.(type) {
|
switch input.(type) {
|
||||||
case *big.Int:
|
case *big.Int:
|
||||||
var b *big.Int
|
b := input.(*big.Int)
|
||||||
b = input.(*big.Int)
|
|
||||||
strValues[fieldName] = b.String()
|
strValues[fieldName] = b.String()
|
||||||
case common.Address:
|
case common.Address:
|
||||||
var a 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
|
c.ContractInfo.AddTokenHolderAddress(a.String()) // cache address in a list of contract's token holder addresses
|
||||||
case common.Hash:
|
case common.Hash:
|
||||||
var h common.Hash
|
h := input.(common.Hash)
|
||||||
h = input.(common.Hash)
|
|
||||||
strValues[fieldName] = h.String()
|
strValues[fieldName] = h.String()
|
||||||
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:
|
||||||
|
b := input.([]byte)
|
||||||
|
strValues[fieldName] = string(b)
|
||||||
|
case byte:
|
||||||
|
b := input.(byte)
|
||||||
|
strValues[fieldName] = string(b)
|
||||||
default:
|
default:
|
||||||
return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input))
|
return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input))
|
||||||
}
|
}
|
||||||
@ -98,7 +101,6 @@ func (c *converter) Convert(watchedEvent core.WatchedEvent, event types.Event) (
|
|||||||
// Only hold onto logs that pass our address filter, if any
|
// Only hold onto logs that pass our address filter, if any
|
||||||
if c.ContractInfo.PassesEventFilter(strValues) {
|
if c.ContractInfo.PassesEventFilter(strValues) {
|
||||||
eventLog := &types.Log{
|
eventLog := &types.Log{
|
||||||
Event: event,
|
|
||||||
Id: watchedEvent.LogID,
|
Id: watchedEvent.LogID,
|
||||||
Values: strValues,
|
Values: strValues,
|
||||||
Block: watchedEvent.BlockNumber,
|
Block: watchedEvent.BlockNumber,
|
||||||
|
@ -25,40 +25,41 @@ 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"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Converter", func() {
|
var _ = Describe("Converter", func() {
|
||||||
var info *contract.Contract
|
var con *contract.Contract
|
||||||
var wantedEvents = []string{"Transfer"}
|
var wantedEvents = []string{"Transfer"}
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
info = test_helpers.SetupTusdContract(wantedEvents, []string{})
|
con = test_helpers.SetupTusdContract(wantedEvents, []string{})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Update", func() {
|
Describe("Update", func() {
|
||||||
It("Updates contract info held by the converter", func() {
|
It("Updates contract con held by the converter", func() {
|
||||||
c := converter.NewConverter(info)
|
c := converter.NewConverter(con)
|
||||||
Expect(c.ContractInfo).To(Equal(info))
|
Expect(c.ContractInfo).To(Equal(con))
|
||||||
|
|
||||||
info := test_helpers.SetupTusdContract([]string{}, []string{})
|
con := test_helpers.SetupTusdContract([]string{}, []string{})
|
||||||
c.Update(info)
|
c.Update(con)
|
||||||
Expect(c.ContractInfo).To(Equal(info))
|
Expect(c.ContractInfo).To(Equal(con))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Convert", func() {
|
Describe("Convert", func() {
|
||||||
It("Converts a watched event log to mapping of event input names to values", func() {
|
It("Converts a watched event log to mapping of event input names to values", func() {
|
||||||
_, ok := info.Events["Approval"]
|
_, ok := con.Events["Approval"]
|
||||||
Expect(ok).To(Equal(false))
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
event, ok := info.Events["Transfer"]
|
event, ok := con.Events["Transfer"]
|
||||||
Expect(ok).To(Equal(true))
|
Expect(ok).To(Equal(true))
|
||||||
err = info.GenerateFilters()
|
err = con.GenerateFilters()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
c := converter.NewConverter(info)
|
c := converter.NewConverter(con)
|
||||||
log, err := c.Convert(test_helpers.MockTranferEvent, event)
|
log, err := c.Convert(mocks.MockTranferEvent, event)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
from := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21")
|
from := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21")
|
||||||
@ -72,10 +73,36 @@ var _ = Describe("Converter", func() {
|
|||||||
Expect(v).To(Equal(value.String()))
|
Expect(v).To(Equal(value.String()))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("Keeps track of addresses it sees to grow a token holder address list for the contract", func() {
|
||||||
|
event, ok := con.Events["Transfer"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
|
||||||
|
c := converter.NewConverter(con)
|
||||||
|
_, err := c.Convert(mocks.MockTranferEvent, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs["0x"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs[""]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs["0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
It("Fails with an empty contract", func() {
|
It("Fails with an empty contract", func() {
|
||||||
event := info.Events["Transfer"]
|
event := con.Events["Transfer"]
|
||||||
c := converter.NewConverter(&contract.Contract{})
|
c := converter.NewConverter(&contract.Contract{})
|
||||||
_, err = c.Convert(test_helpers.MockTranferEvent, event)
|
_, err = c.Convert(mocks.MockTranferEvent, event)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,182 +0,0 @@
|
|||||||
// VulcanizeDB
|
|
||||||
// Copyright © 2018 Vulcanize
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event repository is used to persist event data into custom tables
|
|
||||||
type EventRepository interface {
|
|
||||||
PersistLog(event types.Log, contractAddr, contractName string) error
|
|
||||||
CreateEventTable(contractName string, event types.Log) (bool, error)
|
|
||||||
CreateContractSchema(contractName string) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventRepository struct {
|
|
||||||
db *postgres.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEventRepository(db *postgres.DB) *eventRepository {
|
|
||||||
|
|
||||||
return &eventRepository{
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a schema for the contract if needed
|
|
||||||
// Creates table for the watched contract event if needed
|
|
||||||
// Persists converted event log data into this custom table
|
|
||||||
func (d *eventRepository) PersistLog(event types.Log, contractAddr, contractName string) error {
|
|
||||||
_, err := d.CreateContractSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = d.CreateEventTable(contractAddr, event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.persistLog(event, contractAddr, contractName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a custom postgres command to persist logs for the given event
|
|
||||||
func (d *eventRepository) persistLog(event types.Log, contractAddr, contractName string) error {
|
|
||||||
// Begin postgres string
|
|
||||||
pgStr := fmt.Sprintf("INSERT INTO c%s.%s_event ", strings.ToLower(contractAddr), strings.ToLower(event.Name))
|
|
||||||
pgStr = pgStr + "(vulcanize_log_id, token_name, block, tx"
|
|
||||||
|
|
||||||
// Pack the corresponding variables in a slice
|
|
||||||
var data []interface{}
|
|
||||||
data = append(data,
|
|
||||||
event.Id,
|
|
||||||
contractName,
|
|
||||||
event.Block,
|
|
||||||
event.Tx)
|
|
||||||
|
|
||||||
// Iterate over name-value pairs in the log adding
|
|
||||||
// names to the string and pushing values to the slice
|
|
||||||
counter := 0 // Keep track of number of inputs
|
|
||||||
for inputName, input := range event.Values {
|
|
||||||
counter += 1
|
|
||||||
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) // Add underscore after to avoid any collisions with reserved pg words
|
|
||||||
data = append(data, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish off the string and execute the command using the packed data
|
|
||||||
// For each input entry we created we add its postgres command variable to the string
|
|
||||||
pgStr = pgStr + ") VALUES ($1, $2, $3, $4"
|
|
||||||
for i := 0; i < counter; i++ {
|
|
||||||
pgStr = pgStr + fmt.Sprintf(", $%d", i+5)
|
|
||||||
}
|
|
||||||
pgStr = pgStr + ") ON CONFLICT (vulcanize_log_id) DO NOTHING"
|
|
||||||
|
|
||||||
_, err := d.db.Exec(pgStr, data...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks for event table and creates it if it does not already exist
|
|
||||||
func (d *eventRepository) CreateEventTable(contractAddr string, event types.Log) (bool, error) {
|
|
||||||
tableExists, err := d.checkForTable(contractAddr, event.Name)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tableExists {
|
|
||||||
err = d.newEventTable(contractAddr, event)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !tableExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a table for the given contract and event
|
|
||||||
func (d *eventRepository) newEventTable(contractAddr string, event types.Log) error {
|
|
||||||
// Begin pg string
|
|
||||||
pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS c%s.%s_event ", strings.ToLower(contractAddr), strings.ToLower(event.Name))
|
|
||||||
pgStr = pgStr + "(id SERIAL, vulcanize_log_id INTEGER NOT NULL UNIQUE, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL, tx CHARACTER VARYING(66) NOT NULL,"
|
|
||||||
|
|
||||||
// Iterate over event fields, using their name and pgType to grow the string
|
|
||||||
for _, field := range event.Fields {
|
|
||||||
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
|
|
||||||
}
|
|
||||||
|
|
||||||
pgStr = pgStr + " CONSTRAINT log_index_fk FOREIGN KEY (vulcanize_log_id) REFERENCES logs (id) ON DELETE CASCADE)"
|
|
||||||
_, err := d.db.Exec(pgStr)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a table already exists for the given contract and event
|
|
||||||
func (d *eventRepository) checkForTable(contractAddr string, eventName string) (bool, error) {
|
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'c%s' AND table_name = '%s_event')", strings.ToLower(contractAddr), strings.ToLower(eventName))
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
err := d.db.Get(&exists, pgStr)
|
|
||||||
|
|
||||||
return exists, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks for contract schema and creates it if it does not already exist
|
|
||||||
func (d *eventRepository) CreateContractSchema(contractAddr string) (bool, error) {
|
|
||||||
if contractAddr == "" {
|
|
||||||
return false, errors.New("error: no contract address specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
schemaExists, err := d.checkForSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !schemaExists {
|
|
||||||
err = d.newContractSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !schemaExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a schema for the given contract
|
|
||||||
func (d *eventRepository) newContractSchema(contractAddr string) error {
|
|
||||||
_, err := d.db.Exec("CREATE SCHEMA IF NOT EXISTS c" + strings.ToLower(contractAddr))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a schema already exists for the given contract
|
|
||||||
func (d *eventRepository) checkForSchema(contractAddr string) (bool, error) {
|
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'c%s')", strings.ToLower(contractAddr))
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
err := d.db.QueryRow(pgStr).Scan(&exists)
|
|
||||||
|
|
||||||
return exists, err
|
|
||||||
}
|
|
@ -1,171 +0,0 @@
|
|||||||
// VulcanizeDB
|
|
||||||
// Copyright © 2018 Vulcanize
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package repository_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/repository"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
|
||||||
"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/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
var mockEvent = core.WatchedEvent{
|
|
||||||
Name: constants.TransferEvent.String(),
|
|
||||||
BlockNumber: 5488076,
|
|
||||||
Address: constants.TusdContractAddress,
|
|
||||||
TxHash: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
|
|
||||||
Index: 110,
|
|
||||||
Topic0: constants.TransferEvent.Signature(),
|
|
||||||
Topic1: "0x000000000000000000000000000000000000000000000000000000000000af21",
|
|
||||||
Topic2: "0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391",
|
|
||||||
Topic3: "",
|
|
||||||
Data: "0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe",
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Repository", func() {
|
|
||||||
var db *postgres.DB
|
|
||||||
var dataStore repository.EventRepository
|
|
||||||
var err error
|
|
||||||
var log *types.Log
|
|
||||||
var con *contract.Contract
|
|
||||||
var vulcanizeLogId int64
|
|
||||||
var wantedEvents = []string{"Transfer"}
|
|
||||||
var event types.Event
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
db, con = test_helpers.SetupTusdRepo(&vulcanizeLogId, wantedEvents, []string{})
|
|
||||||
mockEvent.LogID = vulcanizeLogId
|
|
||||||
|
|
||||||
event = con.Events["Transfer"]
|
|
||||||
err = con.GenerateFilters()
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
c := converter.NewConverter(con)
|
|
||||||
log, err = c.Convert(mockEvent, event)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
dataStore = repository.NewEventRepository(db)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
|
||||||
test_helpers.TearDown(db)
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("CreateContractSchema", func() {
|
|
||||||
It("Creates schema if it doesn't exist", func() {
|
|
||||||
created, err := dataStore.CreateContractSchema(con.Address)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateContractSchema(con.Address)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(false))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("CreateEventTable", func() {
|
|
||||||
It("Creates table if it doesn't exist", func() {
|
|
||||||
created, err := dataStore.CreateContractSchema(con.Address)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateEventTable(con.Address, *log)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateEventTable(con.Address, *log)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(false))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("PersistLog", func() {
|
|
||||||
It("Persists contract event log values into custom tables, adding any addresses to a growing list of contract associated addresses", func() {
|
|
||||||
err = dataStore.PersistLog(*log, con.Address, con.Name)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"]
|
|
||||||
Expect(ok).To(Equal(true))
|
|
||||||
Expect(b).To(Equal(true))
|
|
||||||
|
|
||||||
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"]
|
|
||||||
Expect(ok).To(Equal(true))
|
|
||||||
Expect(b).To(Equal(true))
|
|
||||||
|
|
||||||
scanLog := test_helpers.TransferLog{}
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog)
|
|
||||||
expectedLog := test_helpers.TransferLog{
|
|
||||||
Id: 1,
|
|
||||||
VulvanizeLogId: vulcanizeLogId,
|
|
||||||
TokenName: "TrueUSD",
|
|
||||||
Block: 5488076,
|
|
||||||
Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
|
|
||||||
From: "0x000000000000000000000000000000000000Af21",
|
|
||||||
To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391",
|
|
||||||
Value: "1097077688018008265106216665536940668749033598146",
|
|
||||||
}
|
|
||||||
Expect(scanLog).To(Equal(expectedLog))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Doesn't persist duplicate event logs", func() {
|
|
||||||
// Perist once
|
|
||||||
err = dataStore.PersistLog(*log, con.Address, con.Name)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
scanLog := test_helpers.TransferLog{}
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog)
|
|
||||||
expectedLog := test_helpers.TransferLog{
|
|
||||||
Id: 1,
|
|
||||||
VulvanizeLogId: vulcanizeLogId,
|
|
||||||
TokenName: "TrueUSD",
|
|
||||||
Block: 5488076,
|
|
||||||
Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
|
|
||||||
From: "0x000000000000000000000000000000000000Af21",
|
|
||||||
To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391",
|
|
||||||
Value: "1097077688018008265106216665536940668749033598146",
|
|
||||||
}
|
|
||||||
|
|
||||||
Expect(scanLog).To(Equal(expectedLog))
|
|
||||||
|
|
||||||
// Attempt to persist the same log again
|
|
||||||
err = dataStore.PersistLog(*log, con.Address, con.Name)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
// Show that no new logs were entered
|
|
||||||
var count int
|
|
||||||
err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM c%s.transfer_event", constants.TusdContractAddress))
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(count).To(Equal(1))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Fails with empty log", func() {
|
|
||||||
err = dataStore.PersistLog(types.Log{}, con.Address, con.Name)
|
|
||||||
Expect(err).To(HaveOccurred())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
func TestRetriever(t *testing.T) {
|
func TestRetriever(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Full Retriever Suite Test")
|
RunSpecs(t, "Full Block Number Retriever Suite Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = BeforeSuite(func() {
|
var _ = BeforeSuite(func() {
|
||||||
|
@ -19,18 +19,17 @@ package transformer
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/repository"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
||||||
"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"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Requires a fully synced vDB and a running eth node (or infura)
|
// Requires a fully synced vDB and a running eth node (or infura)
|
||||||
@ -71,14 +70,14 @@ type transformer struct {
|
|||||||
// Transformer takes in config for blockchain, database, and network id
|
// Transformer takes in config for blockchain, database, and network id
|
||||||
func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *transformer {
|
func NewTransformer(network string, BC core.BlockChain, DB *postgres.DB) *transformer {
|
||||||
return &transformer{
|
return &transformer{
|
||||||
Poller: poller.NewPoller(BC, DB),
|
Poller: poller.NewPoller(BC, DB, types.FullSync),
|
||||||
Parser: parser.NewParser(network),
|
Parser: parser.NewParser(network),
|
||||||
BlockRetriever: retriever.NewBlockRetriever(DB),
|
BlockRetriever: retriever.NewBlockRetriever(DB),
|
||||||
Converter: converter.NewConverter(&contract.Contract{}),
|
Converter: converter.NewConverter(&contract.Contract{}),
|
||||||
Contracts: map[string]*contract.Contract{},
|
Contracts: map[string]*contract.Contract{},
|
||||||
WatchedEventRepository: repositories.WatchedEventRepository{DB: DB},
|
WatchedEventRepository: repositories.WatchedEventRepository{DB: DB},
|
||||||
FilterRepository: repositories.FilterRepository{DB: DB},
|
FilterRepository: repositories.FilterRepository{DB: DB},
|
||||||
EventRepository: repository.NewEventRepository(DB),
|
EventRepository: repository.NewEventRepository(DB, types.FullSync),
|
||||||
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{},
|
||||||
@ -184,27 +183,26 @@ func (tr transformer) Execute() error {
|
|||||||
tr.Update(con)
|
tr.Update(con)
|
||||||
|
|
||||||
// Iterate through contract filters and get watched event logs
|
// Iterate through contract filters and get watched event logs
|
||||||
for eventName, filter := range con.Filters {
|
for eventName := range con.Filters {
|
||||||
watchedEvents, err := tr.GetWatchedEvents(eventName)
|
watchedEvents, err := tr.GetWatchedEvents(eventName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(fmt.Sprintf("Error fetching events for %s:", filter.Name), err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over watched event logs
|
// Iterate over watched event logs
|
||||||
for _, we := range watchedEvents {
|
for _, we := range watchedEvents {
|
||||||
// Convert them to our custom log type
|
// Convert them to our custom log type
|
||||||
log, err := tr.Converter.Convert(*we, con.Events[eventName])
|
cstm, err := tr.Converter.Convert(*we, con.Events[eventName])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if log == nil {
|
if cstm == nil {
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// If log is not empty, immediately persist in repo
|
// If log is not empty, immediately persist in repo
|
||||||
// Run this in seperate goroutine?
|
// Run this in seperate goroutine?
|
||||||
err = tr.PersistLog(*log, con.Address, con.Name)
|
err = tr.PersistLogs([]types.Log{*cstm}, con.Events[eventName], con.Address, con.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ import (
|
|||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/full/transformer"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Transformer", func() {
|
var _ = Describe("Transformer", func() {
|
||||||
@ -95,8 +96,8 @@ var _ = Describe("Transformer", func() {
|
|||||||
|
|
||||||
Describe("Init", func() {
|
Describe("Init", func() {
|
||||||
It("Initializes transformer's contract objects", func() {
|
It("Initializes transformer's contract objects", func() {
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock1)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock2)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
|
||||||
t := transformer.NewTransformer("", blockChain, db)
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
err = t.Init()
|
err = t.Init()
|
||||||
@ -120,8 +121,8 @@ var _ = Describe("Transformer", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Does nothing if watched events are unset", func() {
|
It("Does nothing if watched events are unset", func() {
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock1)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock2)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
|
||||||
t := transformer.NewTransformer("", blockChain, db)
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
err = t.Init()
|
err = t.Init()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
@ -133,8 +134,8 @@ var _ = Describe("Transformer", func() {
|
|||||||
|
|
||||||
Describe("Execute", func() {
|
Describe("Execute", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock1)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock1)
|
||||||
blockRepository.CreateOrUpdateBlock(test_helpers.TransferBlock2)
|
blockRepository.CreateOrUpdateBlock(mocks.TransferBlock2)
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Transforms watched contract data into custom repositories", func() {
|
It("Transforms watched contract data into custom repositories", func() {
|
||||||
@ -148,7 +149,7 @@ var _ = Describe("Transformer", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
log := test_helpers.TransferLog{}
|
log := test_helpers.TransferLog{}
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.transfer_event WHERE block = 6194634", constants.TusdContractAddress)).StructScan(&log)
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event WHERE block = 6194634", constants.TusdContractAddress)).StructScan(&log)
|
||||||
|
|
||||||
// We don't know vulcID, so compare individual fields instead of complete structures
|
// We don't know vulcID, so compare individual fields instead of complete structures
|
||||||
Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee"))
|
Expect(log.Tx).To(Equal("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad654eee"))
|
||||||
@ -198,12 +199,12 @@ var _ = Describe("Transformer", func() {
|
|||||||
|
|
||||||
res := test_helpers.BalanceOf{}
|
res := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%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_ = '0x000000000000000000000000000000000000Af21' 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"))
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6194634'", constants.TusdContractAddress)).StructScan(&res)
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6194634'", constants.TusdContractAddress)).StructScan(&res)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -17,16 +17,22 @@
|
|||||||
package converter
|
package converter
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
geth "github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
gethTypes "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Converter interface {
|
type Converter interface {
|
||||||
Convert(log geth.Log, event types.Event) (*types.Log, error)
|
Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error)
|
||||||
Update(info *contract.Contract)
|
Update(info *contract.Contract)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,6 +51,66 @@ func (c *converter) Update(info *contract.Contract) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert the given watched event log into a types.Log for the given event
|
// Convert the given watched event log into a types.Log for the given event
|
||||||
func (c *converter) Convert(log geth.Log, event types.Event) (*types.Log, error) {
|
func (c *converter) Convert(logs []gethTypes.Log, event types.Event, headerID int64) ([]types.Log, error) {
|
||||||
return nil, errors.New("implement me")
|
contract := bind.NewBoundContract(common.HexToAddress(c.ContractInfo.Address), c.ContractInfo.ParsedAbi, nil, nil, nil)
|
||||||
|
returnLogs := make([]types.Log, 0, len(logs))
|
||||||
|
for _, log := range logs {
|
||||||
|
values := make(map[string]interface{})
|
||||||
|
for _, field := range event.Fields {
|
||||||
|
var i interface{}
|
||||||
|
values[field.Name] = i
|
||||||
|
}
|
||||||
|
|
||||||
|
err := contract.UnpackLogIntoMap(values, event.Name, log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
strValues := make(map[string]string, len(values))
|
||||||
|
for fieldName, input := range values {
|
||||||
|
// Postgres cannot handle custom types, resolve everything to strings
|
||||||
|
switch input.(type) {
|
||||||
|
case *big.Int:
|
||||||
|
b := input.(*big.Int)
|
||||||
|
strValues[fieldName] = b.String()
|
||||||
|
case common.Address:
|
||||||
|
a := input.(common.Address)
|
||||||
|
strValues[fieldName] = a.String()
|
||||||
|
c.ContractInfo.AddTokenHolderAddress(a.String()) // cache address in a list of contract's token holder addresses
|
||||||
|
case common.Hash:
|
||||||
|
h := input.(common.Hash)
|
||||||
|
strValues[fieldName] = h.String()
|
||||||
|
case string:
|
||||||
|
strValues[fieldName] = input.(string)
|
||||||
|
case bool:
|
||||||
|
strValues[fieldName] = strconv.FormatBool(input.(bool))
|
||||||
|
case []byte:
|
||||||
|
b := input.([]byte)
|
||||||
|
strValues[fieldName] = string(b)
|
||||||
|
case byte:
|
||||||
|
b := input.(byte)
|
||||||
|
strValues[fieldName] = string(b)
|
||||||
|
default:
|
||||||
|
return nil, errors.New(fmt.Sprintf("error: unhandled abi type %T", input))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only hold onto logs that pass our address filter, if any
|
||||||
|
if c.ContractInfo.PassesEventFilter(strValues) {
|
||||||
|
raw, err := json.Marshal(log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
returnLogs = append(returnLogs, types.Log{
|
||||||
|
LogIndex: log.Index,
|
||||||
|
Values: strValues,
|
||||||
|
Raw: raw,
|
||||||
|
TransactionIndex: log.TxIndex,
|
||||||
|
Id: headerID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return returnLogs, nil
|
||||||
}
|
}
|
||||||
|
@ -15,3 +15,98 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package converter_test
|
package converter_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/light/converter"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Converter", func() {
|
||||||
|
var con *contract.Contract
|
||||||
|
var wantedEvents = []string{"Transfer", "Mint"}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
con = test_helpers.SetupTusdContract(wantedEvents, []string{})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Update", func() {
|
||||||
|
It("Updates contract info held by the converter", func() {
|
||||||
|
c := converter.NewConverter(con)
|
||||||
|
Expect(c.ContractInfo).To(Equal(con))
|
||||||
|
|
||||||
|
info := test_helpers.SetupTusdContract([]string{}, []string{})
|
||||||
|
c.Update(info)
|
||||||
|
Expect(c.ContractInfo).To(Equal(info))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Convert", func() {
|
||||||
|
It("Converts a watched event log to mapping of event input names to values", func() {
|
||||||
|
_, ok := con.Events["Approval"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
event, ok := con.Events["Transfer"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
|
||||||
|
c := converter.NewConverter(con)
|
||||||
|
logs, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(logs)).To(Equal(2))
|
||||||
|
|
||||||
|
sender1 := common.HexToAddress("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391")
|
||||||
|
sender2 := common.HexToAddress("0x000000000000000000000000000000000000000000000000000000000000af21")
|
||||||
|
value := helpers.BigFromString("1097077688018008265106216665536940668749033598146")
|
||||||
|
|
||||||
|
Expect(logs[0].Values["to"]).To(Equal(sender1.String()))
|
||||||
|
Expect(logs[0].Values["from"]).To(Equal(sender2.String()))
|
||||||
|
Expect(logs[0].Values["value"]).To(Equal(value.String()))
|
||||||
|
Expect(logs[0].Id).To(Equal(int64(232)))
|
||||||
|
Expect(logs[1].Values["to"]).To(Equal(sender2.String()))
|
||||||
|
Expect(logs[1].Values["from"]).To(Equal(sender1.String()))
|
||||||
|
Expect(logs[1].Values["value"]).To(Equal(value.String()))
|
||||||
|
Expect(logs[1].Id).To(Equal(int64(232)))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Keeps track of addresses it sees to grow a token holder address list for the contract", func() {
|
||||||
|
event, ok := con.Events["Transfer"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
|
||||||
|
c := converter.NewConverter(con)
|
||||||
|
_, err := c.Convert([]types.Log{mocks.MockTransferLog1, mocks.MockTransferLog2}, event, 232)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs["0x"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs[""]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
_, ok = con.TknHolderAddrs["0x09THISE21a5IS5cFAKE1D82fAND43bCE06MADEUP"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails with an empty contract", func() {
|
||||||
|
event := con.Events["Transfer"]
|
||||||
|
c := converter.NewConverter(&contract.Contract{})
|
||||||
|
_, err = c.Convert([]types.Log{mocks.MockTransferLog1}, event, 232)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
// VulcanizeDB
|
|
||||||
// Copyright © 2018 Vulcanize
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package repository
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventRepository interface {
|
|
||||||
PersistLog(event types.Log, contractAddr, contractName string) error
|
|
||||||
CreateEventTable(contractName string, event types.Log) (bool, error)
|
|
||||||
CreateContractSchema(contractName string) (bool, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type eventRepository struct {
|
|
||||||
db *postgres.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEventRepository(db *postgres.DB) *eventRepository {
|
|
||||||
return &eventRepository{
|
|
||||||
db: db,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a schema for the contract if needed
|
|
||||||
// Creates table for the watched contract event if needed
|
|
||||||
// Persists converted event log data into this custom table
|
|
||||||
func (r *eventRepository) PersistLog(event types.Log, contractAddr, contractName string) error {
|
|
||||||
_, err := r.CreateContractSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = r.CreateEventTable(contractAddr, event)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.persistLog(event, contractAddr, contractName)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a custom postgres command to persist logs for the given event
|
|
||||||
func (r *eventRepository) persistLog(event types.Log, contractAddr, contractName string) error {
|
|
||||||
// Begin postgres string
|
|
||||||
pgStr := fmt.Sprintf("INSERT INTO l%s.%s_event ", strings.ToLower(contractAddr), strings.ToLower(event.Name))
|
|
||||||
pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx"
|
|
||||||
|
|
||||||
// Pack the corresponding variables in a slice
|
|
||||||
var data []interface{}
|
|
||||||
data = append(data,
|
|
||||||
event.Id,
|
|
||||||
contractName,
|
|
||||||
event.Raw,
|
|
||||||
event.LogIndex,
|
|
||||||
event.TransactionIndex)
|
|
||||||
|
|
||||||
// Iterate over name-value pairs in the log adding
|
|
||||||
// names to the string and pushing values to the slice
|
|
||||||
counter := 0 // Keep track of number of inputs
|
|
||||||
for inputName, input := range event.Values {
|
|
||||||
counter += 1
|
|
||||||
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) // Add underscore after to avoid any collisions with reserved pg words
|
|
||||||
data = append(data, input)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish off the string and execute the command using the packed data
|
|
||||||
// For each input entry we created we add its postgres command variable to the string
|
|
||||||
pgStr = pgStr + ") VALUES ($1, $2, $3, $4, $5"
|
|
||||||
for i := 0; i < counter; i++ {
|
|
||||||
pgStr = pgStr + fmt.Sprintf(", $%d", i+6)
|
|
||||||
}
|
|
||||||
pgStr = pgStr + ")"
|
|
||||||
|
|
||||||
_, err := r.db.Exec(pgStr, data...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks for event table and creates it if it does not already exist
|
|
||||||
func (r *eventRepository) CreateEventTable(contractAddr string, event types.Log) (bool, error) {
|
|
||||||
tableExists, err := r.checkForTable(contractAddr, event.Name)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tableExists {
|
|
||||||
err = r.newEventTable(contractAddr, event)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !tableExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a table for the given contract and event
|
|
||||||
func (r *eventRepository) newEventTable(contractAddr string, event types.Log) error {
|
|
||||||
// Begin pg string
|
|
||||||
pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS l%s.%s_event ", strings.ToLower(contractAddr), strings.ToLower(event.Name))
|
|
||||||
pgStr = pgStr + "(id SERIAL, header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, token_name CHARACTER VARYING(66) NOT NULL, raw_log JSONB, log_idx INTEGER NOT NULL, tx_idx INTEGER NOT NULL,"
|
|
||||||
|
|
||||||
// Iterate over event fields, using their name and pgType to grow the string
|
|
||||||
for _, field := range event.Fields {
|
|
||||||
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
|
|
||||||
}
|
|
||||||
|
|
||||||
pgStr = pgStr + " UNIQUE (header_id, tx_idx, log_idx))"
|
|
||||||
_, err := r.db.Exec(pgStr)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a table already exists for the given contract and event
|
|
||||||
func (r *eventRepository) checkForTable(contractAddr string, eventName string) (bool, error) {
|
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'l%s' AND table_name = '%s_event')", strings.ToLower(contractAddr), strings.ToLower(eventName))
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
err := r.db.Get(&exists, pgStr)
|
|
||||||
|
|
||||||
return exists, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks for contract schema and creates it if it does not already exist
|
|
||||||
func (r *eventRepository) CreateContractSchema(contractAddr string) (bool, error) {
|
|
||||||
if contractAddr == "" {
|
|
||||||
return false, errors.New("error: no contract address specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
schemaExists, err := r.checkForSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !schemaExists {
|
|
||||||
err = r.newContractSchema(contractAddr)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return !schemaExists, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a schema for the given contract
|
|
||||||
func (r *eventRepository) newContractSchema(contractAddr string) error {
|
|
||||||
_, err := r.db.Exec("CREATE SCHEMA IF NOT EXISTS l" + strings.ToLower(contractAddr))
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checks if a schema already exists for the given contract
|
|
||||||
func (r *eventRepository) checkForSchema(contractAddr string) (bool, error) {
|
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'l%s')", strings.ToLower(contractAddr))
|
|
||||||
|
|
||||||
var exists bool
|
|
||||||
err := r.db.QueryRow(pgStr).Scan(&exists)
|
|
||||||
|
|
||||||
return exists, err
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
// VulcanizeDB
|
|
||||||
// Copyright © 2018 Vulcanize
|
|
||||||
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package repository_test
|
|
@ -17,30 +17,61 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const columnCacheSize = 1000
|
||||||
|
|
||||||
type HeaderRepository interface {
|
type HeaderRepository interface {
|
||||||
|
AddCheckColumn(eventID string) error
|
||||||
MarkHeaderChecked(headerID int64, eventID string) error
|
MarkHeaderChecked(headerID int64, eventID string) error
|
||||||
MissingHeaders(startingBlockNumber int64, endingBlockNumber int64, eventID string) ([]core.Header, error)
|
MissingHeaders(startingBlockNumber int64, endingBlockNumber int64, eventID string) ([]core.Header, error)
|
||||||
|
CheckCache(key string) (interface{}, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type headerRepository struct {
|
type headerRepository struct {
|
||||||
db *postgres.DB
|
db *postgres.DB
|
||||||
|
columns *lru.Cache // Cache created columns to minimize db connections
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHeaderRepository(db *postgres.DB) *headerRepository {
|
func NewHeaderRepository(db *postgres.DB) *headerRepository {
|
||||||
|
ccs, _ := lru.New(columnCacheSize)
|
||||||
return &headerRepository{
|
return &headerRepository{
|
||||||
db: db,
|
db: db,
|
||||||
|
columns: ccs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *headerRepository) AddCheckColumn(eventID string) error {
|
||||||
|
// Check cache to see if column already exists before querying pg
|
||||||
|
_, ok := r.columns.Get(eventID)
|
||||||
|
if ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStr := "ALTER TABLE public.checked_headers ADD COLUMN IF NOT EXISTS "
|
||||||
|
pgStr = pgStr + eventID + " BOOLEAN NOT NULL DEFAULT FALSE"
|
||||||
|
_, err := r.db.Exec(pgStr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add column name to cache
|
||||||
|
r.columns.Add(eventID, true)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *headerRepository) MarkHeaderChecked(headerID int64, eventID string) error {
|
func (r *headerRepository) MarkHeaderChecked(headerID int64, eventID string) error {
|
||||||
_, err := r.db.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`)
|
_, err := r.db.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`)
|
||||||
VALUES ($1, $2)
|
VALUES ($1, $2)
|
||||||
ON CONFLICT (header_id) DO
|
ON CONFLICT (header_id) DO
|
||||||
UPDATE SET `+eventID+` = $2`, headerID, true)
|
UPDATE SET `+eventID+` = $2`, headerID, true)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,3 +99,15 @@ func (r *headerRepository) MissingHeaders(startingBlockNumber int64, endingBlock
|
|||||||
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *headerRepository) CheckCache(key string) (interface{}, bool) {
|
||||||
|
return r.columns.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MarkHeaderCheckedInTransaction(headerID int64, tx *sql.Tx, eventID string) error {
|
||||||
|
_, err := tx.Exec(`INSERT INTO public.checked_headers (header_id, `+eventID+`)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
ON CONFLICT (header_id) DO
|
||||||
|
UPDATE SET `+eventID+` = $2`, headerID, true)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -15,3 +15,127 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package repository_test
|
package repository_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Repository", func() {
|
||||||
|
var db *postgres.DB
|
||||||
|
var r repository.HeaderRepository
|
||||||
|
var headerRepository repositories.HeaderRepository
|
||||||
|
var eventID, query string
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
db, _ = test_helpers.SetupDBandBC()
|
||||||
|
r = repository.NewHeaderRepository(db)
|
||||||
|
headerRepository = repositories.NewHeaderRepository(db)
|
||||||
|
eventID = "eventName_contractAddr"
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
test_helpers.TearDown(db)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("AddCheckColumn", func() {
|
||||||
|
It("Creates a column for the given eventID to mark if the header has been checked for that event", func() {
|
||||||
|
query = fmt.Sprintf("SELECT %s FROM checked_headers", eventID)
|
||||||
|
_, err := db.Exec(query)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
err = r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = db.Exec(query)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches column it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
_, ok := r.CheckCache(eventID)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
err := r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
v, ok := r.CheckCache(eventID)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("MissingHeaders", func() {
|
||||||
|
It("Returns all unchecked headers for the given eventID", func() {
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
err := r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
missingHeaders, err := r.MissingHeaders(6194630, 6194635, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(missingHeaders)).To(Equal(3))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails if eventID does not yet exist in check_headers table", func() {
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
err := r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = r.MissingHeaders(6194630, 6194635, "notEventId")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("MarkHeaderChecked", func() {
|
||||||
|
It("Marks the header checked for the given eventID", func() {
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
err := r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
missingHeaders, err := r.MissingHeaders(6194630, 6194635, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(missingHeaders)).To(Equal(3))
|
||||||
|
|
||||||
|
headerID := missingHeaders[0].Id
|
||||||
|
err = r.MarkHeaderChecked(headerID, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
missingHeaders, err = r.MissingHeaders(6194630, 6194635, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(missingHeaders)).To(Equal(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails if eventID does not yet exist in check_headers table", func() {
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
err := r.AddCheckColumn(eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
missingHeaders, err := r.MissingHeaders(6194630, 6194635, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(missingHeaders)).To(Equal(3))
|
||||||
|
|
||||||
|
headerID := missingHeaders[0].Id
|
||||||
|
err = r.MarkHeaderChecked(headerID, "notEventId")
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
missingHeaders, err = r.MissingHeaders(6194630, 6194635, eventID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(len(missingHeaders)).To(Equal(3))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/light/retriever"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Block Retriever", func() {
|
var _ = Describe("Block Retriever", func() {
|
||||||
@ -43,9 +44,9 @@ var _ = Describe("Block Retriever", func() {
|
|||||||
|
|
||||||
Describe("RetrieveFirstBlock", func() {
|
Describe("RetrieveFirstBlock", func() {
|
||||||
It("Retrieves block number of earliest header in the database", func() {
|
It("Retrieves block number of earliest header in the database", func() {
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader1)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader2)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader3)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
|
||||||
i, err := r.RetrieveFirstBlock()
|
i, err := r.RetrieveFirstBlock()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
@ -60,9 +61,9 @@ var _ = Describe("Block Retriever", func() {
|
|||||||
|
|
||||||
Describe("RetrieveMostRecentBlock", func() {
|
Describe("RetrieveMostRecentBlock", func() {
|
||||||
It("Retrieves the latest header's block number", func() {
|
It("Retrieves the latest header's block number", func() {
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader1)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader2)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader2)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader3)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
|
|
||||||
i, err := r.RetrieveMostRecentBlock()
|
i, err := r.RetrieveMostRecentBlock()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
func TestRetriever(t *testing.T) {
|
func TestRetriever(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Light Retriever Suite Test")
|
RunSpecs(t, "Light BLock Number Retriever Suite Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = BeforeSuite(func() {
|
var _ = BeforeSuite(func() {
|
||||||
|
@ -18,7 +18,8 @@ package transformer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
@ -31,12 +32,14 @@ import (
|
|||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
||||||
"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"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Requires a light synced vDB (headers) and a running eth node (or infura)
|
// Requires a light synced vDB (headers) and a running eth node (or infura)
|
||||||
type transformer struct {
|
type transformer struct {
|
||||||
// Database interfaces
|
// Database interfaces
|
||||||
repository.EventRepository // Holds transformed watched event log data
|
srep.EventRepository // Holds transformed watched event log data
|
||||||
repository.HeaderRepository // Interface for interaction with header repositories
|
repository.HeaderRepository // Interface for interaction with header repositories
|
||||||
|
|
||||||
// Pre-processing interfaces
|
// Pre-processing interfaces
|
||||||
@ -72,14 +75,14 @@ type transformer struct {
|
|||||||
func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *transformer {
|
func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *transformer {
|
||||||
|
|
||||||
return &transformer{
|
return &transformer{
|
||||||
Poller: poller.NewPoller(bc, db),
|
Poller: poller.NewPoller(bc, db, types.LightSync),
|
||||||
Fetcher: fetcher.NewFetcher(bc),
|
Fetcher: fetcher.NewFetcher(bc),
|
||||||
Parser: parser.NewParser(network),
|
Parser: parser.NewParser(network),
|
||||||
HeaderRepository: repository.NewHeaderRepository(db),
|
HeaderRepository: repository.NewHeaderRepository(db),
|
||||||
BlockRetriever: retriever.NewBlockRetriever(db),
|
BlockRetriever: retriever.NewBlockRetriever(db),
|
||||||
Converter: converter.NewConverter(&contract.Contract{}),
|
Converter: converter.NewConverter(&contract.Contract{}),
|
||||||
Contracts: map[string]*contract.Contract{},
|
Contracts: map[string]*contract.Contract{},
|
||||||
EventRepository: repository.NewEventRepository(db),
|
EventRepository: srep.NewEventRepository(db, types.LightSync),
|
||||||
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{},
|
||||||
@ -93,7 +96,7 @@ func NewTransformer(network string, bc core.BlockChain, db *postgres.DB) *transf
|
|||||||
// Uses parser to pull event info from abi
|
// Uses parser to pull event info from abi
|
||||||
// Use this info to generate event filters
|
// Use this info to generate event filters
|
||||||
func (tr *transformer) Init() error {
|
func (tr *transformer) Init() error {
|
||||||
|
// Iterate through all internal contract addresses
|
||||||
for contractAddr, subset := range tr.WatchedEvents {
|
for contractAddr, subset := range tr.WatchedEvents {
|
||||||
// Get Abi
|
// Get Abi
|
||||||
err := tr.Parser.Parse(contractAddr)
|
err := tr.Parser.Parse(contractAddr)
|
||||||
@ -101,7 +104,7 @@ func (tr *transformer) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get first block for contract and most recent block for the chain
|
// Get first block and most recent block number in the header repo
|
||||||
firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock()
|
firstBlock, err := tr.BlockRetriever.RetrieveFirstBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -111,7 +114,7 @@ func (tr *transformer) Init() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set to specified range if it falls within the contract's bounds
|
// Set to specified range if it falls within the bounds
|
||||||
if firstBlock < tr.ContractRanges[contractAddr][0] {
|
if firstBlock < tr.ContractRanges[contractAddr][0] {
|
||||||
firstBlock = tr.ContractRanges[contractAddr][0]
|
firstBlock = tr.ContractRanges[contractAddr][0]
|
||||||
}
|
}
|
||||||
@ -119,14 +122,11 @@ func (tr *transformer) Init() error {
|
|||||||
lastBlock = tr.ContractRanges[contractAddr][1]
|
lastBlock = tr.ContractRanges[contractAddr][1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get contract name
|
// Get contract name if it has one
|
||||||
var name = new(string)
|
var name = new(string)
|
||||||
err = tr.FetchContractData(tr.Abi(), contractAddr, "name", nil, &name, lastBlock)
|
tr.FetchContractData(tr.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 filter addresses
|
||||||
EventAddrs := map[string]bool{}
|
EventAddrs := map[string]bool{}
|
||||||
for _, addr := range tr.EventAddrs[contractAddr] {
|
for _, addr := range tr.EventAddrs[contractAddr] {
|
||||||
EventAddrs[addr] = true
|
EventAddrs[addr] = true
|
||||||
@ -142,6 +142,7 @@ func (tr *transformer) Init() error {
|
|||||||
Network: tr.Network,
|
Network: tr.Network,
|
||||||
Address: contractAddr,
|
Address: contractAddr,
|
||||||
Abi: tr.Abi(),
|
Abi: tr.Abi(),
|
||||||
|
ParsedAbi: tr.ParsedAbi(),
|
||||||
StartingBlock: firstBlock,
|
StartingBlock: firstBlock,
|
||||||
LastBlock: lastBlock,
|
LastBlock: lastBlock,
|
||||||
Events: tr.GetEvents(subset),
|
Events: tr.GetEvents(subset),
|
||||||
@ -151,7 +152,7 @@ func (tr *transformer) Init() error {
|
|||||||
TknHolderAddrs: map[string]bool{},
|
TknHolderAddrs: map[string]bool{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store contract info for further processing
|
// Store contract info for execution
|
||||||
tr.Contracts[contractAddr] = info
|
tr.Contracts[contractAddr] = info
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,53 +161,69 @@ func (tr *transformer) Init() error {
|
|||||||
|
|
||||||
func (tr *transformer) Execute() error {
|
func (tr *transformer) Execute() error {
|
||||||
if len(tr.Contracts) == 0 {
|
if len(tr.Contracts) == 0 {
|
||||||
return errors.New("error: transformer has no initialized contracts to work with")
|
return errors.New("error: transformer has no initialized contracts")
|
||||||
}
|
}
|
||||||
// Iterate through all internal contracts
|
// Iterate through all internal contracts
|
||||||
for _, con := range tr.Contracts {
|
for _, con := range tr.Contracts {
|
||||||
|
|
||||||
// Update converter with current contract
|
// Update converter with current contract
|
||||||
tr.Update(con)
|
tr.Update(con)
|
||||||
|
|
||||||
|
// Iterate through events
|
||||||
for _, event := range con.Events {
|
for _, event := range con.Events {
|
||||||
topics := [][]common.Hash{{common.HexToHash(event.Sig())}}
|
// Filter using the event signature
|
||||||
eventId := event.Name + "_" + con.Address
|
topics := [][]common.Hash{{common.HexToHash(helpers.GenerateSignature(event.Sig()))}}
|
||||||
|
|
||||||
|
// Generate eventID and use it to create a checked_header column if one does not already exist
|
||||||
|
eventId := strings.ToLower(event.Name + "_" + con.Address)
|
||||||
|
if err := tr.AddCheckColumn(eventId); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find unchecked headers for this event
|
||||||
missingHeaders, err := tr.MissingHeaders(con.StartingBlock, con.LastBlock, eventId)
|
missingHeaders, err := tr.MissingHeaders(con.StartingBlock, con.LastBlock, eventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iterate over headers
|
||||||
for _, header := range missingHeaders {
|
for _, header := range missingHeaders {
|
||||||
|
// And fetch event logs using the header, contract address, and topics filter
|
||||||
logs, err := tr.FetchLogs([]string{con.Address}, topics, header)
|
logs, err := tr.FetchLogs([]string{con.Address}, topics, header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark the header checked for this eventID and continue to next iteration if no logs are found
|
||||||
if len(logs) < 1 {
|
if len(logs) < 1 {
|
||||||
err = tr.MarkHeaderChecked(header.Id, eventId)
|
err = tr.MarkHeaderChecked(header.Id, eventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, l := range logs {
|
// Convert logs into custom type
|
||||||
mapping, err := tr.Convert(l, event)
|
convertedLogs, err := tr.Convert(logs, event, header.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if mapping == nil {
|
if len(convertedLogs) < 1 {
|
||||||
break
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tr.PersistLog(*mapping, con.Address, con.Name)
|
// If logs aren't empty, persist them
|
||||||
if err != nil {
|
err = tr.PersistLogs(convertedLogs, event, con.Address, con.Name)
|
||||||
return err
|
if 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
|
||||||
|
@ -17,9 +17,7 @@
|
|||||||
package transformer_test
|
package transformer_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
@ -29,6 +27,7 @@ import (
|
|||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/light/transformer"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Transformer", func() {
|
var _ = Describe("Transformer", func() {
|
||||||
@ -36,7 +35,7 @@ var _ = Describe("Transformer", func() {
|
|||||||
var err error
|
var err error
|
||||||
var blockChain core.BlockChain
|
var blockChain core.BlockChain
|
||||||
var headerRepository repositories.HeaderRepository
|
var headerRepository repositories.HeaderRepository
|
||||||
rand.Seed(time.Now().UnixNano())
|
var headerID int64
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
db, blockChain = test_helpers.SetupDBandBC()
|
db, blockChain = test_helpers.SetupDBandBC()
|
||||||
@ -94,8 +93,8 @@ var _ = Describe("Transformer", func() {
|
|||||||
|
|
||||||
Describe("Init", func() {
|
Describe("Init", func() {
|
||||||
It("Initializes transformer's contract objects", func() {
|
It("Initializes transformer's contract objects", func() {
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader1)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader3)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
t := transformer.NewTransformer("", blockChain, db)
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
err = t.Init()
|
err = t.Init()
|
||||||
@ -111,7 +110,7 @@ var _ = Describe("Transformer", func() {
|
|||||||
Expect(c.Address).To(Equal(constants.TusdContractAddress))
|
Expect(c.Address).To(Equal(constants.TusdContractAddress))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Fails to initialize if first and most recent blocks cannot be fetched from vDB", func() {
|
It("Fails to initialize if first and most recent block numbers cannot be fetched from vDB headers table", func() {
|
||||||
t := transformer.NewTransformer("", blockChain, db)
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
err = t.Init()
|
err = t.Init()
|
||||||
@ -119,8 +118,8 @@ var _ = Describe("Transformer", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("Does nothing if watched events are unset", func() {
|
It("Does nothing if watched events are unset", func() {
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader1)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
headerRepository.CreateOrUpdateHeader(test_helpers.MockHeader3)
|
headerRepository.CreateOrUpdateHeader(mocks.MockHeader3)
|
||||||
t := transformer.NewTransformer("", blockChain, db)
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
err = t.Init()
|
err = t.Init()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
@ -131,6 +130,96 @@ var _ = Describe("Transformer", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
Describe("Execute", func() {
|
Describe("Execute", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
header1, err := blockChain.GetHeaderByNumber(6791668)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
header2, err := blockChain.GetHeaderByNumber(6791669)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
header3, err := blockChain.GetHeaderByNumber(6791670)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
headerRepository.CreateOrUpdateHeader(header1)
|
||||||
|
headerID, err = headerRepository.CreateOrUpdateHeader(header2)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
headerRepository.CreateOrUpdateHeader(header3)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Transforms watched contract data into custom repositories", func() {
|
||||||
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
|
t.SetMethods(constants.TusdContractAddress, nil)
|
||||||
|
|
||||||
|
err = t.Init()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = t.Execute()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
log := test_helpers.LightTransferLog{}
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event", constants.TusdContractAddress)).StructScan(&log)
|
||||||
|
|
||||||
|
// We don't know vulcID, so compare individual fields instead of complete structures
|
||||||
|
Expect(log.HeaderID).To(Equal(headerID))
|
||||||
|
Expect(log.From).To(Equal("0x1062a747393198f70F71ec65A582423Dba7E5Ab3"))
|
||||||
|
Expect(log.To).To(Equal("0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0"))
|
||||||
|
Expect(log.Value).To(Equal("9998940000000000000000"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Keeps track of contract-related addresses while transforming event data", func() {
|
||||||
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
|
t.SetMethods(constants.TusdContractAddress, nil)
|
||||||
|
err = t.Init()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
c, ok := t.Contracts[constants.TusdContractAddress]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
|
||||||
|
err = t.Execute()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
b, ok := c.TknHolderAddrs["0x1062a747393198f70F71ec65A582423Dba7E5Ab3"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
b, ok = c.TknHolderAddrs["0x2930096dB16b4A44Ecd4084EA4bd26F7EeF1AEf0"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
_, ok = c.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843b1234567890"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
_, ok = c.TknHolderAddrs["0x"]
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Polls given methods using generated token holder address", func() {
|
||||||
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
|
t.SetMethods(constants.TusdContractAddress, []string{"balanceOf"})
|
||||||
|
err = t.Init()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = t.Execute()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
res := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x1062a747393198f70F71ec65A582423Dba7E5Ab3' AND block = '6791669'", constants.TusdContractAddress)).StructScan(&res)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(res.Balance).To(Equal("55849938025000000000000"))
|
||||||
|
Expect(res.TokenName).To(Equal("TrueUSD"))
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method WHERE who_ = '0x09BbBBE21a5975cAc061D82f7b843b1234567890' AND block = '6791669'", constants.TusdContractAddress)).StructScan(&res)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails if initialization has not been done", func() {
|
||||||
|
t := transformer.NewTransformer("", blockChain, db)
|
||||||
|
t.SetEvents(constants.TusdContractAddress, []string{"Transfer"})
|
||||||
|
t.SetMethods(constants.TusdContractAddress, nil)
|
||||||
|
|
||||||
|
err = t.Execute()
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -22,6 +22,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"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Contract", func() {
|
var _ = Describe("Contract", func() {
|
||||||
@ -38,11 +39,11 @@ var _ = Describe("Contract", func() {
|
|||||||
|
|
||||||
val, ok := info.Filters["Transfer"]
|
val, ok := info.Filters["Transfer"]
|
||||||
Expect(ok).To(Equal(true))
|
Expect(ok).To(Equal(true))
|
||||||
Expect(val).To(Equal(test_helpers.ExpectedTransferFilter))
|
Expect(val).To(Equal(mocks.ExpectedTransferFilter))
|
||||||
|
|
||||||
val, ok = info.Filters["Approval"]
|
val, ok = info.Filters["Approval"]
|
||||||
Expect(ok).To(Equal(true))
|
Expect(ok).To(Equal(true))
|
||||||
Expect(val).To(Equal(test_helpers.ExpectedApprovalFilter))
|
Expect(val).To(Equal(mocks.ExpectedApprovalFilter))
|
||||||
|
|
||||||
val, ok = info.Filters["Mint"]
|
val, ok = info.Filters["Mint"]
|
||||||
Expect(ok).To(Equal(false))
|
Expect(ok).To(Equal(false))
|
||||||
|
@ -33,7 +33,7 @@ import (
|
|||||||
"github.com/vulcanize/vulcanizedb/pkg/geth/node"
|
"github.com/vulcanize/vulcanizedb/pkg/geth/node"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/contract"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TransferLog struct {
|
type TransferLog struct {
|
||||||
@ -47,6 +47,18 @@ type TransferLog struct {
|
|||||||
Value string `db:"value_"`
|
Value string `db:"value_"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LightTransferLog struct {
|
||||||
|
Id int64 `db:"id"`
|
||||||
|
HeaderID int64 `db:"header_id"`
|
||||||
|
TokenName string `db:"token_name"`
|
||||||
|
LogIndex int64 `db:"log_idx"`
|
||||||
|
TxIndex int64 `db:"tx_idx"`
|
||||||
|
From string `db:"from_"`
|
||||||
|
To string `db:"to_"`
|
||||||
|
Value string `db:"value_"`
|
||||||
|
RawLog []byte `db:"raw_log"`
|
||||||
|
}
|
||||||
|
|
||||||
type BalanceOf struct {
|
type BalanceOf struct {
|
||||||
Id int64 `db:"id"`
|
Id int64 `db:"id"`
|
||||||
TokenName string `db:"token_name"`
|
TokenName string `db:"token_name"`
|
||||||
@ -119,8 +131,8 @@ func SetupTusdRepo(vulcanizeLogId *int64, wantedEvents, wantedMethods []string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract {
|
func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract {
|
||||||
p := parser.NewParser("")
|
p := mocks.NewParser(constants.TusdAbiString)
|
||||||
err := p.Parse(constants.TusdContractAddress)
|
err := p.Parse()
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
return &contract.Contract{
|
return &contract.Contract{
|
||||||
@ -139,25 +151,40 @@ func SetupTusdContract(wantedEvents, wantedMethods []string) *contract.Contract
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TearDown(db *postgres.DB) {
|
func TearDown(db *postgres.DB) {
|
||||||
_, err := db.Query(`DELETE FROM blocks`)
|
tx, err := db.Begin()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DELETE FROM headers`)
|
_, err = tx.Exec(`DELETE FROM blocks`)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DELETE FROM checked_headers`)
|
_, err = tx.Exec(`DELETE FROM headers`)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DELETE FROM logs`)
|
_, err = tx.Exec(`DELETE FROM checked_headers`)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DELETE FROM transactions`)
|
_, err = tx.Exec(`DELETE FROM logs`)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DELETE FROM receipts`)
|
_, err = tx.Exec(`DELETE FROM transactions`)
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
_, err = db.Query(`DROP SCHEMA IF EXISTS c0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E CASCADE`)
|
_, err = tx.Exec(`DELETE FROM receipts`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = tx.Exec(`ALTER TABLE public.checked_headers DROP COLUMN IF EXISTS eventName_contractAddr`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = tx.Exec(`ALTER TABLE public.checked_headers DROP COLUMN IF EXISTS transfer_0x8dd5fbce2f6a956c3022ba3663759011dd51e73e`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = tx.Exec(`DROP SCHEMA IF EXISTS full_0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E CASCADE`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
_, err = tx.Exec(`DROP SCHEMA IF EXISTS light_0x8dd5fbCe2F6a956C3022bA3663759011Dd51e73E CASCADE`)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,15 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package test_helpers
|
package mocks
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
@ -126,3 +130,44 @@ var MockHeader3 = core.Header{
|
|||||||
Raw: rawFakeHeader,
|
Raw: rawFakeHeader,
|
||||||
Timestamp: "50000030",
|
Timestamp: "50000030",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var MockTransferLog1 = types.Log{
|
||||||
|
Index: 1,
|
||||||
|
Address: common.HexToAddress(constants.TusdContractAddress),
|
||||||
|
BlockNumber: 5488076,
|
||||||
|
TxIndex: 110,
|
||||||
|
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash(constants.TransferEvent.Signature()),
|
||||||
|
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
|
||||||
|
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"),
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var MockTransferLog2 = types.Log{
|
||||||
|
Index: 3,
|
||||||
|
Address: common.HexToAddress(constants.TusdContractAddress),
|
||||||
|
BlockNumber: 5488077,
|
||||||
|
TxIndex: 2,
|
||||||
|
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash(constants.TransferEvent.Signature()),
|
||||||
|
common.HexToHash("0x9dd48110dcc444fdc242510c09bbbbe21a5975cac061d82f7b843bce061ba391"),
|
||||||
|
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
|
||||||
|
}
|
||||||
|
|
||||||
|
var MockMintLog = types.Log{
|
||||||
|
Index: 10,
|
||||||
|
Address: common.HexToAddress(constants.TusdContractAddress),
|
||||||
|
BlockNumber: 548808,
|
||||||
|
TxIndex: 50,
|
||||||
|
TxHash: common.HexToHash("0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6minty"),
|
||||||
|
Topics: []common.Hash{
|
||||||
|
common.HexToHash(constants.MintEvent.Signature()),
|
||||||
|
common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000af21"),
|
||||||
|
},
|
||||||
|
Data: hexutil.MustDecode("0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc200000000000000000000000089d24a6b4ccb1b6faa2625fe562bdd9a23260359000000000000000000000000000000000000000000000000392d2e2bda9c00000000000000000000000000000000000000000000000000927f41fa0a4a418000000000000000000000000000000000000000000000000000000000005adcfebe"),
|
||||||
|
}
|
@ -23,7 +23,7 @@ import (
|
|||||||
|
|
||||||
"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/constants"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/mocks"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/helpers/test_helpers/mocks"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/parser"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -42,10 +42,10 @@ type poller struct {
|
|||||||
contract contract.Contract
|
contract contract.Contract
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPoller(blockChain core.BlockChain, db *postgres.DB) *poller {
|
func NewPoller(blockChain core.BlockChain, db *postgres.DB, mode types.Mode) *poller {
|
||||||
|
|
||||||
return &poller{
|
return &poller{
|
||||||
MethodRepository: repository.NewMethodRepository(db),
|
MethodRepository: repository.NewMethodRepository(db, mode),
|
||||||
bc: blockChain,
|
bc: blockChain,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,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/poller"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/poller"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Poller", func() {
|
var _ = Describe("Poller", func() {
|
||||||
@ -37,78 +38,80 @@ var _ = Describe("Poller", func() {
|
|||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var bc core.BlockChain
|
var bc core.BlockChain
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
db, bc = test_helpers.SetupDBandBC()
|
|
||||||
p = poller.NewPoller(bc, db)
|
|
||||||
})
|
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
test_helpers.TearDown(db)
|
test_helpers.TearDown(db)
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("PollContract", func() {
|
Describe("Full sync mode", func() {
|
||||||
It("Polls specified contract methods using contract's token holder address list", func() {
|
BeforeEach(func() {
|
||||||
con = test_helpers.SetupTusdContract(nil, []string{"balanceOf"})
|
db, bc = test_helpers.SetupDBandBC()
|
||||||
Expect(con.Abi).To(Equal(constants.TusdAbiString))
|
p = poller.NewPoller(bc, db, types.FullSync)
|
||||||
con.StartingBlock = 6707322
|
|
||||||
con.LastBlock = 6707323
|
|
||||||
con.TknHolderAddrs = map[string]bool{
|
|
||||||
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
|
|
||||||
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := p.PollContract(*con)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
scanStruct := test_helpers.BalanceOf{}
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(scanStruct.Balance).To(Equal("66386309548896882859581786"))
|
|
||||||
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(scanStruct.Balance).To(Equal("66386309548896882859581786"))
|
|
||||||
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(scanStruct.Balance).To(Equal("17982350181394112023885864"))
|
|
||||||
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(scanStruct.Balance).To(Equal("17982350181394112023885864"))
|
|
||||||
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Does not poll and persist any methods if none are specified", func() {
|
Describe("PollContract", func() {
|
||||||
con = test_helpers.SetupTusdContract(nil, nil)
|
It("Polls specified contract methods using contract's token holder address list", func() {
|
||||||
Expect(con.Abi).To(Equal(constants.TusdAbiString))
|
con = test_helpers.SetupTusdContract(nil, []string{"balanceOf"})
|
||||||
con.StartingBlock = 6707322
|
Expect(con.Abi).To(Equal(constants.TusdAbiString))
|
||||||
con.LastBlock = 6707323
|
con.StartingBlock = 6707322
|
||||||
con.TknHolderAddrs = map[string]bool{
|
con.LastBlock = 6707323
|
||||||
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
|
con.TknHolderAddrs = map[string]bool{
|
||||||
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
|
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
|
||||||
}
|
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
|
||||||
|
}
|
||||||
|
|
||||||
err := p.PollContract(*con)
|
err := p.PollContract(*con)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
scanStruct := test_helpers.BalanceOf{}
|
scanStruct := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(scanStruct.Balance).To(Equal("66386309548896882859581786"))
|
||||||
|
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(scanStruct.Balance).To(Equal("66386309548896882859581786"))
|
||||||
|
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(scanStruct.Balance).To(Equal("17982350181394112023885864"))
|
||||||
|
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE' AND block = '6707323'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(scanStruct.Balance).To(Equal("17982350181394112023885864"))
|
||||||
|
Expect(scanStruct.TokenName).To(Equal("TrueUSD"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Does not poll and persist any methods if none are specified", func() {
|
||||||
|
con = test_helpers.SetupTusdContract(nil, nil)
|
||||||
|
Expect(con.Abi).To(Equal(constants.TusdAbiString))
|
||||||
|
con.StartingBlock = 6707322
|
||||||
|
con.LastBlock = 6707323
|
||||||
|
con.TknHolderAddrs = map[string]bool{
|
||||||
|
"0xfE9e8709d3215310075d67E3ed32A380CCf451C8": true,
|
||||||
|
"0x3f5CE5FBFe3E9af3971dD833D26bA9b5C936f0bE": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := p.PollContract(*con)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
scanStruct := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method WHERE who_ = '0xfE9e8709d3215310075d67E3ed32A380CCf451C8' AND block = '6707322'", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Describe("PollMethod", func() {
|
Describe("PollMethod", func() {
|
||||||
It("Polls a single contract method", func() {
|
It("Polls a single contract method", func() {
|
||||||
var name = new(string)
|
var name = new(string)
|
||||||
err := p.FetchContractData(constants.TusdAbiString, constants.TusdContractAddress, "name", nil, &name, 6197514)
|
err := p.FetchContractData(constants.TusdAbiString, constants.TusdContractAddress, "name", nil, &name, 6197514)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(*name).To(Equal("TrueUSD"))
|
Expect(*name).To(Equal("TrueUSD"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
316
pkg/omni/shared/repository/event_repository.go
Normal file
316
pkg/omni/shared/repository/event_repository.go
Normal file
@ -0,0 +1,316 @@
|
|||||||
|
// VulcanizeDB
|
||||||
|
// Copyright © 2018 Vulcanize
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/golang-lru"
|
||||||
|
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Number of contract address and method ids to keep in cache
|
||||||
|
contractCacheSize = 100
|
||||||
|
eventChacheSize = 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Event repository is used to persist event data into custom tables
|
||||||
|
type EventRepository interface {
|
||||||
|
PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error
|
||||||
|
CreateEventTable(contractAddr string, event types.Event) (bool, error)
|
||||||
|
CreateContractSchema(contractName string) (bool, error)
|
||||||
|
CheckSchemaCache(key string) (interface{}, bool)
|
||||||
|
CheckTableCache(key string) (interface{}, bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
type eventRepository struct {
|
||||||
|
db *postgres.DB
|
||||||
|
mode types.Mode
|
||||||
|
schemas *lru.Cache // Cache names of recently used schemas to minimize db connections
|
||||||
|
tables *lru.Cache // Cache names of recently used tables to minimize db connections
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventRepository(db *postgres.DB, mode types.Mode) *eventRepository {
|
||||||
|
ccs, _ := lru.New(contractCacheSize)
|
||||||
|
ecs, _ := lru.New(eventChacheSize)
|
||||||
|
return &eventRepository{
|
||||||
|
db: db,
|
||||||
|
mode: mode,
|
||||||
|
schemas: ccs,
|
||||||
|
tables: ecs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a schema for the contract if needed
|
||||||
|
// Creates table for the watched contract event if needed
|
||||||
|
// Persists converted event log data into this custom table
|
||||||
|
func (r *eventRepository) PersistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
|
||||||
|
if logs == nil {
|
||||||
|
return errors.New("event repository error: passed a nil log slice")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(logs) == 0 {
|
||||||
|
return errors.New("event repository error: passed an empty log slice")
|
||||||
|
}
|
||||||
|
_, err := r.CreateContractSchema(contractAddr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.CreateEventTable(contractAddr, eventInfo)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.persistLogs(logs, eventInfo, contractAddr, contractName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *eventRepository) persistLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
|
||||||
|
var err error
|
||||||
|
switch r.mode {
|
||||||
|
case types.LightSync:
|
||||||
|
err = r.persistLightSyncLogs(logs, eventInfo, contractAddr, contractName)
|
||||||
|
case types.FullSync:
|
||||||
|
err = r.persistFullSyncLogs(logs, eventInfo, contractAddr, contractName)
|
||||||
|
default:
|
||||||
|
return errors.New("event repository error: unhandled mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a custom postgres command to persist logs for the given event (compatible with light synced vDB)
|
||||||
|
func (r *eventRepository) persistLightSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
|
||||||
|
tx, err := r.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range logs {
|
||||||
|
// Begin pg query string
|
||||||
|
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name))
|
||||||
|
pgStr = pgStr + "(header_id, token_name, raw_log, log_idx, tx_idx"
|
||||||
|
el := len(event.Values)
|
||||||
|
|
||||||
|
// Pack the corresponding variables in a slice
|
||||||
|
data := make([]interface{}, 0, 5+el)
|
||||||
|
data = append(data,
|
||||||
|
event.Id,
|
||||||
|
contractName,
|
||||||
|
event.Raw,
|
||||||
|
event.LogIndex,
|
||||||
|
event.TransactionIndex)
|
||||||
|
|
||||||
|
// Iterate over inputs and append name to query string and value to input data
|
||||||
|
for inputName, input := range event.Values {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName)) // Add underscore after to avoid any collisions with reserved pg words
|
||||||
|
data = append(data, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each input entry we created we add its postgres command variable to the string
|
||||||
|
pgStr = pgStr + ") VALUES ($1, $2, $3, $4, $5"
|
||||||
|
for i := 0; i < el; i++ {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(", $%d", i+6)
|
||||||
|
}
|
||||||
|
pgStr = pgStr + ")"
|
||||||
|
|
||||||
|
_, err = tx.Exec(pgStr, data...)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eventId := strings.ToLower(eventInfo.Name + "_" + contractAddr)
|
||||||
|
err = repository.MarkHeaderCheckedInTransaction(logs[0].Id, tx, eventId)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a custom postgres command to persist logs for the given event (compatible with fully synced vDB)
|
||||||
|
func (r *eventRepository) persistFullSyncLogs(logs []types.Log, eventInfo types.Event, contractAddr, contractName string) error {
|
||||||
|
tx, err := r.db.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, event := range logs {
|
||||||
|
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_event ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventInfo.Name))
|
||||||
|
pgStr = pgStr + "(vulcanize_log_id, token_name, block, tx"
|
||||||
|
el := len(event.Values)
|
||||||
|
|
||||||
|
data := make([]interface{}, 0, 4+el)
|
||||||
|
data = append(data,
|
||||||
|
event.Id,
|
||||||
|
contractName,
|
||||||
|
event.Block,
|
||||||
|
event.Tx)
|
||||||
|
|
||||||
|
for inputName, input := range event.Values {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(inputName))
|
||||||
|
data = append(data, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pgStr = pgStr + ") VALUES ($1, $2, $3, $4"
|
||||||
|
for i := 0; i < el; i++ {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(", $%d", i+5)
|
||||||
|
}
|
||||||
|
pgStr = pgStr + ") ON CONFLICT (vulcanize_log_id) DO NOTHING"
|
||||||
|
|
||||||
|
_, err = tx.Exec(pgStr, data...)
|
||||||
|
if err != nil {
|
||||||
|
tx.Rollback()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tx.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for event table and creates it if it does not already exist
|
||||||
|
// Returns true if it created a new table; returns false if table already existed
|
||||||
|
func (r *eventRepository) CreateEventTable(contractAddr string, event types.Event) (bool, error) {
|
||||||
|
tableID := fmt.Sprintf("%s_%s.%s_event", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(event.Name))
|
||||||
|
|
||||||
|
// Check cache before querying pq to see if table exists
|
||||||
|
_, ok := r.tables.Get(tableID)
|
||||||
|
if ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
tableExists, err := r.checkForTable(contractAddr, event.Name)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tableExists {
|
||||||
|
err = r.newEventTable(tableID, event)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add table id to cache
|
||||||
|
r.tables.Add(tableID, true)
|
||||||
|
|
||||||
|
return !tableExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a table for the given contract and event
|
||||||
|
func (r *eventRepository) newEventTable(tableID string, event types.Event) error {
|
||||||
|
// Begin pg string
|
||||||
|
var pgStr = fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s ", tableID)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Handle different modes
|
||||||
|
switch r.mode {
|
||||||
|
case types.FullSync:
|
||||||
|
pgStr = pgStr + "(id SERIAL, vulcanize_log_id INTEGER NOT NULL UNIQUE, token_name CHARACTER VARYING(66) NOT NULL, block INTEGER NOT NULL, tx CHARACTER VARYING(66) NOT NULL,"
|
||||||
|
|
||||||
|
// Iterate over event fields, using their name and pgType to grow the string
|
||||||
|
for _, field := range event.Fields {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
|
||||||
|
}
|
||||||
|
pgStr = pgStr + " CONSTRAINT log_index_fk FOREIGN KEY (vulcanize_log_id) REFERENCES logs (id) ON DELETE CASCADE)"
|
||||||
|
case types.LightSync:
|
||||||
|
pgStr = pgStr + "(id SERIAL, header_id INTEGER NOT NULL REFERENCES headers (id) ON DELETE CASCADE, token_name CHARACTER VARYING(66) NOT NULL, raw_log JSONB, log_idx INTEGER NOT NULL, tx_idx INTEGER NOT NULL,"
|
||||||
|
|
||||||
|
for _, field := range event.Fields {
|
||||||
|
pgStr = pgStr + fmt.Sprintf(" %s_ %s NOT NULL,", strings.ToLower(field.Name), field.PgType)
|
||||||
|
}
|
||||||
|
pgStr = pgStr + " UNIQUE (header_id, tx_idx, log_idx))"
|
||||||
|
default:
|
||||||
|
return errors.New("unhandled repository mode")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = r.db.Exec(pgStr)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a table already exists for the given contract and event
|
||||||
|
func (r *eventRepository) checkForTable(contractAddr string, eventName string) (bool, error) {
|
||||||
|
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_event')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(eventName))
|
||||||
|
|
||||||
|
var exists bool
|
||||||
|
err := r.db.Get(&exists, pgStr)
|
||||||
|
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for contract schema and creates it if it does not already exist
|
||||||
|
// Returns true if it created a new schema; returns false if schema already existed
|
||||||
|
func (r *eventRepository) CreateContractSchema(contractAddr string) (bool, error) {
|
||||||
|
if contractAddr == "" {
|
||||||
|
return false, errors.New("error: no contract address specified")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache before querying pq to see if schema exists
|
||||||
|
_, ok := r.schemas.Get(contractAddr)
|
||||||
|
if ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
schemaExists, err := r.checkForSchema(contractAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if !schemaExists {
|
||||||
|
err = r.newContractSchema(contractAddr)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add schema name to cache
|
||||||
|
r.schemas.Add(contractAddr, true)
|
||||||
|
|
||||||
|
return !schemaExists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a schema for the given contract
|
||||||
|
func (r *eventRepository) newContractSchema(contractAddr string) error {
|
||||||
|
_, err := r.db.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks if a schema already exists for the given contract
|
||||||
|
func (r *eventRepository) checkForSchema(contractAddr string) (bool, error) {
|
||||||
|
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr))
|
||||||
|
|
||||||
|
var exists bool
|
||||||
|
err := r.db.QueryRow(pgStr).Scan(&exists)
|
||||||
|
|
||||||
|
return exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *eventRepository) CheckSchemaCache(key string) (interface{}, bool) {
|
||||||
|
return r.schemas.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *eventRepository) CheckTableCache(key string) (interface{}, bool) {
|
||||||
|
return r.tables.Get(key)
|
||||||
|
}
|
349
pkg/omni/shared/repository/event_repository_test.go
Normal file
349
pkg/omni/shared/repository/event_repository_test.go
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
// VulcanizeDB
|
||||||
|
// Copyright © 2018 Vulcanize
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package repository_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
geth "github.com/ethereum/go-ethereum/core/types"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
fc "github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
||||||
|
lc "github.com/vulcanize/vulcanizedb/pkg/omni/light/converter"
|
||||||
|
lr "github.com/vulcanize/vulcanizedb/pkg/omni/light/repository"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
|
"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/mocks"
|
||||||
|
sr "github.com/vulcanize/vulcanizedb/pkg/omni/shared/repository"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("Repository", func() {
|
||||||
|
var db *postgres.DB
|
||||||
|
var dataStore sr.EventRepository
|
||||||
|
var err error
|
||||||
|
var log *types.Log
|
||||||
|
var logs []types.Log
|
||||||
|
var con *contract.Contract
|
||||||
|
var vulcanizeLogId int64
|
||||||
|
var wantedEvents = []string{"Transfer"}
|
||||||
|
var event types.Event
|
||||||
|
var headerID int64
|
||||||
|
var mockEvent = mocks.MockTranferEvent
|
||||||
|
var mockLog1 = mocks.MockTransferLog1
|
||||||
|
var mockLog2 = mocks.MockTransferLog2
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
db, con = test_helpers.SetupTusdRepo(&vulcanizeLogId, wantedEvents, []string{})
|
||||||
|
mockEvent.LogID = vulcanizeLogId
|
||||||
|
|
||||||
|
event = con.Events["Transfer"]
|
||||||
|
err = con.GenerateFilters()
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
test_helpers.TearDown(db)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Full sync mode", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
dataStore = sr.NewEventRepository(db, types.FullSync)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateContractSchema", func() {
|
||||||
|
It("Creates schema if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
_, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateEventTable", func() {
|
||||||
|
It("Creates table if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
tableID := fmt.Sprintf("%s_%s.%s_event", types.FullSync, strings.ToLower(con.Address), strings.ToLower(event.Name))
|
||||||
|
_, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("PersistLogs", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
c := fc.NewConverter(con)
|
||||||
|
log, err = c.Convert(mockEvent, event)
|
||||||
|
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() {
|
||||||
|
err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
b, ok := con.TknHolderAddrs["0x000000000000000000000000000000000000Af21"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
b, ok = con.TknHolderAddrs["0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"]
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(b).To(Equal(true))
|
||||||
|
|
||||||
|
scanLog := test_helpers.TransferLog{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
expectedLog := test_helpers.TransferLog{
|
||||||
|
Id: 1,
|
||||||
|
VulvanizeLogId: vulcanizeLogId,
|
||||||
|
TokenName: "TrueUSD",
|
||||||
|
Block: 5488076,
|
||||||
|
Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
|
||||||
|
From: "0x000000000000000000000000000000000000Af21",
|
||||||
|
To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391",
|
||||||
|
Value: "1097077688018008265106216665536940668749033598146",
|
||||||
|
}
|
||||||
|
Expect(scanLog).To(Equal(expectedLog))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Doesn't persist duplicate event logs", func() {
|
||||||
|
// Try to persist the same log twice in a single call
|
||||||
|
err = dataStore.PersistLogs([]types.Log{*log, *log}, event, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
scanLog := test_helpers.TransferLog{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.transfer_event", constants.TusdContractAddress)).StructScan(&scanLog)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
expectedLog := test_helpers.TransferLog{
|
||||||
|
Id: 1,
|
||||||
|
VulvanizeLogId: vulcanizeLogId,
|
||||||
|
TokenName: "TrueUSD",
|
||||||
|
Block: 5488076,
|
||||||
|
Tx: "0x135391a0962a63944e5908e6fedfff90fb4be3e3290a21017861099bad6546ae",
|
||||||
|
From: "0x000000000000000000000000000000000000Af21",
|
||||||
|
To: "0x09BbBBE21a5975cAc061D82f7b843bCE061BA391",
|
||||||
|
Value: "1097077688018008265106216665536940668749033598146",
|
||||||
|
}
|
||||||
|
Expect(scanLog).To(Equal(expectedLog))
|
||||||
|
|
||||||
|
// Attempt to persist the same log again in seperate call
|
||||||
|
err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Show that no new logs were entered
|
||||||
|
var count int
|
||||||
|
err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM full_%s.transfer_event", constants.TusdContractAddress))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(count).To(Equal(1))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails with empty log", func() {
|
||||||
|
err = dataStore.PersistLogs([]types.Log{}, event, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("Light sync mode", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
dataStore = sr.NewEventRepository(db, types.LightSync)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateContractSchema", func() {
|
||||||
|
It("Creates schema if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
_, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
tableID := fmt.Sprintf("%s_%s.%s_event", types.LightSync, strings.ToLower(con.Address), strings.ToLower(event.Name))
|
||||||
|
_, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateEventTable", func() {
|
||||||
|
It("Creates table if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateEventTable(con.Address, event)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("PersistLogs", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
headerRepository := repositories.NewHeaderRepository(db)
|
||||||
|
headerID, err = headerRepository.CreateOrUpdateHeader(mocks.MockHeader1)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
c := lc.NewConverter(con)
|
||||||
|
logs, err = c.Convert([]geth.Log{mockLog1, mockLog2}, event, headerID)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Persists contract event log values into custom tables", func() {
|
||||||
|
hr := lr.NewHeaderRepository(db)
|
||||||
|
err = hr.AddCheckColumn(event.Name + "_" + con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = dataStore.PersistLogs(logs, event, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
var count int
|
||||||
|
err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM light_%s.transfer_event", constants.TusdContractAddress))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(count).To(Equal(2))
|
||||||
|
|
||||||
|
scanLog := test_helpers.LightTransferLog{}
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.transfer_event LIMIT 1", constants.TusdContractAddress)).StructScan(&scanLog)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(scanLog.HeaderID).To(Equal(headerID))
|
||||||
|
Expect(scanLog.TokenName).To(Equal("TrueUSD"))
|
||||||
|
Expect(scanLog.TxIndex).To(Equal(int64(110)))
|
||||||
|
Expect(scanLog.LogIndex).To(Equal(int64(1)))
|
||||||
|
Expect(scanLog.From).To(Equal("0x000000000000000000000000000000000000Af21"))
|
||||||
|
Expect(scanLog.To).To(Equal("0x09BbBBE21a5975cAc061D82f7b843bCE061BA391"))
|
||||||
|
Expect(scanLog.Value).To(Equal("1097077688018008265106216665536940668749033598146"))
|
||||||
|
|
||||||
|
var expectedRawLog, rawLog geth.Log
|
||||||
|
err = json.Unmarshal(logs[0].Raw, &expectedRawLog)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
err = json.Unmarshal(scanLog.RawLog, &rawLog)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(rawLog).To(Equal(expectedRawLog))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Doesn't persist duplicate event logs", func() {
|
||||||
|
hr := lr.NewHeaderRepository(db)
|
||||||
|
err = hr.AddCheckColumn(event.Name + "_" + con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Try and fail to persist the same log twice in a single call
|
||||||
|
err = dataStore.PersistLogs([]types.Log{logs[0], logs[0]}, event, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
// Successfuly persist the two unique logs
|
||||||
|
err = dataStore.PersistLogs(logs, event, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
// Try and fail to persist the same logs again in separate call
|
||||||
|
err = dataStore.PersistLogs([]types.Log{*log}, event, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
|
||||||
|
// Show that no new logs were entered
|
||||||
|
var count int
|
||||||
|
err = db.Get(&count, fmt.Sprintf("SELECT COUNT(*) FROM light_%s.transfer_event", constants.TusdContractAddress))
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(count).To(Equal(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails if the persisted event does not have a corresponding eventID column in the checked_headers table", func() {
|
||||||
|
err = dataStore.PersistLogs(logs, event, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails with empty log", func() {
|
||||||
|
err = dataStore.PersistLogs([]types.Log{}, event, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -21,28 +21,41 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"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/shared/types"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const methodCacheSize = 1000
|
||||||
|
|
||||||
type MethodRepository interface {
|
type MethodRepository interface {
|
||||||
PersistResult(method types.Result, contractAddr, contractName string) error
|
PersistResult(method types.Result, contractAddr, contractName string) error
|
||||||
CreateMethodTable(contractAddr string, method types.Result) (bool, error)
|
CreateMethodTable(contractAddr string, method types.Result) (bool, error)
|
||||||
CreateContractSchema(contractAddr string) (bool, error)
|
CreateContractSchema(contractAddr string) (bool, error)
|
||||||
|
CheckSchemaCache(key string) (interface{}, bool)
|
||||||
|
CheckTableCache(key string) (interface{}, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
type methodRepository struct {
|
type methodRepository struct {
|
||||||
*postgres.DB
|
*postgres.DB
|
||||||
|
mode types.Mode
|
||||||
|
schemas *lru.Cache // Cache names of recently used schemas to minimize db connections
|
||||||
|
tables *lru.Cache // Cache names of recently used tables to minimize db connections
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMethodRepository(db *postgres.DB) *methodRepository {
|
func NewMethodRepository(db *postgres.DB, mode types.Mode) *methodRepository {
|
||||||
|
ccs, _ := lru.New(contractCacheSize)
|
||||||
|
mcs, _ := lru.New(methodCacheSize)
|
||||||
return &methodRepository{
|
return &methodRepository{
|
||||||
DB: db,
|
DB: db,
|
||||||
|
mode: mode,
|
||||||
|
schemas: ccs,
|
||||||
|
tables: mcs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *methodRepository) PersistResult(method types.Result, contractAddr, contractName string) error {
|
func (r *methodRepository) PersistResult(method types.Result, contractAddr, contractName string) error {
|
||||||
if len(method.Args) != len(method.Inputs) {
|
if len(method.Args) != len(method.Inputs) {
|
||||||
return errors.New("error: given number of inputs does not match number of method arguments")
|
return errors.New("error: given number of inputs does not match number of method arguments")
|
||||||
}
|
}
|
||||||
@ -50,51 +63,48 @@ func (d *methodRepository) PersistResult(method types.Result, contractAddr, cont
|
|||||||
return errors.New("error: given number of outputs does not match number of method return values")
|
return errors.New("error: given number of outputs does not match number of method return values")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := d.CreateContractSchema(contractAddr)
|
_, err := r.CreateContractSchema(contractAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = d.CreateMethodTable(contractAddr, method)
|
_, err = r.CreateMethodTable(contractAddr, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return d.persistResult(method, contractAddr, contractName)
|
return r.persistResult(method, 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 (d *methodRepository) persistResult(method types.Result, contractAddr, contractName string) error {
|
func (r *methodRepository) persistResult(method types.Result, contractAddr, contractName string) error {
|
||||||
// Begin postgres string
|
// Begin postgres string
|
||||||
pgStr := fmt.Sprintf("INSERT INTO c%s.%s_method ", strings.ToLower(contractAddr), strings.ToLower(method.Name))
|
pgStr := fmt.Sprintf("INSERT INTO %s_%s.%s_method ", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(method.Name))
|
||||||
pgStr = pgStr + "(token_name, block"
|
pgStr = pgStr + "(token_name, block"
|
||||||
|
ml := len(method.Args)
|
||||||
|
|
||||||
// Pack the corresponding variables in a slice
|
// Preallocate slice of needed size and proceed to pack variables into it in same order they appear in string
|
||||||
var data []interface{}
|
data := make([]interface{}, 0, 3+ml)
|
||||||
data = append(data,
|
data = append(data,
|
||||||
contractName,
|
contractName,
|
||||||
method.Block)
|
method.Block)
|
||||||
|
|
||||||
// Iterate over method args and return value, adding names
|
// Iterate over method args and return value, adding names
|
||||||
// to the string and pushing values to the slice
|
// to the string and pushing values to the slice
|
||||||
counter := 0 // Keep track of number of inputs
|
|
||||||
for i, arg := range method.Args {
|
for i, arg := range method.Args {
|
||||||
counter += 1
|
|
||||||
pgStr = pgStr + fmt.Sprintf(", %s_", strings.ToLower(arg.Name)) // Add underscore after to avoid any collisions with reserved pg words
|
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])
|
data = append(data, method.Inputs[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
counter += 1
|
|
||||||
pgStr = pgStr + ", returned) VALUES ($1, $2"
|
pgStr = pgStr + ", returned) VALUES ($1, $2"
|
||||||
data = append(data, method.Output)
|
data = append(data, method.Output)
|
||||||
|
|
||||||
// For each input entry we created we add its postgres command variable to the string
|
// For each input entry we created we add its postgres command variable to the string
|
||||||
for i := 0; i < counter; i++ {
|
for i := 0; i <= ml; i++ {
|
||||||
pgStr = pgStr + fmt.Sprintf(", $%d", i+3)
|
pgStr = pgStr + fmt.Sprintf(", $%d", i+3)
|
||||||
}
|
}
|
||||||
pgStr = pgStr + ")"
|
pgStr = pgStr + ")"
|
||||||
|
|
||||||
_, err := d.DB.Exec(pgStr, data...)
|
_, err := r.DB.Exec(pgStr, data...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -103,26 +113,35 @@ func (d *methodRepository) persistResult(method types.Result, contractAddr, cont
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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 (d *methodRepository) CreateMethodTable(contractAddr string, method types.Result) (bool, error) {
|
func (r *methodRepository) CreateMethodTable(contractAddr string, method types.Result) (bool, error) {
|
||||||
tableExists, err := d.checkForTable(contractAddr, 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
|
||||||
|
_, ok := r.tables.Get(tableID)
|
||||||
|
if ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
tableExists, err := r.checkForTable(contractAddr, method.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !tableExists {
|
if !tableExists {
|
||||||
err = d.newMethodTable(contractAddr, method)
|
err = r.newMethodTable(tableID, method)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add schema name to cache
|
||||||
|
r.tables.Add(tableID, true)
|
||||||
|
|
||||||
return !tableExists, nil
|
return !tableExists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a table for the given contract and event
|
// Creates a table for the given contract and event
|
||||||
func (d *methodRepository) newMethodTable(contractAddr string, method types.Result) error {
|
func (r *methodRepository) newMethodTable(tableID string, method types.Result) error {
|
||||||
// Begin pg string
|
// Begin pg string
|
||||||
pgStr := fmt.Sprintf("CREATE TABLE IF NOT EXISTS c%s.%s_method ", strings.ToLower(contractAddr), strings.ToLower(method.Name))
|
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,"
|
||||||
|
|
||||||
// Iterate over method inputs and outputs, using their name and pgType to grow the string
|
// Iterate over method inputs and outputs, using their name and pgType to grow the string
|
||||||
@ -132,54 +151,69 @@ func (d *methodRepository) newMethodTable(contractAddr string, method types.Resu
|
|||||||
|
|
||||||
pgStr = pgStr + fmt.Sprintf(" returned %s NOT NULL)", method.Return[0].PgType)
|
pgStr = pgStr + fmt.Sprintf(" returned %s NOT NULL)", method.Return[0].PgType)
|
||||||
|
|
||||||
_, err := d.DB.Exec(pgStr)
|
_, err := r.DB.Exec(pgStr)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a table already exists for the given contract and event
|
// Checks if a table already exists for the given contract and event
|
||||||
func (d *methodRepository) checkForTable(contractAddr string, methodName string) (bool, error) {
|
func (r *methodRepository) checkForTable(contractAddr string, methodName string) (bool, error) {
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = 'c%s' AND table_name = '%s_method')", strings.ToLower(contractAddr), strings.ToLower(methodName))
|
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = '%s_%s' AND table_name = '%s_method')", r.mode.String(), strings.ToLower(contractAddr), strings.ToLower(methodName))
|
||||||
var exists bool
|
var exists bool
|
||||||
err := d.DB.Get(&exists, pgStr)
|
err := r.DB.Get(&exists, pgStr)
|
||||||
|
|
||||||
return exists, err
|
return exists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks for contract schema and creates it if it does not already exist
|
// Checks for contract schema and creates it if it does not already exist
|
||||||
func (d *methodRepository) CreateContractSchema(contractAddr string) (bool, error) {
|
func (r *methodRepository) CreateContractSchema(contractAddr string) (bool, error) {
|
||||||
if contractAddr == "" {
|
if contractAddr == "" {
|
||||||
return false, errors.New("error: no contract address specified")
|
return false, errors.New("error: no contract address specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
schemaExists, err := d.checkForSchema(contractAddr)
|
// Check cache before querying pq to see if schema exists
|
||||||
|
_, ok := r.schemas.Get(contractAddr)
|
||||||
|
if ok {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
schemaExists, err := r.checkForSchema(contractAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !schemaExists {
|
if !schemaExists {
|
||||||
err = d.newContractSchema(contractAddr)
|
err = r.newContractSchema(contractAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add schema name to cache
|
||||||
|
r.schemas.Add(contractAddr, true)
|
||||||
|
|
||||||
return !schemaExists, nil
|
return !schemaExists, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a schema for the given contract
|
// Creates a schema for the given contract
|
||||||
func (d *methodRepository) newContractSchema(contractAddr string) error {
|
func (r *methodRepository) newContractSchema(contractAddr string) error {
|
||||||
_, err := d.DB.Exec("CREATE SCHEMA IF NOT EXISTS c" + strings.ToLower(contractAddr))
|
_, err := r.DB.Exec("CREATE SCHEMA IF NOT EXISTS " + r.mode.String() + "_" + strings.ToLower(contractAddr))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a schema already exists for the given contract
|
// Checks if a schema already exists for the given contract
|
||||||
func (d *methodRepository) checkForSchema(contractAddr string) (bool, error) {
|
func (r *methodRepository) checkForSchema(contractAddr string) (bool, error) {
|
||||||
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'c%s')", strings.ToLower(contractAddr))
|
pgStr := fmt.Sprintf("SELECT EXISTS (SELECT schema_name FROM information_schema.schemata WHERE schema_name = '%s_%s')", r.mode.String(), strings.ToLower(contractAddr))
|
||||||
|
|
||||||
var exists bool
|
var exists bool
|
||||||
err := d.DB.Get(&exists, pgStr)
|
err := r.DB.Get(&exists, pgStr)
|
||||||
|
|
||||||
return exists, err
|
return exists, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *methodRepository) CheckSchemaCache(key string) (interface{}, bool) {
|
||||||
|
return r.schemas.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *methodRepository) CheckTableCache(key string) (interface{}, bool) {
|
||||||
|
return r.tables.Get(key)
|
||||||
|
}
|
||||||
|
@ -18,6 +18,7 @@ package repository_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
@ -36,10 +37,11 @@ var _ = Describe("Repository", func() {
|
|||||||
var con *contract.Contract
|
var con *contract.Contract
|
||||||
var err error
|
var err error
|
||||||
var mockResult types.Result
|
var mockResult types.Result
|
||||||
|
var method types.Method
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
con = test_helpers.SetupTusdContract([]string{}, []string{"balanceOf"})
|
con = test_helpers.SetupTusdContract([]string{}, []string{"balanceOf"})
|
||||||
method := con.Methods["balanceOf"]
|
method = con.Methods["balanceOf"]
|
||||||
mockResult = types.Result{
|
mockResult = types.Result{
|
||||||
Method: method,
|
Method: method,
|
||||||
PgType: method.Return[0].PgType,
|
PgType: method.Return[0].PgType,
|
||||||
@ -50,62 +52,188 @@ var _ = Describe("Repository", func() {
|
|||||||
mockResult.Inputs[0] = "0xfE9e8709d3215310075d67E3ed32A380CCf451C8"
|
mockResult.Inputs[0] = "0xfE9e8709d3215310075d67E3ed32A380CCf451C8"
|
||||||
mockResult.Output = "66386309548896882859581786"
|
mockResult.Output = "66386309548896882859581786"
|
||||||
db, _ = test_helpers.SetupDBandBC()
|
db, _ = test_helpers.SetupDBandBC()
|
||||||
dataStore = repository.NewMethodRepository(db)
|
dataStore = repository.NewMethodRepository(db, types.FullSync)
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
test_helpers.TearDown(db)
|
test_helpers.TearDown(db)
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("CreateContractSchema", func() {
|
Describe("Full Sync Mode", func() {
|
||||||
It("Creates schema if it doesn't exist", func() {
|
BeforeEach(func() {
|
||||||
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
dataStore = repository.NewMethodRepository(db, types.FullSync)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
})
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateContractSchema(constants.TusdContractAddress)
|
Describe("CreateContractSchema", func() {
|
||||||
Expect(err).ToNot(HaveOccurred())
|
It("Creates schema if it doesn't exist", func() {
|
||||||
Expect(created).To(Equal(false))
|
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
_, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateMethodTable", func() {
|
||||||
|
It("Creates table if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
tableID := fmt.Sprintf("%s_%s.%s_method", types.FullSync, strings.ToLower(con.Address), strings.ToLower(method.Name))
|
||||||
|
_, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(con.Address, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("PersistResult", func() {
|
||||||
|
It("Persists result from method polling in custom pg table", func() {
|
||||||
|
err = dataStore.PersistResult(mockResult, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
scanStruct := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM full_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
expectedLog := test_helpers.BalanceOf{
|
||||||
|
Id: 1,
|
||||||
|
TokenName: "TrueUSD",
|
||||||
|
Block: 6707323,
|
||||||
|
Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8",
|
||||||
|
Balance: "66386309548896882859581786",
|
||||||
|
}
|
||||||
|
Expect(scanStruct).To(Equal(expectedLog))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails with empty result", func() {
|
||||||
|
err = dataStore.PersistResult(types.Result{}, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("CreateMethodTable", func() {
|
Describe("Light Sync Mode", func() {
|
||||||
It("Creates table if it doesn't exist", func() {
|
BeforeEach(func() {
|
||||||
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
dataStore = repository.NewMethodRepository(db, types.LightSync)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(true))
|
|
||||||
|
|
||||||
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
Expect(created).To(Equal(false))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("PersistResult", func() {
|
|
||||||
It("Persists result from method polling in custom pg table", func() {
|
|
||||||
err = dataStore.PersistResult(mockResult, con.Address, con.Name)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
|
|
||||||
scanStruct := test_helpers.BalanceOf{}
|
|
||||||
|
|
||||||
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM c%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct)
|
|
||||||
expectedLog := test_helpers.BalanceOf{
|
|
||||||
Id: 1,
|
|
||||||
TokenName: "TrueUSD",
|
|
||||||
Block: 6707323,
|
|
||||||
Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8",
|
|
||||||
Balance: "66386309548896882859581786",
|
|
||||||
}
|
|
||||||
Expect(scanStruct).To(Equal(expectedLog))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("Fails with empty result", func() {
|
Describe("CreateContractSchema", func() {
|
||||||
err = dataStore.PersistResult(types.Result{}, con.Address, con.Name)
|
It("Creates schema if it doesn't exist", func() {
|
||||||
Expect(err).To(HaveOccurred())
|
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches schema it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
_, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckSchemaCache(con.Address)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("CreateMethodTable", func() {
|
||||||
|
It("Creates table if it doesn't exist", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(constants.TusdContractAddress)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(constants.TusdContractAddress, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(false))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Caches table it creates so that it does not need to repeatedly query the database to check for it's existence", func() {
|
||||||
|
created, err := dataStore.CreateContractSchema(con.Address)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
tableID := fmt.Sprintf("%s_%s.%s_method", types.LightSync, strings.ToLower(con.Address), strings.ToLower(method.Name))
|
||||||
|
_, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(false))
|
||||||
|
|
||||||
|
created, err = dataStore.CreateMethodTable(con.Address, mockResult)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
Expect(created).To(Equal(true))
|
||||||
|
|
||||||
|
v, ok := dataStore.CheckTableCache(tableID)
|
||||||
|
Expect(ok).To(Equal(true))
|
||||||
|
Expect(v).To(Equal(true))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("PersistResult", func() {
|
||||||
|
It("Persists result from method polling in custom pg table for light sync mode vDB", func() {
|
||||||
|
err = dataStore.PersistResult(mockResult, con.Address, con.Name)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
scanStruct := test_helpers.BalanceOf{}
|
||||||
|
|
||||||
|
err = db.QueryRowx(fmt.Sprintf("SELECT * FROM light_%s.balanceof_method", constants.TusdContractAddress)).StructScan(&scanStruct)
|
||||||
|
expectedLog := test_helpers.BalanceOf{
|
||||||
|
Id: 1,
|
||||||
|
TokenName: "TrueUSD",
|
||||||
|
Block: 6707323,
|
||||||
|
Address: "0xfE9e8709d3215310075d67E3ed32A380CCf451C8",
|
||||||
|
Balance: "66386309548896882859581786",
|
||||||
|
}
|
||||||
|
Expect(scanStruct).To(Equal(expectedLog))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Fails with empty result", func() {
|
||||||
|
err = dataStore.PersistResult(types.Result{}, con.Address, con.Name)
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,7 @@ package retriever
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
@ -28,19 +29,19 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Address retriever is used to retrieve the addresses associated with a contract
|
// Address retriever is used to retrieve the addresses associated with a contract
|
||||||
// It requires a vDB synced database with blocks, transactions, receipts, logs,
|
|
||||||
// AND all of the targeted events persisted
|
|
||||||
type AddressRetriever interface {
|
type AddressRetriever interface {
|
||||||
RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error)
|
RetrieveTokenHolderAddresses(info contract.Contract) (map[common.Address]bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type addressRetriever struct {
|
type addressRetriever struct {
|
||||||
db *postgres.DB
|
db *postgres.DB
|
||||||
|
mode types.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddressRetriever(db *postgres.DB) (r *addressRetriever) {
|
func NewAddressRetriever(db *postgres.DB, mode types.Mode) (r *addressRetriever) {
|
||||||
return &addressRetriever{
|
return &addressRetriever{
|
||||||
db: db,
|
db: db,
|
||||||
|
mode: mode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +85,7 @@ func (r *addressRetriever) retrieveTransferAddresses(con contract.Contract) ([]s
|
|||||||
|
|
||||||
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
|
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
pgStr := fmt.Sprintf("SELECT %s_ FROM c%s.%s_event", strings.ToLower(field.Name), strings.ToLower(con.Address), strings.ToLower(event.Name))
|
pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name))
|
||||||
err := r.db.Select(&addrs, pgStr)
|
err := r.db.Select(&addrs, pgStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
return []string{}, err
|
||||||
@ -105,7 +106,7 @@ func (r *addressRetriever) retrieveTokenMintees(con contract.Contract) ([]string
|
|||||||
|
|
||||||
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
|
if field.Type.T == abi.AddressTy { // If they have address type, retrieve those addresses
|
||||||
addrs := make([]string, 0)
|
addrs := make([]string, 0)
|
||||||
pgStr := fmt.Sprintf("SELECT %s_ FROM c%s.%s_event", strings.ToLower(field.Name), strings.ToLower(con.Address), strings.ToLower(event.Name))
|
pgStr := fmt.Sprintf("SELECT %s_ FROM %s_%s.%s_event", strings.ToLower(field.Name), r.mode.String(), strings.ToLower(con.Address), strings.ToLower(event.Name))
|
||||||
err := r.db.Select(&addrs, pgStr)
|
err := r.db.Select(&addrs, pgStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{}, err
|
return []string{}, err
|
@ -20,16 +20,16 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/full/converter"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/repository"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/full/retriever"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/constants"
|
||||||
"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/repository"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/retriever"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/omni/shared/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var mockEvent = core.WatchedEvent{
|
var mockEvent = core.WatchedEvent{
|
||||||
@ -68,11 +68,11 @@ var _ = Describe("Address Retriever Test", func() {
|
|||||||
log, err = c.Convert(mockEvent, event)
|
log, err = c.Convert(mockEvent, event)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
dataStore = repository.NewEventRepository(db)
|
dataStore = repository.NewEventRepository(db, types.FullSync)
|
||||||
dataStore.PersistLog(*log, info.Address, info.Name)
|
dataStore.PersistLogs([]types.Log{*log}, event, info.Address, info.Name)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
r = retriever.NewAddressRetriever(db)
|
r = retriever.NewAddressRetriever(db, types.FullSync)
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
@ -14,7 +14,7 @@
|
|||||||
// You should have received a copy of the GNU Affero General Public License
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
package repository_test
|
package retriever_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
@ -25,9 +25,9 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRepository(t *testing.T) {
|
func TestRetriever(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "Full Repository Suite Test")
|
RunSpecs(t, "Address Retriever Suite Test")
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ = BeforeSuite(func() {
|
var _ = BeforeSuite(func() {
|
@ -21,7 +21,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
"github.com/i-norden/go-ethereum/core/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Event struct {
|
type Event struct {
|
||||||
@ -37,7 +36,6 @@ type Field struct {
|
|||||||
|
|
||||||
// Struct to hold instance of an event log data
|
// Struct to hold instance of an event log data
|
||||||
type Log struct {
|
type Log struct {
|
||||||
Event
|
|
||||||
Id int64 // VulcanizeIdLog for full sync and header ID for light sync omni watcher
|
Id int64 // VulcanizeIdLog for full sync and header ID for light sync omni watcher
|
||||||
Values map[string]string // Map of event input names to their values
|
Values map[string]string // Map of event input names to their values
|
||||||
|
|
||||||
@ -48,7 +46,7 @@ type Log struct {
|
|||||||
// Used for lightSync only
|
// Used for lightSync only
|
||||||
LogIndex uint
|
LogIndex uint
|
||||||
TransactionIndex uint
|
TransactionIndex uint
|
||||||
Raw types.Log
|
Raw []byte // json.Unmarshalled byte array of geth/core/types.Log{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unpack abi.Event into our custom Event struct
|
// Unpack abi.Event into our custom Event struct
|
||||||
|
64
pkg/omni/shared/types/mode.go
Normal file
64
pkg/omni/shared/types/mode.go
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// VulcanizeDB
|
||||||
|
// Copyright © 2018 Vulcanize
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Affero General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU Affero General Public License
|
||||||
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Mode int
|
||||||
|
|
||||||
|
const (
|
||||||
|
LightSync Mode = iota
|
||||||
|
FullSync
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mode Mode) IsValid() bool {
|
||||||
|
return mode >= LightSync && mode <= FullSync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mode Mode) String() string {
|
||||||
|
switch mode {
|
||||||
|
case LightSync:
|
||||||
|
return "light"
|
||||||
|
case FullSync:
|
||||||
|
return "full"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mode Mode) MarshalText() ([]byte, error) {
|
||||||
|
switch mode {
|
||||||
|
case LightSync:
|
||||||
|
return []byte("light"), nil
|
||||||
|
case FullSync:
|
||||||
|
return []byte("full"), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("omni watcher: unknown mode %d, want LightSync or FullSync", mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mode *Mode) UnmarshalText(text []byte) error {
|
||||||
|
switch string(text) {
|
||||||
|
case "light":
|
||||||
|
*mode = LightSync
|
||||||
|
case "full":
|
||||||
|
*mode = FullSync
|
||||||
|
default:
|
||||||
|
return fmt.Errorf(`omni watcher: unknown mode %q, want "light" or "full"`, text)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user