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"
"math/big"
"github.com/8thlight/vulcanizedb/cmd"
"github.com/8thlight/vulcanizedb/pkg/geth"
"github.com/8thlight/vulcanizedb/pkg/watched_contracts"
@ -15,14 +17,27 @@ import (
func main() {
environment := flag.String("environment", "", "Environment name")
contractHash := flag.String("contract-hash", "", "Contract hash to show summary")
_blockNumber := flag.Int64("block-number", -1, "Block number of summary")
flag.Parse()
config := cmd.LoadConfig(*environment)
blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
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 {
log.Fatalln(err)
}
output := watched_contracts.GenerateConsoleOutput(contractSummary)
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))
close(done)
}, 10)
}, 15)
It("retrieves the genesis block and first block", func(done Done) {
genesisBlock := blockchain.GetBlockByNumber(int64(0))
@ -52,6 +52,6 @@ var _ = Describe("Reading from the Geth blockchain", func() {
Expect(firstBlock.Number).To(Equal(int64(1)))
close(done)
}, 10)
}, 15)
})

View File

@ -1,10 +1,12 @@
package core
import "math/big"
type Blockchain interface {
GetBlockByNumber(blockNumber int64) Block
SubscribeToBlocks(blocks chan Block)
StartListening()
StopListening()
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 (
"sort"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core"
)
@ -22,8 +24,13 @@ func (blockchain *Blockchain) GetContract(contractHash string) (core.Contract, e
return contract, err
}
func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName string) (interface{}, error) {
result := blockchain.contractAttributes[contract.Hash][attributeName]
func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName string, blockNumber *big.Int) (interface{}, error) {
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
}
@ -63,17 +70,23 @@ func (blockchain *Blockchain) StopListening() {
blockchain.WasToldToStop = true
}
func (blockchain *Blockchain) SetContractStateAttribute(contractHash string, attributeName string, attributeValue string) {
contractStateAttributes := blockchain.contractAttributes[contractHash]
if contractStateAttributes == nil {
blockchain.contractAttributes[contractHash] = make(map[string]string)
func (blockchain *Blockchain) SetContractStateAttribute(contractHash string, blockNumber *big.Int, attributeName string, attributeValue string) {
var key string
if blockNumber == nil {
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) {
var contractAttributes core.ContractAttributes
attributes, ok := blockchain.contractAttributes[contractHash]
attributes, ok := blockchain.contractAttributes[contractHash+"-1"]
if ok {
for key, _ := range attributes {
contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"})

View File

@ -7,9 +7,13 @@ import (
"sort"
"context"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/config"
"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"
)
@ -30,18 +34,40 @@ func (blockchain *GethBlockchain) GetContract(contractHash string) (core.Contrac
}
}
func (blockchain *GethBlockchain) GetAttribute(contract core.Contract, attributeName string) (interface{}, error) {
boundContract, err := bindContract(common.HexToAddress(contract.Hash), blockchain.client, blockchain.client)
func (blockchain *GethBlockchain) getParseAbi(contract core.Contract) (abi.ABI, error) {
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 {
return nil, err
}
var result interface{}
err = boundContract.Call(&bind.CallOpts{}, &result, attributeName)
output, err := callContract(contract, input, err, blockchain, blockNumber)
if err != nil {
return nil, err
}
err = parsed.Unpack(&result, attributeName, output)
if err != nil {
return nil, ErrInvalidStateAttribute
}
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) {
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)
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
//import (
// "math/big"
//
// cfg "github.com/8thlight/vulcanizedb/pkg/config"
// "github.com/8thlight/vulcanizedb/pkg/geth"
// "github.com/8thlight/vulcanizedb/pkg/geth/testing"
@ -57,19 +59,43 @@ package geth_test
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
//
// contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "name")
// 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)
// 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() {
// config, _ := cfg.NewConfig("public")
// blockchain := geth.NewGethBlockchain(config.Client.IPCPath)
// contractHash := "MISSINGHASH"
//
// contract, _ := blockchain.GetContract(contractHash)
// name, err := blockchain.GetAttribute(contract, "name")
// name, err := blockchain.GetAttribute(contract, "name", nil)
//
// Expect(err).To(Equal(geth.ErrMissingAbiFile))
// Expect(name).To(BeNil())
@ -81,7 +107,7 @@ package geth_test
// contractHash := "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"
//
// 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(name).To(BeNil())

View File

@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/repositories"
)
@ -15,27 +17,29 @@ type ContractSummary struct {
LastTransaction *core.Transaction
blockChain core.Blockchain
Attributes core.ContractAttributes
BlockNumber *big.Int
}
var NewContractNotWatchedErr = func(contractHash string) error {
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)
if watchedContract != nil {
return newContractSummary(blockchain, *watchedContract), nil
return newContractSummary(blockchain, *watchedContract, blockNumber), nil
} else {
return ContractSummary{}, NewContractNotWatchedErr(contractHash)
}
}
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
}
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)
return ContractSummary{
blockChain: blockchain,
@ -44,6 +48,7 @@ func newContractSummary(blockchain core.Blockchain, watchedContract core.Watched
NumberOfTransactions: len(watchedContract.Transactions),
LastTransaction: lastTransaction(watchedContract),
Attributes: contract.Attributes,
BlockNumber: blockNumber,
}
}

View File

@ -1,6 +1,8 @@
package watched_contracts_test
import (
"math/big"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/fakes"
"github.com/8thlight/vulcanizedb/pkg/repositories"
@ -9,14 +11,18 @@ import (
. "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() {
It("returns an error", func() {
repository := repositories.NewInMemory()
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(err).NotTo(BeNil())
@ -30,7 +36,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateWatchedContract(watchedContract)
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(err).To(BeNil())
@ -42,7 +48,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123")
contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.ContractHash).To(Equal("0x123"))
})
@ -60,7 +66,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateBlock(block)
blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123")
contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.NumberOfTransactions).To(Equal(2))
})
@ -78,7 +84,7 @@ var _ bool = Describe("The watched contract summary", func() {
repository.CreateBlock(block)
blockchain := fakes.NewBlockchain()
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123")
contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.LastTransaction.Hash).To(Equal("TRANSACTION2"))
})
@ -88,23 +94,38 @@ var _ bool = Describe("The watched contract summary", func() {
watchedContract := core.WatchedContract{Hash: "0x123"}
repository.CreateWatchedContract(watchedContract)
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")
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() {
repository := repositories.NewInMemory()
watchedContract := core.WatchedContract{Hash: "0x123"}
repository.CreateWatchedContract(watchedContract)
blockchain := fakes.NewBlockchain()
blockchain.SetContractStateAttribute("0x123", "foo", "bar")
blockchain.SetContractStateAttribute("0x123", "baz", "bar")
blockchain.SetContractStateAttribute("0x123", nil, "foo", "bar")
blockchain.SetContractStateAttribute("0x123", nil, "baz", "bar")
contractSummary, _ := watched_contracts.NewSummary(blockchain, repository, "0x123")
contractSummary, _ := NewCurrentContractSummary(blockchain, repository, "0x123")
Expect(contractSummary.Attributes).To(Equal(
core.ContractAttributes{