Contract hist (#84)

Add ability to query contract historical state
This commit is contained in:
Matt K 2017-12-04 12:54:33 -06:00 committed by ericmeyer
parent 7501fe70a7
commit 71de8e970d
8 changed files with 143 additions and 44 deletions

View File

@ -7,6 +7,8 @@ import (
"fmt" "fmt"
"math/big"
"github.com/8thlight/vulcanizedb/cmd" "github.com/8thlight/vulcanizedb/cmd"
"github.com/8thlight/vulcanizedb/pkg/geth" "github.com/8thlight/vulcanizedb/pkg/geth"
"github.com/8thlight/vulcanizedb/pkg/watched_contracts" "github.com/8thlight/vulcanizedb/pkg/watched_contracts"
@ -15,14 +17,27 @@ import (
func main() { func main() {
environment := flag.String("environment", "", "Environment name") environment := flag.String("environment", "", "Environment name")
contractHash := flag.String("contract-hash", "", "Contract hash to show summary") contractHash := flag.String("contract-hash", "", "Contract hash to show summary")
_blockNumber := flag.Int64("block-number", -1, "Block number of summary")
flag.Parse() flag.Parse()
config := cmd.LoadConfig(*environment) config := cmd.LoadConfig(*environment)
blockchain := geth.NewGethBlockchain(config.Client.IPCPath) blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
repository := cmd.LoadPostgres(config.Database) repository := cmd.LoadPostgres(config.Database)
contractSummary, err := watched_contracts.NewSummary(blockchain, repository, *contractHash) blockNumber := requestedBlockNumber(_blockNumber)
contractSummary, err := watched_contracts.NewSummary(blockchain, repository, *contractHash, blockNumber)
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }
output := watched_contracts.GenerateConsoleOutput(contractSummary) output := watched_contracts.GenerateConsoleOutput(contractSummary)
fmt.Println(output) fmt.Println(output)
} }
func requestedBlockNumber(blockNumber *int64) *big.Int {
var _blockNumber *big.Int
if *blockNumber == -1 {
_blockNumber = nil
} else {
_blockNumber = big.NewInt(*blockNumber)
}
return _blockNumber
}

View File

@ -42,7 +42,7 @@ var _ = Describe("Reading from the Geth blockchain", func() {
Expect(firstBlock.Number + 1).Should(Equal(secondBlock.Number)) Expect(firstBlock.Number + 1).Should(Equal(secondBlock.Number))
close(done) close(done)
}, 10) }, 15)
It("retrieves the genesis block and first block", func(done Done) { It("retrieves the genesis block and first block", func(done Done) {
genesisBlock := blockchain.GetBlockByNumber(int64(0)) genesisBlock := blockchain.GetBlockByNumber(int64(0))
@ -52,6 +52,6 @@ var _ = Describe("Reading from the Geth blockchain", func() {
Expect(firstBlock.Number).To(Equal(int64(1))) Expect(firstBlock.Number).To(Equal(int64(1)))
close(done) close(done)
}, 10) }, 15)
}) })

View File

@ -1,10 +1,12 @@
package core package core
import "math/big"
type Blockchain interface { type Blockchain interface {
GetBlockByNumber(blockNumber int64) Block GetBlockByNumber(blockNumber int64) Block
SubscribeToBlocks(blocks chan Block) SubscribeToBlocks(blocks chan Block)
StartListening() StartListening()
StopListening() StopListening()
GetContract(contractHash string) (Contract, error) GetContract(contractHash string) (Contract, error)
GetAttribute(contract Contract, attributeName string) (interface{}, error) GetAttribute(contract Contract, attributeName string, blockNumber *big.Int) (interface{}, error)
} }

View File

@ -3,6 +3,8 @@ package fakes
import ( import (
"sort" "sort"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core" "github.com/8thlight/vulcanizedb/pkg/core"
) )
@ -22,8 +24,13 @@ func (blockchain *Blockchain) GetContract(contractHash string) (core.Contract, e
return contract, err return contract, err
} }
func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName string) (interface{}, error) { func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName string, blockNumber *big.Int) (interface{}, error) {
result := blockchain.contractAttributes[contract.Hash][attributeName] var result interface{}
if blockNumber == nil {
result = blockchain.contractAttributes[contract.Hash+"-1"][attributeName]
} else {
result = blockchain.contractAttributes[contract.Hash+blockNumber.String()][attributeName]
}
return result, nil return result, nil
} }
@ -63,17 +70,23 @@ func (blockchain *Blockchain) StopListening() {
blockchain.WasToldToStop = true blockchain.WasToldToStop = true
} }
func (blockchain *Blockchain) SetContractStateAttribute(contractHash string, attributeName string, attributeValue string) { func (blockchain *Blockchain) SetContractStateAttribute(contractHash string, blockNumber *big.Int, attributeName string, attributeValue string) {
contractStateAttributes := blockchain.contractAttributes[contractHash] var key string
if contractStateAttributes == nil { if blockNumber == nil {
blockchain.contractAttributes[contractHash] = make(map[string]string) key = contractHash + "-1"
} else {
key = contractHash + blockNumber.String()
} }
blockchain.contractAttributes[contractHash][attributeName] = attributeValue contractStateAttributes := blockchain.contractAttributes[key]
if contractStateAttributes == nil {
blockchain.contractAttributes[key] = make(map[string]string)
}
blockchain.contractAttributes[key][attributeName] = attributeValue
} }
func (blockchain *Blockchain) getContractAttributes(contractHash string) (core.ContractAttributes, error) { func (blockchain *Blockchain) getContractAttributes(contractHash string) (core.ContractAttributes, error) {
var contractAttributes core.ContractAttributes var contractAttributes core.ContractAttributes
attributes, ok := blockchain.contractAttributes[contractHash] attributes, ok := blockchain.contractAttributes[contractHash+"-1"]
if ok { if ok {
for key, _ := range attributes { for key, _ := range attributes {
contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"}) contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"})

View File

@ -7,9 +7,13 @@ import (
"sort" "sort"
"context"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/config" "github.com/8thlight/vulcanizedb/pkg/config"
"github.com/8thlight/vulcanizedb/pkg/core" "github.com/8thlight/vulcanizedb/pkg/core"
"github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
) )
@ -30,18 +34,40 @@ func (blockchain *GethBlockchain) GetContract(contractHash string) (core.Contrac
} }
} }
func (blockchain *GethBlockchain) GetAttribute(contract core.Contract, attributeName string) (interface{}, error) { func (blockchain *GethBlockchain) getParseAbi(contract core.Contract) (abi.ABI, error) {
boundContract, err := bindContract(common.HexToAddress(contract.Hash), blockchain.client, blockchain.client) abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", contract.Hash))
parsed, err := ParseAbiFile(abiFilePath)
if err != nil {
return abi.ABI{}, err
}
return parsed, nil
}
func (blockchain *GethBlockchain) GetAttribute(contract core.Contract, attributeName string, blockNumber *big.Int) (interface{}, error) {
parsed, err := blockchain.getParseAbi(contract)
var result interface{}
if err != nil {
return result, err
}
input, err := parsed.Pack(attributeName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
var result interface{} output, err := callContract(contract, input, err, blockchain, blockNumber)
err = boundContract.Call(&bind.CallOpts{}, &result, attributeName) if err != nil {
return nil, err
}
err = parsed.Unpack(&result, attributeName, output)
if err != nil { if err != nil {
return nil, ErrInvalidStateAttribute return nil, ErrInvalidStateAttribute
} }
return result, nil return result, nil
} }
func callContract(contract core.Contract, input []byte, err error, blockchain *GethBlockchain, blockNumber *big.Int) ([]byte, error) {
to := common.HexToAddress(contract.Hash)
msg := ethereum.CallMsg{To: &to, Data: input}
return blockchain.client.CallContract(context.Background(), msg, blockNumber)
}
func (blockchain *GethBlockchain) getContractAttributes(contractHash string) (core.ContractAttributes, error) { func (blockchain *GethBlockchain) getContractAttributes(contractHash string) (core.ContractAttributes, error) {
abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", contractHash)) abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", contractHash))
@ -56,12 +82,3 @@ func (blockchain *GethBlockchain) getContractAttributes(contractHash string) (co
sort.Sort(contractAttributes) sort.Sort(contractAttributes)
return contractAttributes, nil return contractAttributes, nil
} }
func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor) (bind.BoundContract, error) {
abiFilePath := filepath.Join(config.ProjectRoot(), "contracts", "public", fmt.Sprintf("%s.json", address.Hex()))
parsed, err := ParseAbiFile(abiFilePath)
if err != nil {
return bind.BoundContract{}, err
}
return *bind.NewBoundContract(address, parsed, caller, transactor), nil
}

View File

@ -1,6 +1,8 @@
package geth_test package geth_test
//import ( //import (
// "math/big"
//
// cfg "github.com/8thlight/vulcanizedb/pkg/config" // cfg "github.com/8thlight/vulcanizedb/pkg/config"
// "github.com/8thlight/vulcanizedb/pkg/geth" // "github.com/8thlight/vulcanizedb/pkg/geth"
// "github.com/8thlight/vulcanizedb/pkg/geth/testing" // "github.com/8thlight/vulcanizedb/pkg/geth/testing"
@ -57,19 +59,43 @@ package geth_test
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" // contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
// //
// contract, _ := blockchain.GetContract(contractHash) // contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "name") // name, err := blockchain.GetAttribute(contract, "name", nil)
// //
// Expect(err).To(BeNil()) // Expect(err).To(BeNil())
// Expect(name).To(Equal("OMGToken")) // 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)
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
//
// contract, _ := blockchain.GetContract(contractHash)
// 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)
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
//
// contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "name", big.NewInt(4652791))
//
// Expect(name).To(Equal("OMGToken"))
// Expect(err).To(BeNil())
// })
//
// It("returns an error when there is no ABI for the given contract", func() { // It("returns an error when there is no ABI for the given contract", func() {
// config, _ := cfg.NewConfig("public") // config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath) // blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contractHash := "MISSINGHASH" // contractHash := "MISSINGHASH"
// //
// contract, _ := blockchain.GetContract(contractHash) // contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "name") // name, err := blockchain.GetAttribute(contract, "name", nil)
// //
// Expect(err).To(Equal(geth.ErrMissingAbiFile)) // Expect(err).To(Equal(geth.ErrMissingAbiFile))
// Expect(name).To(BeNil()) // Expect(name).To(BeNil())
@ -81,7 +107,7 @@ package geth_test
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07" // contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
// //
// contract, _ := blockchain.GetContract(contractHash) // contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "missing_attribute") // name, err := blockchain.GetAttribute(contract, "missing_attribute", nil)
// //
// Expect(err).To(Equal(geth.ErrInvalidStateAttribute)) // Expect(err).To(Equal(geth.ErrInvalidStateAttribute))
// Expect(name).To(BeNil()) // Expect(name).To(BeNil())

View File

@ -4,6 +4,8 @@ import (
"errors" "errors"
"fmt" "fmt"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core" "github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/repositories" "github.com/8thlight/vulcanizedb/pkg/repositories"
) )
@ -15,27 +17,29 @@ type ContractSummary struct {
LastTransaction *core.Transaction LastTransaction *core.Transaction
blockChain core.Blockchain blockChain core.Blockchain
Attributes core.ContractAttributes Attributes core.ContractAttributes
BlockNumber *big.Int
} }
var NewContractNotWatchedErr = func(contractHash string) error { var NewContractNotWatchedErr = func(contractHash string) error {
return errors.New(fmt.Sprintf("Contract %v not being watched", contractHash)) return errors.New(fmt.Sprintf("Contract %v not being watched", contractHash))
} }
func NewSummary(blockchain core.Blockchain, repository repositories.Repository, contractHash string) (ContractSummary, error) { func NewSummary(blockchain core.Blockchain, repository repositories.Repository, contractHash string, blockNumber *big.Int) (ContractSummary, error) {
watchedContract := repository.FindWatchedContract(contractHash) watchedContract := repository.FindWatchedContract(contractHash)
if watchedContract != nil { if watchedContract != nil {
return newContractSummary(blockchain, *watchedContract), nil return newContractSummary(blockchain, *watchedContract, blockNumber), nil
} else { } else {
return ContractSummary{}, NewContractNotWatchedErr(contractHash) return ContractSummary{}, NewContractNotWatchedErr(contractHash)
} }
} }
func (contractSummary ContractSummary) GetStateAttribute(attributeName string) interface{} { func (contractSummary ContractSummary) GetStateAttribute(attributeName string) interface{} {
result, _ := contractSummary.blockChain.GetAttribute(contractSummary.Contract, attributeName) var result interface{}
result, _ = contractSummary.blockChain.GetAttribute(contractSummary.Contract, attributeName, contractSummary.BlockNumber)
return result return result
} }
func newContractSummary(blockchain core.Blockchain, watchedContract core.WatchedContract) ContractSummary { func newContractSummary(blockchain core.Blockchain, watchedContract core.WatchedContract, blockNumber *big.Int) ContractSummary {
contract, _ := blockchain.GetContract(watchedContract.Hash) contract, _ := blockchain.GetContract(watchedContract.Hash)
return ContractSummary{ return ContractSummary{
blockChain: blockchain, blockChain: blockchain,
@ -44,6 +48,7 @@ func newContractSummary(blockchain core.Blockchain, watchedContract core.Watched
NumberOfTransactions: len(watchedContract.Transactions), NumberOfTransactions: len(watchedContract.Transactions),
LastTransaction: lastTransaction(watchedContract), LastTransaction: lastTransaction(watchedContract),
Attributes: contract.Attributes, Attributes: contract.Attributes,
BlockNumber: blockNumber,
} }
} }

View File

@ -1,6 +1,8 @@
package watched_contracts_test package watched_contracts_test
import ( import (
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core" "github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/fakes" "github.com/8thlight/vulcanizedb/pkg/fakes"
"github.com/8thlight/vulcanizedb/pkg/repositories" "github.com/8thlight/vulcanizedb/pkg/repositories"
@ -9,14 +11,18 @@ import (
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
) )
var _ bool = Describe("The watched contract summary", func() { func NewCurrentContractSummary(blockchain core.Blockchain, repository repositories.Repository, contractHash string) (watched_contracts.ContractSummary, error) {
return watched_contracts.NewSummary(blockchain, repository, contractHash, nil)
}
var _ = Describe("The watched contract summary", func() {
Context("when the given contract is not being watched", func() { Context("when the given contract is not being watched", func() {
It("returns an error", func() { It("returns an error", func() {
repository := repositories.NewInMemory() repository := repositories.NewInMemory()
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
contractSummary, err := watched_contracts.NewSummary(blockchain, repository, "123") contractSummary, err := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary).To(Equal(watched_contracts.ContractSummary{})) Expect(contractSummary).To(Equal(watched_contracts.ContractSummary{}))
Expect(err).NotTo(BeNil()) Expect(err).NotTo(BeNil())
@ -30,7 +36,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateWatchedContract(watchedContract) repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
contractSummary, err := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, err := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary).NotTo(Equal(watched_contracts.ContractSummary{})) Expect(contractSummary).NotTo(Equal(watched_contracts.ContractSummary{}))
Expect(err).To(BeNil()) Expect(err).To(BeNil())
@ -42,7 +48,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateWatchedContract(watchedContract) repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.ContractHash).To(Equal("0x123")) Expect(contractSummary.ContractHash).To(Equal("0x123"))
}) })
@ -60,7 +66,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateBlock(block) repository.CreateBlock(block)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.NumberOfTransactions).To(Equal(2)) Expect(contractSummary.NumberOfTransactions).To(Equal(2))
}) })
@ -78,7 +84,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateBlock(block) repository.CreateBlock(block)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.LastTransaction.Hash).To(Equal("TRANSACTION2")) Expect(contractSummary.LastTransaction.Hash).To(Equal("TRANSACTION2"))
}) })
@ -88,23 +94,38 @@ var _ bool = Describe("The watched contract summary", func() {
watchedContract := core.WatchedContract{Hash: "0x123"} watchedContract := core.WatchedContract{Hash: "0x123"}
repository.CreateWatchedContract(watchedContract) repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
blockchain.SetContractStateAttribute("0x123", "foo", "bar") blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar")
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
attribute := contractSummary.GetStateAttribute("foo") attribute := contractSummary.GetStateAttribute("foo")
Expect(attribute).To(Equal("bar")) Expect(attribute).To(Equal("bar"))
}) })
It("gets contract state attribute for the contract from the blockchain at specific block height", func() {
repository := repositories.NewInMemory()
watchedContract := core.WatchedContract{Hash: "0x123"}
repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain()
blockNumber := big.NewInt(1000)
blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar")
blockchain.SetContractStateAttribute("0x123", blockNumber, "foo", "baz")
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123", blockNumber)
attribute := contractSummary.GetStateAttribute("foo")
Expect(attribute).To(Equal("baz"))
})
It("gets attributes for the contract from the blockchain", func() { It("gets attributes for the contract from the blockchain", func() {
repository := repositories.NewInMemory() repository := repositories.NewInMemory()
watchedContract := core.WatchedContract{Hash: "0x123"} watchedContract := core.WatchedContract{Hash: "0x123"}
repository.CreateWatchedContract(watchedContract) repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain() blockchain := fakes.NewBlockchain()
blockchain.SetContractStateAttribute("0x123", "foo", "bar") blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar")
blockchain.SetContractStateAttribute("0x123", "baz", "bar") blockchain.SetContractStateAttribute("0x123", nil, "baz", "bar")
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123") contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.Attributes).To(Equal( Expect(contractSummary.Attributes).To(Equal(
core.ContractAttributes{ core.ContractAttributes{