Get logs for a contract (#99)

* Add ability to fetch logs for a contract and a block

* Test contract related code against Infura, so can run on Travis

* Add godo task for getLogs
This commit is contained in:
Matt K
2017-12-11 15:08:00 -06:00
committed by GitHub
parent 921bde1089
commit 5e64283a12
16 changed files with 420 additions and 119 deletions
+26 -5
View File
@@ -12,6 +12,8 @@ import (
"errors"
"net/url"
"github.com/BurntSushi/toml"
)
@@ -24,6 +26,10 @@ var NewErrConfigFileNotFound = func(environment string) error {
return errors.New(fmt.Sprintf("No configuration found for environment: %v", environment))
}
var NewErrBadConnectionString = func(connectionString string) error {
return errors.New(fmt.Sprintf("connection string is invalid: %v", connectionString))
}
func NewConfig(environment string) (Config, error) {
filenameWithExtension := fmt.Sprintf("%s.toml", environment)
absolutePath := filepath.Join(ProjectRoot(), "environments", filenameWithExtension)
@@ -31,7 +37,7 @@ func NewConfig(environment string) (Config, error) {
if err != nil {
return Config{}, NewErrConfigFileNotFound(environment)
} else {
if !filepath.IsAbs(config.Client.IPCPath) {
if !filepath.IsAbs(config.Client.IPCPath) && !isUrl(config.Client.IPCPath) {
config.Client.IPCPath = filepath.Join(ProjectRoot(), config.Client.IPCPath)
}
return config, nil
@@ -43,16 +49,31 @@ func ProjectRoot() string {
return path.Join(path.Dir(filename), "..", "..")
}
func isUrl(s string) bool {
_, err := url.ParseRequestURI(s)
if err == nil {
return true
}
return false
}
func fileExists(s string) bool {
_, err := os.Stat(s)
if err == nil {
return true
}
return false
}
func parseConfigFile(filePath string) (Config, error) {
var cfg Config
_, err := os.Stat(filePath)
if err != nil {
return Config{}, err
if !isUrl(filePath) && !fileExists(filePath) {
return Config{}, NewErrBadConnectionString(filePath)
} else {
_, err := toml.DecodeFile(filePath, &cfg)
if err != nil {
return Config{}, err
}
return cfg, err
return cfg, nil
}
}
+10
View File
@@ -28,4 +28,14 @@ var _ = Describe("Loading the config", func() {
Expect(err).NotTo(BeNil())
})
It("reads the infura config using the environment", func() {
infuraConfig, err := cfg.NewConfig("infura")
Expect(err).To(BeNil())
Expect(infuraConfig.Database.Hostname).To(Equal("localhost"))
Expect(infuraConfig.Database.Name).To(Equal("vulcanize_private"))
Expect(infuraConfig.Database.Port).To(Equal(5432))
Expect(infuraConfig.Client.IPCPath).To(Equal("https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL"))
})
})
+1
View File
@@ -10,4 +10,5 @@ type Blockchain interface {
StopListening()
GetAttributes(contract Contract) (ContractAttributes, error)
GetAttribute(contract Contract, attributeName string, blockNumber *big.Int) (interface{}, error)
GetLogs(contract Contract, blockNumber *big.Int) ([]Log, error)
}
+9
View File
@@ -0,0 +1,9 @@
package core
type Log struct {
BlockNumber int64
TxHash string
Address string
Topics []string
Data string
}
+6
View File
@@ -9,6 +9,7 @@ import (
)
type Blockchain struct {
logs map[string][]core.Log
blocks map[int64]core.Block
contractAttributes map[string]map[string]string
blocksChannel chan core.Block
@@ -16,6 +17,10 @@ type Blockchain struct {
node core.Node
}
func (blockchain *Blockchain) GetLogs(contract core.Contract, blockNumber *big.Int) ([]core.Log, error) {
return blockchain.logs[contract.Hash], nil
}
func (blockchain *Blockchain) Node() core.Node {
return blockchain.node
}
@@ -33,6 +38,7 @@ func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName
func NewBlockchain() *Blockchain {
return &Blockchain{
blocks: make(map[int64]core.Block),
logs: make(map[string][]core.Log),
contractAttributes: make(map[string]map[string]string),
node: core.Node{GenesisBlock: "GENESIS"},
}
-101
View File
@@ -1,101 +0,0 @@
package geth_test
//import (
// "math/big"
//
// cfg "github.com/8thlight/vulcanizedb/pkg/config"
// "github.com/8thlight/vulcanizedb/pkg/geth"
// "github.com/8thlight/vulcanizedb/pkg/geth/testing"
// . "github.com/onsi/ginkgo"
// . "github.com/onsi/gomega"
//)
//
//var _ = Describe("Reading contracts", func() {
//
// Describe("Reading the list of attributes", func() {
// It("returns a string attribute for a real contract", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// contractAttributes, err := blockchain.GetAttributes(contract)
//
// Expect(err).To(BeNil())
// Expect(len(contractAttributes)).NotTo(Equal(0))
// symbolAttribute := *testing.FindAttribute(contractAttributes, "symbol")
// Expect(symbolAttribute.Name).To(Equal("symbol"))
// Expect(symbolAttribute.Type).To(Equal("string"))
// })
//
// It("does not return an attribute that takes an input", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// contractAttributes, err := blockchain.GetAttributes(contract)
//
// Expect(err).To(BeNil())
// attribute := testing.FindAttribute(contractAttributes, "balanceOf")
// Expect(attribute).To(BeNil())
// })
//
// It("does not return an attribute that is not constant", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// contractAttributes, err := blockchain.GetAttributes(contract)
//
// Expect(err).To(BeNil())
// attribute := testing.FindAttribute(contractAttributes, "unpause")
// Expect(attribute).To(BeNil())
// })
// })
//
// Describe("Getting a contract attribute", func() {
// It("returns the correct attribute for a real contract", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
//
// contract := testing.SampleContract()
// name, err := blockchain.GetAttribute(contract, "name", nil)
//
// Expect(err).To(BeNil())
// Expect(name).To(Equal("OMGToken"))
// })
//
// It("returns the correct attribute for a real contract", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// name, err := blockchain.GetAttribute(contract, "name", nil)
//
// Expect(err).To(BeNil())
// Expect(name).To(Equal("OMGToken"))
// })
//
// It("returns the correct attribute for a real contract at a specific block height", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// name, err := blockchain.GetAttribute(contract, "name", big.NewInt(4652791))
//
// Expect(name).To(Equal("OMGToken"))
// Expect(err).To(BeNil())
// })
//
// It("returns an error when asking for an attribute that does not exist", func() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contract := testing.SampleContract()
//
// name, err := blockchain.GetAttribute(contract, "missing_attribute", nil)
//
// Expect(err).To(Equal(geth.ErrInvalidStateAttribute))
// Expect(name).To(BeNil())
// })
// })
//
//})
+21
View File
@@ -8,6 +8,7 @@ import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/geth/node"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/rpc"
@@ -22,6 +23,21 @@ type GethBlockchain struct {
node core.Node
}
func (blockchain *GethBlockchain) GetLogs(contract core.Contract, blockNumber *big.Int) ([]core.Log, error) {
contractAddress := common.HexToAddress(contract.Hash)
fc := ethereum.FilterQuery{
FromBlock: blockNumber,
ToBlock: blockNumber,
Addresses: []common.Address{contractAddress},
}
gethLogs, err := blockchain.client.FilterLogs(context.Background(), fc)
if err != nil {
return []core.Log{}, err
}
logs := GethLogsToCoreLogs(gethLogs)
return logs, nil
}
func (blockchain *GethBlockchain) Node() core.Node {
return blockchain.node
}
@@ -60,3 +76,8 @@ func (blockchain *GethBlockchain) StartListening() {
func (blockchain *GethBlockchain) StopListening() {
blockchain.newHeadSubscription.Unsubscribe()
}
func (blockchain *GethBlockchain) latestBlock() *big.Int {
block, _ := blockchain.client.HeaderByNumber(context.Background(), nil)
return block.Number
}
+32
View File
@@ -0,0 +1,32 @@
package geth
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
func GethLogToCoreLog(gethLog types.Log) core.Log {
topics := gethLog.Topics
var hexTopics []string
for _, topic := range topics {
hexTopics = append(hexTopics, topic.Hex())
}
return core.Log{
Address: gethLog.Address.Hex(),
BlockNumber: int64(gethLog.BlockNumber),
Topics: hexTopics,
TxHash: gethLog.TxHash.Hex(),
Data: hexutil.Encode(gethLog.Data),
}
}
func GethLogsToCoreLogs(gethLogs []types.Log) []core.Log {
var logs []core.Log
for _, log := range gethLogs {
log := GethLogToCoreLog(log)
logs = append(logs, log)
}
return logs
}
+91
View File
@@ -0,0 +1,91 @@
package geth_test
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/geth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Conversion of GethLog to core.Log", func() {
It("converts geth log to internal log format", func() {
gethLog := types.Log{
Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
Index: 2,
TxIndex: 3,
TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
Topics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
},
}
expected := core.Log{
Address: gethLog.Address.Hex(),
BlockNumber: int64(gethLog.BlockNumber),
Data: hexutil.Encode(gethLog.Data),
TxHash: gethLog.TxHash.Hex(),
Topics: []string{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef").Hex(),
common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615").Hex(),
},
}
coreLog := geth.GethLogToCoreLog(gethLog)
Expect(coreLog.Address).To(Equal(expected.Address))
Expect(coreLog.BlockNumber).To(Equal(expected.BlockNumber))
Expect(coreLog.Data).To(Equal(expected.Data))
Expect(coreLog.Topics[0]).To(Equal(expected.Topics[0]))
Expect(coreLog.Topics[1]).To(Equal(expected.Topics[1]))
Expect(coreLog.TxHash).To(Equal(expected.TxHash))
})
It("converts geth log array to array of internal logs", func() {
gethLogOne := types.Log{
Address: common.HexToAddress("0xecf8f87f810ecf450940c9f60066b4a7a501d6a7"),
BlockHash: common.HexToHash("0x656c34545f90a730a19008c0e7a7cd4fb3895064b48d6d69761bd5abad681056"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x000000000000000000000000000000000000000000000001a055690d9db80000"),
Index: 2,
TxIndex: 3,
TxHash: common.HexToHash("0x3b198bfd5d2907285af009e9ae84a0ecd63677110d89d7e030251acb87f6487e"),
Topics: []common.Hash{
common.HexToHash("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"),
common.HexToHash("0x00000000000000000000000080b2c9d7cbbf30a1b0fc8983c647d754c6525615"),
},
}
gethLogTwo := types.Log{
Address: common.HexToAddress("0x123"),
BlockHash: common.HexToHash("0x576"),
BlockNumber: 2019236,
Data: hexutil.MustDecode("0x0000000000000000000000000000000000000000000000000000000000000001"),
Index: 3,
TxIndex: 4,
TxHash: common.HexToHash("0x134"),
Topics: []common.Hash{
common.HexToHash("0xaaa"),
common.HexToHash("0xbbb"),
},
}
expectedOne := geth.GethLogToCoreLog(gethLogOne)
expectedTwo := geth.GethLogToCoreLog(gethLogTwo)
coreLogs := geth.GethLogsToCoreLogs([]types.Log{gethLogOne, gethLogTwo})
Expect(len(coreLogs)).To(Equal(2))
Expect(coreLogs[0]).To(Equal(expectedOne))
Expect(coreLogs[1]).To(Equal(expectedTwo))
})
})