forked from cerc-io/ipld-eth-server
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:
+26
-5
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
package core
|
||||
|
||||
type Log struct {
|
||||
BlockNumber int64
|
||||
TxHash string
|
||||
Address string
|
||||
Topics []string
|
||||
Data string
|
||||
}
|
||||
@@ -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"},
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
// })
|
||||
// })
|
||||
//
|
||||
//})
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
Reference in New Issue
Block a user