Rename geth to eth, signifying client independence (#161)
This commit is contained in:
+105
@@ -0,0 +1,105 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fs"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidAbiFile = errors.New("invalid abi")
|
||||
ErrMissingAbiFile = errors.New("missing abi")
|
||||
ErrApiRequestFailed = errors.New("etherscan api request failed")
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Status string
|
||||
Message string
|
||||
Result string
|
||||
}
|
||||
|
||||
type EtherScanAPI struct {
|
||||
client *http.Client
|
||||
url string
|
||||
}
|
||||
|
||||
func NewEtherScanClient(url string) *EtherScanAPI {
|
||||
return &EtherScanAPI{
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
url: url,
|
||||
}
|
||||
}
|
||||
|
||||
func GenURL(network string) string {
|
||||
switch network {
|
||||
case "ropsten":
|
||||
return "https://ropsten.etherscan.io"
|
||||
case "kovan":
|
||||
return "https://kovan.etherscan.io"
|
||||
case "rinkeby":
|
||||
return "https://rinkeby.etherscan.io"
|
||||
default:
|
||||
return "https://api.etherscan.io"
|
||||
}
|
||||
}
|
||||
|
||||
//https://api.etherscan.io/api?module=contract&action=getabi&address=%s
|
||||
func (e *EtherScanAPI) GetAbi(contractHash string) (string, error) {
|
||||
target := new(Response)
|
||||
request := fmt.Sprintf("%s/api?module=contract&action=getabi&address=%s", e.url, contractHash)
|
||||
r, err := e.client.Get(request)
|
||||
if err != nil {
|
||||
return "", ErrApiRequestFailed
|
||||
}
|
||||
defer r.Body.Close()
|
||||
err = json.NewDecoder(r.Body).Decode(&target)
|
||||
return target.Result, err
|
||||
}
|
||||
|
||||
func ParseAbiFile(abiFilePath string) (abi.ABI, error) {
|
||||
abiString, err := ReadAbiFile(abiFilePath)
|
||||
if err != nil {
|
||||
return abi.ABI{}, ErrMissingAbiFile
|
||||
}
|
||||
return ParseAbi(abiString)
|
||||
}
|
||||
|
||||
func ParseAbi(abiString string) (abi.ABI, error) {
|
||||
parsedAbi, err := abi.JSON(strings.NewReader(abiString))
|
||||
if err != nil {
|
||||
return abi.ABI{}, ErrInvalidAbiFile
|
||||
}
|
||||
return parsedAbi, nil
|
||||
}
|
||||
|
||||
func ReadAbiFile(abiFilePath string) (string, error) {
|
||||
reader := fs.FsReader{}
|
||||
filesBytes, err := reader.Read(abiFilePath)
|
||||
if err != nil {
|
||||
return "", ErrMissingAbiFile
|
||||
}
|
||||
return string(filesBytes), nil
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/onsi/gomega/ghttp"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
var _ = Describe("ABI files", func() {
|
||||
|
||||
Describe("Reading ABI files", func() {
|
||||
|
||||
It("loads a valid ABI file", func() {
|
||||
path := test_config.ABIFilePath + "valid_abi.json"
|
||||
|
||||
contractAbi, err := eth.ParseAbiFile(path)
|
||||
|
||||
Expect(contractAbi).NotTo(BeNil())
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
It("reads the contents of a valid ABI file", func() {
|
||||
path := test_config.ABIFilePath + "valid_abi.json"
|
||||
|
||||
contractAbi, err := eth.ReadAbiFile(path)
|
||||
|
||||
Expect(contractAbi).To(Equal("[{\"foo\": \"bar\"}]"))
|
||||
Expect(err).To(BeNil())
|
||||
})
|
||||
|
||||
It("returns an error when the file does not exist", func() {
|
||||
path := test_config.ABIFilePath + "missing_abi.json"
|
||||
|
||||
contractAbi, err := eth.ParseAbiFile(path)
|
||||
|
||||
Expect(contractAbi).To(Equal(abi.ABI{}))
|
||||
Expect(err).To(Equal(eth.ErrMissingAbiFile))
|
||||
})
|
||||
|
||||
It("returns an error when the file has invalid contents", func() {
|
||||
path := test_config.ABIFilePath + "invalid_abi.json"
|
||||
|
||||
contractAbi, err := eth.ParseAbiFile(path)
|
||||
|
||||
Expect(contractAbi).To(Equal(abi.ABI{}))
|
||||
Expect(err).To(Equal(eth.ErrInvalidAbiFile))
|
||||
})
|
||||
|
||||
Describe("Request ABI from endpoint", func() {
|
||||
|
||||
var (
|
||||
server *ghttp.Server
|
||||
client *eth.EtherScanAPI
|
||||
abiString string
|
||||
err error
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
server = ghttp.NewServer()
|
||||
client = eth.NewEtherScanClient(server.URL())
|
||||
path := test_config.ABIFilePath + "sample_abi.json"
|
||||
abiString, err = eth.ReadAbiFile(path)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = eth.ParseAbi(abiString)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
server.Close()
|
||||
})
|
||||
|
||||
Describe("Fetching ABI from api (etherscan)", func() {
|
||||
BeforeEach(func() {
|
||||
|
||||
response := fmt.Sprintf(`{"status":"1","message":"OK","result":%q}`, abiString)
|
||||
server.AppendHandlers(
|
||||
ghttp.CombineHandlers(
|
||||
ghttp.VerifyRequest("GET", "/api", "module=contract&action=getabi&address=0xd26114cd6EE289AccF82350c8d8487fedB8A0C07"),
|
||||
ghttp.RespondWith(http.StatusOK, response),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
It("should make a GET request with supplied contract hash", func() {
|
||||
|
||||
abi, err := client.GetAbi("0xd26114cd6EE289AccF82350c8d8487fedB8A0C07")
|
||||
Expect(server.ReceivedRequests()).Should(HaveLen(1))
|
||||
Expect(err).ShouldNot(HaveOccurred())
|
||||
Expect(abi).Should(Equal(abiString))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Generating etherscan endpoints based on network", func() {
|
||||
It("should return the main endpoint as the default", func() {
|
||||
url := eth.GenURL("")
|
||||
Expect(url).To(Equal("https://api.etherscan.io"))
|
||||
})
|
||||
|
||||
It("generates various test network endpoint if test network is supplied", func() {
|
||||
ropstenUrl := eth.GenURL("ropsten")
|
||||
rinkebyUrl := eth.GenURL("rinkeby")
|
||||
kovanUrl := eth.GenURL("kovan")
|
||||
|
||||
Expect(ropstenUrl).To(Equal("https://ropsten.etherscan.io"))
|
||||
Expect(kovanUrl).To(Equal("https://kovan.etherscan.io"))
|
||||
Expect(rinkebyUrl).To(Equal("https://rinkeby.etherscan.io"))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,272 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/client"
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
)
|
||||
|
||||
var ErrEmptyHeader = errors.New("empty header returned over RPC")
|
||||
|
||||
const MAX_BATCH_SIZE = 100
|
||||
|
||||
type BlockChain struct {
|
||||
blockConverter vulcCommon.BlockConverter
|
||||
ethClient core.EthClient
|
||||
headerConverter vulcCommon.HeaderConverter
|
||||
node core.Node
|
||||
rpcClient core.RpcClient
|
||||
transactionConverter vulcCommon.TransactionConverter
|
||||
}
|
||||
|
||||
func NewBlockChain(ethClient core.EthClient, rpcClient core.RpcClient, node core.Node, converter vulcCommon.TransactionConverter) *BlockChain {
|
||||
return &BlockChain{
|
||||
blockConverter: vulcCommon.NewBlockConverter(converter),
|
||||
ethClient: ethClient,
|
||||
headerConverter: vulcCommon.HeaderConverter{},
|
||||
node: node,
|
||||
rpcClient: rpcClient,
|
||||
transactionConverter: converter,
|
||||
}
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetBlockByNumber(blockNumber int64) (block core.Block, err error) {
|
||||
gethBlock, err := blockChain.ethClient.BlockByNumber(context.Background(), big.NewInt(blockNumber))
|
||||
if err != nil {
|
||||
return block, err
|
||||
}
|
||||
return blockChain.blockConverter.ToCoreBlock(gethBlock)
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetEthLogsWithCustomQuery(query ethereum.FilterQuery) ([]types.Log, error) {
|
||||
gethLogs, err := blockChain.ethClient.FilterLogs(context.Background(), query)
|
||||
if err != nil {
|
||||
return []types.Log{}, err
|
||||
}
|
||||
return gethLogs, nil
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetHeaderByNumber(blockNumber int64) (header core.Header, err error) {
|
||||
if blockChain.node.NetworkID == core.KOVAN_NETWORK_ID {
|
||||
return blockChain.getPOAHeader(blockNumber)
|
||||
}
|
||||
return blockChain.getPOWHeader(blockNumber)
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetHeadersByNumbers(blockNumbers []int64) (header []core.Header, err error) {
|
||||
if blockChain.node.NetworkID == core.KOVAN_NETWORK_ID {
|
||||
return blockChain.getPOAHeaders(blockNumbers)
|
||||
}
|
||||
return blockChain.getPOWHeaders(blockNumbers)
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetFullSyncLogs(contract core.Contract, startingBlockNumber, endingBlockNumber *big.Int) ([]core.FullSyncLog, error) {
|
||||
if endingBlockNumber == nil {
|
||||
endingBlockNumber = startingBlockNumber
|
||||
}
|
||||
contractAddress := common.HexToAddress(contract.Hash)
|
||||
fc := ethereum.FilterQuery{
|
||||
FromBlock: startingBlockNumber,
|
||||
ToBlock: endingBlockNumber,
|
||||
Addresses: []common.Address{contractAddress},
|
||||
Topics: nil,
|
||||
}
|
||||
gethLogs, err := blockChain.GetEthLogsWithCustomQuery(fc)
|
||||
if err != nil {
|
||||
return []core.FullSyncLog{}, err
|
||||
}
|
||||
logs := vulcCommon.ToFullSyncLogs(gethLogs)
|
||||
return logs, nil
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetTransactions(transactionHashes []common.Hash) ([]core.TransactionModel, error) {
|
||||
numTransactions := len(transactionHashes)
|
||||
var batch []client.BatchElem
|
||||
transactions := make([]core.RpcTransaction, numTransactions)
|
||||
|
||||
for index, transactionHash := range transactionHashes {
|
||||
batchElem := client.BatchElem{
|
||||
Method: "eth_getTransactionByHash",
|
||||
Result: &transactions[index],
|
||||
Args: []interface{}{transactionHash},
|
||||
}
|
||||
batch = append(batch, batchElem)
|
||||
}
|
||||
|
||||
rpcErr := blockChain.rpcClient.BatchCall(batch)
|
||||
if rpcErr != nil {
|
||||
return []core.TransactionModel{}, rpcErr
|
||||
}
|
||||
|
||||
return blockChain.transactionConverter.ConvertRpcTransactionsToModels(transactions)
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) LastBlock() (*big.Int, error) {
|
||||
block, err := blockChain.ethClient.HeaderByNumber(context.Background(), nil)
|
||||
if err != nil {
|
||||
return big.NewInt(0), err
|
||||
}
|
||||
return block.Number, err
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) Node() core.Node {
|
||||
return blockChain.node
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) getPOAHeader(blockNumber int64) (header core.Header, err error) {
|
||||
var POAHeader core.POAHeader
|
||||
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
|
||||
includeTransactions := false
|
||||
err = blockChain.rpcClient.CallContext(context.Background(), &POAHeader, "eth_getBlockByNumber", blockNumberArg, includeTransactions)
|
||||
if err != nil {
|
||||
return header, err
|
||||
}
|
||||
if POAHeader.Number == nil {
|
||||
return header, ErrEmptyHeader
|
||||
}
|
||||
return blockChain.headerConverter.Convert(&types.Header{
|
||||
ParentHash: POAHeader.ParentHash,
|
||||
UncleHash: POAHeader.UncleHash,
|
||||
Coinbase: POAHeader.Coinbase,
|
||||
Root: POAHeader.Root,
|
||||
TxHash: POAHeader.TxHash,
|
||||
ReceiptHash: POAHeader.ReceiptHash,
|
||||
Bloom: POAHeader.Bloom,
|
||||
Difficulty: POAHeader.Difficulty.ToInt(),
|
||||
Number: POAHeader.Number.ToInt(),
|
||||
GasLimit: uint64(POAHeader.GasLimit),
|
||||
GasUsed: uint64(POAHeader.GasUsed),
|
||||
Time: uint64(POAHeader.Time),
|
||||
Extra: POAHeader.Extra,
|
||||
}, POAHeader.Hash.String()), nil
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) getPOAHeaders(blockNumbers []int64) (headers []core.Header, err error) {
|
||||
|
||||
var batch []client.BatchElem
|
||||
var POAHeaders [MAX_BATCH_SIZE]core.POAHeader
|
||||
includeTransactions := false
|
||||
|
||||
for index, blockNumber := range blockNumbers {
|
||||
|
||||
if index >= MAX_BATCH_SIZE {
|
||||
break
|
||||
}
|
||||
|
||||
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
|
||||
|
||||
batchElem := client.BatchElem{
|
||||
Method: "eth_getBlockByNumber",
|
||||
Result: &POAHeaders[index],
|
||||
Args: []interface{}{blockNumberArg, includeTransactions},
|
||||
}
|
||||
|
||||
batch = append(batch, batchElem)
|
||||
}
|
||||
|
||||
err = blockChain.rpcClient.BatchCall(batch)
|
||||
if err != nil {
|
||||
return headers, err
|
||||
}
|
||||
|
||||
for _, POAHeader := range POAHeaders {
|
||||
var header core.Header
|
||||
//Header.Number of the newest block will return nil.
|
||||
if _, err := strconv.ParseUint(POAHeader.Number.ToInt().String(), 16, 64); err == nil {
|
||||
header = blockChain.headerConverter.Convert(&types.Header{
|
||||
ParentHash: POAHeader.ParentHash,
|
||||
UncleHash: POAHeader.UncleHash,
|
||||
Coinbase: POAHeader.Coinbase,
|
||||
Root: POAHeader.Root,
|
||||
TxHash: POAHeader.TxHash,
|
||||
ReceiptHash: POAHeader.ReceiptHash,
|
||||
Bloom: POAHeader.Bloom,
|
||||
Difficulty: POAHeader.Difficulty.ToInt(),
|
||||
Number: POAHeader.Number.ToInt(),
|
||||
GasLimit: uint64(POAHeader.GasLimit),
|
||||
GasUsed: uint64(POAHeader.GasUsed),
|
||||
Time: uint64(POAHeader.Time),
|
||||
Extra: POAHeader.Extra,
|
||||
}, POAHeader.Hash.String())
|
||||
|
||||
headers = append(headers, header)
|
||||
}
|
||||
}
|
||||
|
||||
return headers, err
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) getPOWHeader(blockNumber int64) (header core.Header, err error) {
|
||||
gethHeader, err := blockChain.ethClient.HeaderByNumber(context.Background(), big.NewInt(blockNumber))
|
||||
if err != nil {
|
||||
return header, err
|
||||
}
|
||||
return blockChain.headerConverter.Convert(gethHeader, gethHeader.Hash().String()), nil
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) getPOWHeaders(blockNumbers []int64) (headers []core.Header, err error) {
|
||||
var batch []client.BatchElem
|
||||
var POWHeaders [MAX_BATCH_SIZE]types.Header
|
||||
includeTransactions := false
|
||||
|
||||
for index, blockNumber := range blockNumbers {
|
||||
|
||||
if index >= MAX_BATCH_SIZE {
|
||||
break
|
||||
}
|
||||
|
||||
blockNumberArg := hexutil.EncodeBig(big.NewInt(blockNumber))
|
||||
|
||||
batchElem := client.BatchElem{
|
||||
Method: "eth_getBlockByNumber",
|
||||
Result: &POWHeaders[index],
|
||||
Args: []interface{}{blockNumberArg, includeTransactions},
|
||||
}
|
||||
|
||||
batch = append(batch, batchElem)
|
||||
}
|
||||
|
||||
err = blockChain.rpcClient.BatchCall(batch)
|
||||
if err != nil {
|
||||
return headers, err
|
||||
}
|
||||
|
||||
for _, POWHeader := range POWHeaders {
|
||||
if POWHeader.Number != nil {
|
||||
header := blockChain.headerConverter.Convert(&POWHeader, POWHeader.Hash().String())
|
||||
headers = append(headers, header)
|
||||
}
|
||||
}
|
||||
|
||||
return headers, err
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) GetAccountBalance(address common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
return blockChain.ethClient.BalanceAt(context.Background(), address, blockNumber)
|
||||
}
|
||||
@@ -0,0 +1,272 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"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"
|
||||
|
||||
vulcCore "github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("Geth blockchain", func() {
|
||||
var (
|
||||
mockClient *fakes.MockEthClient
|
||||
blockChain *eth.BlockChain
|
||||
mockRpcClient *fakes.MockRpcClient
|
||||
mockTransactionConverter *fakes.MockTransactionConverter
|
||||
node vulcCore.Node
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
mockClient = fakes.NewMockEthClient()
|
||||
mockRpcClient = fakes.NewMockRpcClient()
|
||||
mockTransactionConverter = fakes.NewMockTransactionConverter()
|
||||
node = vulcCore.Node{}
|
||||
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, mockTransactionConverter)
|
||||
})
|
||||
|
||||
Describe("getting a block", func() {
|
||||
It("fetches block from ethClient", func() {
|
||||
mockClient.SetBlockByNumberReturnBlock(types.NewBlockWithHeader(&types.Header{}))
|
||||
blockNumber := int64(100)
|
||||
|
||||
_, err := blockChain.GetBlockByNumber(blockNumber)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockClient.AssertBlockByNumberCalledWith(context.Background(), big.NewInt(blockNumber))
|
||||
})
|
||||
|
||||
It("returns err if ethClient returns err", func() {
|
||||
mockClient.SetBlockByNumberErr(fakes.FakeError)
|
||||
|
||||
_, err := blockChain.GetBlockByNumber(100)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting a header", func() {
|
||||
Describe("default/mainnet", func() {
|
||||
It("fetches header from ethClient", func() {
|
||||
blockNumber := int64(100)
|
||||
mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)})
|
||||
|
||||
_, err := blockChain.GetHeaderByNumber(blockNumber)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockClient.AssertHeaderByNumberCalledWith(context.Background(), big.NewInt(blockNumber))
|
||||
})
|
||||
|
||||
It("returns err if ethClient returns err", func() {
|
||||
mockClient.SetHeaderByNumberErr(fakes.FakeError)
|
||||
|
||||
_, err := blockChain.GetHeaderByNumber(100)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("fetches headers with multiple blocks", func() {
|
||||
_, err := blockChain.GetHeadersByNumbers([]int64{100, 99})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockRpcClient.AssertBatchCalledWith("eth_getBlockByNumber", 2)
|
||||
})
|
||||
})
|
||||
|
||||
Describe("POA/Kovan", func() {
|
||||
It("fetches header from rpcClient", func() {
|
||||
node.NetworkID = vulcCore.KOVAN_NETWORK_ID
|
||||
blockNumber := hexutil.Big(*big.NewInt(100))
|
||||
mockRpcClient.SetReturnPOAHeader(vulcCore.POAHeader{Number: &blockNumber})
|
||||
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
|
||||
|
||||
_, err := blockChain.GetHeaderByNumber(100)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockRpcClient.AssertCallContextCalledWith(context.Background(), &vulcCore.POAHeader{}, "eth_getBlockByNumber")
|
||||
})
|
||||
|
||||
It("returns err if rpcClient returns err", func() {
|
||||
node.NetworkID = vulcCore.KOVAN_NETWORK_ID
|
||||
mockRpcClient.SetCallContextErr(fakes.FakeError)
|
||||
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
|
||||
|
||||
_, err := blockChain.GetHeaderByNumber(100)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("returns error if returned header is empty", func() {
|
||||
node.NetworkID = vulcCore.KOVAN_NETWORK_ID
|
||||
blockChain = eth.NewBlockChain(mockClient, mockRpcClient, node, fakes.NewMockTransactionConverter())
|
||||
|
||||
_, err := blockChain.GetHeaderByNumber(100)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(eth.ErrEmptyHeader))
|
||||
})
|
||||
|
||||
It("returns multiple headers with multiple blocknumbers", func() {
|
||||
node.NetworkID = vulcCore.KOVAN_NETWORK_ID
|
||||
blockNumber := hexutil.Big(*big.NewInt(100))
|
||||
mockRpcClient.SetReturnPOAHeaders([]vulcCore.POAHeader{{Number: &blockNumber}})
|
||||
|
||||
_, err := blockChain.GetHeadersByNumbers([]int64{100, 99})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockRpcClient.AssertBatchCalledWith("eth_getBlockByNumber", 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting logs with default FilterQuery", func() {
|
||||
It("fetches logs from ethClient", func() {
|
||||
mockClient.SetFilterLogsReturnLogs([]types.Log{{}})
|
||||
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
|
||||
startingBlockNumber := big.NewInt(1)
|
||||
endingBlockNumber := big.NewInt(2)
|
||||
|
||||
_, err := blockChain.GetFullSyncLogs(contract, startingBlockNumber, endingBlockNumber)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedQuery := ethereum.FilterQuery{
|
||||
FromBlock: startingBlockNumber,
|
||||
ToBlock: endingBlockNumber,
|
||||
Addresses: []common.Address{common.HexToAddress(contract.Hash)},
|
||||
}
|
||||
mockClient.AssertFilterLogsCalledWith(context.Background(), expectedQuery)
|
||||
})
|
||||
|
||||
It("returns err if ethClient returns err", func() {
|
||||
mockClient.SetFilterLogsErr(fakes.FakeError)
|
||||
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
|
||||
startingBlockNumber := big.NewInt(1)
|
||||
endingBlockNumber := big.NewInt(2)
|
||||
|
||||
_, err := blockChain.GetFullSyncLogs(contract, startingBlockNumber, endingBlockNumber)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting logs with a custom FilterQuery", func() {
|
||||
It("fetches logs from ethClient", func() {
|
||||
mockClient.SetFilterLogsReturnLogs([]types.Log{{}})
|
||||
address := common.HexToAddress("0x")
|
||||
startingBlockNumber := big.NewInt(1)
|
||||
endingBlockNumber := big.NewInt(2)
|
||||
topic := common.HexToHash("0x")
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: startingBlockNumber,
|
||||
ToBlock: endingBlockNumber,
|
||||
Addresses: []common.Address{address},
|
||||
Topics: [][]common.Hash{{topic}},
|
||||
}
|
||||
|
||||
_, err := blockChain.GetEthLogsWithCustomQuery(query)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockClient.AssertFilterLogsCalledWith(context.Background(), query)
|
||||
})
|
||||
|
||||
It("returns err if ethClient returns err", func() {
|
||||
mockClient.SetFilterLogsErr(fakes.FakeError)
|
||||
contract := vulcCore.Contract{Hash: common.BytesToHash([]byte{1, 2, 3, 4, 5}).Hex()}
|
||||
startingBlockNumber := big.NewInt(1)
|
||||
endingBlockNumber := big.NewInt(2)
|
||||
query := ethereum.FilterQuery{
|
||||
FromBlock: startingBlockNumber,
|
||||
ToBlock: endingBlockNumber,
|
||||
Addresses: []common.Address{common.HexToAddress(contract.Hash)},
|
||||
Topics: nil,
|
||||
}
|
||||
|
||||
_, err := blockChain.GetEthLogsWithCustomQuery(query)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting transactions", func() {
|
||||
It("fetches transaction for each hash", func() {
|
||||
_, err := blockChain.GetTransactions([]common.Hash{{}, {}})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockRpcClient.AssertBatchCalledWith("eth_getTransactionByHash", 2)
|
||||
})
|
||||
|
||||
It("converts transaction indexes from hex to int", func() {
|
||||
_, err := blockChain.GetTransactions([]common.Hash{{}, {}})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(mockTransactionConverter.ConvertHeaderTransactionIndexToIntCalled).To(BeTrue())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting the most recent block number", func() {
|
||||
It("fetches latest header from ethClient", func() {
|
||||
blockNumber := int64(100)
|
||||
mockClient.SetHeaderByNumberReturnHeader(&types.Header{Number: big.NewInt(blockNumber)})
|
||||
|
||||
result, err := blockChain.LastBlock()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
mockClient.AssertHeaderByNumberCalledWith(context.Background(), nil)
|
||||
Expect(result).To(Equal(big.NewInt(blockNumber)))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("getting an account balance", func() {
|
||||
It("fetches the balance for a given account address at a given block height", func() {
|
||||
balance := big.NewInt(100000)
|
||||
mockClient.SetBalanceAt(balance)
|
||||
|
||||
result, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
mockClient.AssertBalanceAtCalled(context.Background(), common.HexToAddress("0x40"), big.NewInt(100))
|
||||
Expect(result).To(Equal(balance))
|
||||
})
|
||||
|
||||
It("fails if the client returns an error", func() {
|
||||
balance := big.NewInt(100000)
|
||||
mockClient.SetBalanceAt(balance)
|
||||
setErr := errors.New("testError")
|
||||
mockClient.SetBalanceAtErr(setErr)
|
||||
|
||||
_, err := blockChain.GetAccountBalance(common.HexToAddress("0x40"), big.NewInt(100))
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(Equal(setErr))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,62 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type EthClient struct {
|
||||
client *ethclient.Client
|
||||
}
|
||||
|
||||
func NewEthClient(client *ethclient.Client) EthClient {
|
||||
return EthClient{client: client}
|
||||
}
|
||||
|
||||
func (client EthClient) BlockByNumber(ctx context.Context, number *big.Int) (*types.Block, error) {
|
||||
return client.client.BlockByNumber(ctx, number)
|
||||
}
|
||||
|
||||
func (client EthClient) CallContract(ctx context.Context, msg ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
|
||||
return client.client.CallContract(ctx, msg, blockNumber)
|
||||
}
|
||||
|
||||
func (client EthClient) FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) {
|
||||
return client.client.FilterLogs(ctx, q)
|
||||
}
|
||||
|
||||
func (client EthClient) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) {
|
||||
return client.client.HeaderByNumber(ctx, number)
|
||||
}
|
||||
|
||||
func (client EthClient) TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error) {
|
||||
return client.client.TransactionSender(ctx, tx, block, index)
|
||||
}
|
||||
|
||||
func (client EthClient) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
||||
return client.client.TransactionReceipt(ctx, txHash)
|
||||
}
|
||||
|
||||
func (client EthClient) BalanceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (*big.Int, error) {
|
||||
return client.client.BalanceAt(ctx, account, blockNumber)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
)
|
||||
|
||||
type RpcClient struct {
|
||||
client *rpc.Client
|
||||
ipcPath string
|
||||
}
|
||||
|
||||
type BatchElem struct {
|
||||
Method string
|
||||
Args []interface{}
|
||||
Result interface{}
|
||||
Error error
|
||||
}
|
||||
|
||||
func NewRpcClient(client *rpc.Client, ipcPath string) RpcClient {
|
||||
return RpcClient{
|
||||
client: client,
|
||||
ipcPath: ipcPath,
|
||||
}
|
||||
}
|
||||
|
||||
func (client RpcClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
|
||||
//If an empty interface (or other nil object) is passed to CallContext, when the JSONRPC message is created the params will
|
||||
//be interpreted as [null]. This seems to work fine for most of the ethereum clients (which presumably ignore a null parameter.
|
||||
//Ganache however does not ignore it, and throws an 'Incorrect number of arguments' error.
|
||||
if args == nil {
|
||||
return client.client.CallContext(ctx, result, method)
|
||||
} else {
|
||||
return client.client.CallContext(ctx, result, method, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func (client RpcClient) IpcPath() string {
|
||||
return client.ipcPath
|
||||
}
|
||||
|
||||
func (client RpcClient) SupportedModules() (map[string]string, error) {
|
||||
return client.client.SupportedModules()
|
||||
}
|
||||
|
||||
func (client RpcClient) BatchCall(batch []BatchElem) error {
|
||||
var rpcBatch []rpc.BatchElem
|
||||
for _, batchElem := range batch {
|
||||
var newBatchElem = rpc.BatchElem{
|
||||
Result: batchElem.Result,
|
||||
Method: batchElem.Method,
|
||||
Args: batchElem.Args,
|
||||
Error: batchElem.Error,
|
||||
}
|
||||
|
||||
rpcBatch = append(rpcBatch, newBatchElem)
|
||||
}
|
||||
return client.client.BatchCall(rpcBatch)
|
||||
}
|
||||
|
||||
// Subscribe subscribes to an rpc "namespace_subscribe" subscription with the given channel
|
||||
// The first argument needs to be the method we wish to invoke
|
||||
func (client RpcClient) Subscribe(namespace string, payloadChan interface{}, args ...interface{}) (*rpc.ClientSubscription, error) {
|
||||
chanVal := reflect.ValueOf(payloadChan)
|
||||
if chanVal.Kind() != reflect.Chan || chanVal.Type().ChanDir()&reflect.SendDir == 0 {
|
||||
return nil, errors.New("second argument to Subscribe must be a writable channel")
|
||||
}
|
||||
if chanVal.IsNil() {
|
||||
return nil, errors.New("channel given to Subscribe must not be nil")
|
||||
}
|
||||
return client.client.Subscribe(context.Background(), namespace, payloadChan, args...)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_import_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestColdImport(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "ColdImport Suite")
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_import
|
||||
|
||||
import (
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/ethereum"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
)
|
||||
|
||||
type ColdImporter struct {
|
||||
blockRepository datastore.BlockRepository
|
||||
converter common.BlockConverter
|
||||
ethDB ethereum.Database
|
||||
receiptRepository datastore.FullSyncReceiptRepository
|
||||
}
|
||||
|
||||
func NewColdImporter(ethDB ethereum.Database, blockRepository datastore.BlockRepository, receiptRepository datastore.FullSyncReceiptRepository, converter common.BlockConverter) *ColdImporter {
|
||||
return &ColdImporter{
|
||||
blockRepository: blockRepository,
|
||||
converter: converter,
|
||||
ethDB: ethDB,
|
||||
receiptRepository: receiptRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (ci *ColdImporter) Execute(startingBlockNumber int64, endingBlockNumber int64, nodeId string) error {
|
||||
missingBlocks := ci.blockRepository.MissingBlockNumbers(startingBlockNumber, endingBlockNumber, nodeId)
|
||||
for _, n := range missingBlocks {
|
||||
hash := ci.ethDB.GetBlockHash(n)
|
||||
|
||||
blockId, err := ci.createBlocksAndTransactions(hash, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ci.createReceiptsAndLogs(hash, n, blockId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
ci.blockRepository.SetBlocksStatus(endingBlockNumber)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ci *ColdImporter) createBlocksAndTransactions(hash []byte, i int64) (int64, error) {
|
||||
block := ci.ethDB.GetBlock(hash, i)
|
||||
coreBlock, err := ci.converter.ToCoreBlock(block)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return ci.blockRepository.CreateOrUpdateBlock(coreBlock)
|
||||
}
|
||||
|
||||
func (ci *ColdImporter) createReceiptsAndLogs(hash []byte, number int64, blockId int64) error {
|
||||
receipts := ci.ethDB.GetBlockReceipts(hash, number)
|
||||
coreReceipts, err := common.ToCoreReceipts(receipts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ci.receiptRepository.CreateReceiptsAndLogs(blockId, coreReceipts)
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_import_test
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/cold_import"
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("Geth cold importer", func() {
|
||||
var fakeGethBlock *types.Block
|
||||
|
||||
BeforeEach(func() {
|
||||
header := &types.Header{}
|
||||
transactions := []*types.Transaction{}
|
||||
uncles := []*types.Header{}
|
||||
receipts := []*types.Receipt{}
|
||||
fakeGethBlock = types.NewBlock(header, transactions, uncles, receipts)
|
||||
})
|
||||
|
||||
It("only populates missing blocks", func() {
|
||||
mockEthereumDatabase := fakes.NewMockEthereumDatabase()
|
||||
mockBlockRepository := fakes.NewMockBlockRepository()
|
||||
mockReceiptRepository := fakes.NewMockReceiptRepository()
|
||||
mockTransactionConverter := fakes.NewMockTransactionConverter()
|
||||
blockConverter := vulcCommon.NewBlockConverter(mockTransactionConverter)
|
||||
|
||||
nodeId := "node_id"
|
||||
startingBlockNumber := int64(120)
|
||||
missingBlockNumber := int64(123)
|
||||
endingBlockNumber := int64(125)
|
||||
fakeHash := []byte{1, 2, 3, 4, 5}
|
||||
mockBlockRepository.SetMissingBlockNumbersReturnArray([]int64{missingBlockNumber})
|
||||
mockEthereumDatabase.SetReturnHash(fakeHash)
|
||||
mockEthereumDatabase.SetReturnBlock(fakeGethBlock)
|
||||
importer := cold_import.NewColdImporter(mockEthereumDatabase, mockBlockRepository, mockReceiptRepository, blockConverter)
|
||||
|
||||
err := importer.Execute(startingBlockNumber, endingBlockNumber, nodeId)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
mockBlockRepository.AssertMissingBlockNumbersCalledWith(startingBlockNumber, endingBlockNumber, nodeId)
|
||||
mockEthereumDatabase.AssertGetBlockHashCalledWith(missingBlockNumber)
|
||||
mockEthereumDatabase.AssertGetBlockCalledWith(fakeHash, missingBlockNumber)
|
||||
})
|
||||
|
||||
It("fetches missing blocks from level db and persists them to pg", func() {
|
||||
mockEthereumDatabase := fakes.NewMockEthereumDatabase()
|
||||
mockBlockRepository := fakes.NewMockBlockRepository()
|
||||
mockReceiptRepository := fakes.NewMockReceiptRepository()
|
||||
mockTransactionConverter := fakes.NewMockTransactionConverter()
|
||||
blockConverter := vulcCommon.NewBlockConverter(mockTransactionConverter)
|
||||
|
||||
blockNumber := int64(123)
|
||||
fakeHash := []byte{1, 2, 3, 4, 5}
|
||||
mockBlockRepository.SetMissingBlockNumbersReturnArray([]int64{blockNumber})
|
||||
mockEthereumDatabase.SetReturnHash(fakeHash)
|
||||
mockEthereumDatabase.SetReturnBlock(fakeGethBlock)
|
||||
importer := cold_import.NewColdImporter(mockEthereumDatabase, mockBlockRepository, mockReceiptRepository, blockConverter)
|
||||
|
||||
err := importer.Execute(blockNumber, blockNumber, "node_id")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
mockEthereumDatabase.AssertGetBlockHashCalledWith(blockNumber)
|
||||
mockEthereumDatabase.AssertGetBlockCalledWith(fakeHash, blockNumber)
|
||||
Expect(mockTransactionConverter.ConvertBlockTransactionsToCoreCalled).To(BeTrue())
|
||||
Expect(mockTransactionConverter.ConvertBlockTransactionsToCorePassedBlock).To(Equal(fakeGethBlock))
|
||||
convertedBlock, err := blockConverter.ToCoreBlock(fakeGethBlock)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockBlockRepository.AssertCreateOrUpdateBlockCalledWith(convertedBlock)
|
||||
})
|
||||
|
||||
It("sets is_final status on populated blocks", func() {
|
||||
mockEthereumDatabase := fakes.NewMockEthereumDatabase()
|
||||
mockBlockRepository := fakes.NewMockBlockRepository()
|
||||
mockReceiptRepository := fakes.NewMockReceiptRepository()
|
||||
mockTransactionConverter := fakes.NewMockTransactionConverter()
|
||||
blockConverter := vulcCommon.NewBlockConverter(mockTransactionConverter)
|
||||
|
||||
startingBlockNumber := int64(120)
|
||||
endingBlockNumber := int64(125)
|
||||
fakeHash := []byte{1, 2, 3, 4, 5}
|
||||
mockBlockRepository.SetMissingBlockNumbersReturnArray([]int64{startingBlockNumber})
|
||||
mockEthereumDatabase.SetReturnHash(fakeHash)
|
||||
mockEthereumDatabase.SetReturnBlock(fakeGethBlock)
|
||||
importer := cold_import.NewColdImporter(mockEthereumDatabase, mockBlockRepository, mockReceiptRepository, blockConverter)
|
||||
|
||||
err := importer.Execute(startingBlockNumber, endingBlockNumber, "node_id")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockBlockRepository.AssertSetBlockStatusCalledWith(endingBlockNumber)
|
||||
})
|
||||
|
||||
It("fetches receipts from level db and persists them to pg", func() {
|
||||
mockEthereumDatabase := fakes.NewMockEthereumDatabase()
|
||||
mockBlockRepository := fakes.NewMockBlockRepository()
|
||||
mockReceiptRepository := fakes.NewMockReceiptRepository()
|
||||
mockTransactionConverter := fakes.NewMockTransactionConverter()
|
||||
blockConverter := vulcCommon.NewBlockConverter(mockTransactionConverter)
|
||||
|
||||
blockNumber := int64(123)
|
||||
blockId := int64(999)
|
||||
mockBlockRepository.SetCreateOrUpdateBlockReturnVals(blockId, nil)
|
||||
fakeReceipts := types.Receipts{{}}
|
||||
mockBlockRepository.SetMissingBlockNumbersReturnArray([]int64{blockNumber})
|
||||
mockEthereumDatabase.SetReturnBlock(fakeGethBlock)
|
||||
mockEthereumDatabase.SetReturnReceipts(fakeReceipts)
|
||||
importer := cold_import.NewColdImporter(mockEthereumDatabase, mockBlockRepository, mockReceiptRepository, blockConverter)
|
||||
|
||||
err := importer.Execute(blockNumber, blockNumber, "node_id")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedReceipts, err := vulcCommon.ToCoreReceipts(fakeReceipts)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
mockReceiptRepository.AssertCreateReceiptsAndLogsCalledWith(blockId, expectedReceipts)
|
||||
})
|
||||
|
||||
It("does not fetch receipts if block already exists", func() {
|
||||
mockEthereumDatabase := fakes.NewMockEthereumDatabase()
|
||||
mockBlockRepository := fakes.NewMockBlockRepository()
|
||||
mockReceiptRepository := fakes.NewMockReceiptRepository()
|
||||
mockTransactionConverter := fakes.NewMockTransactionConverter()
|
||||
blockConverter := vulcCommon.NewBlockConverter(mockTransactionConverter)
|
||||
|
||||
blockNumber := int64(123)
|
||||
mockBlockRepository.SetMissingBlockNumbersReturnArray([]int64{})
|
||||
mockEthereumDatabase.SetReturnBlock(fakeGethBlock)
|
||||
mockBlockRepository.SetCreateOrUpdateBlockReturnVals(0, repositories.ErrBlockExists)
|
||||
importer := cold_import.NewColdImporter(mockEthereumDatabase, mockBlockRepository, mockReceiptRepository, blockConverter)
|
||||
|
||||
err := importer.Execute(blockNumber, blockNumber, "node_id")
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockReceiptRepository.AssertCreateReceiptsAndLogsNotCalled()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,84 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_import
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/crypto"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
ColdImportClientName = "LevelDbColdImport"
|
||||
ColdImportNetworkId float64 = 1
|
||||
)
|
||||
|
||||
var (
|
||||
NoChainDataErr = errors.New("Level DB path does not include chaindata extension.")
|
||||
NoGethRootErr = errors.New("Level DB path does not include root path to geth.")
|
||||
)
|
||||
|
||||
type ColdImportNodeBuilder struct {
|
||||
reader fs.Reader
|
||||
parser crypto.PublicKeyParser
|
||||
}
|
||||
|
||||
func NewColdImportNodeBuilder(reader fs.Reader, parser crypto.PublicKeyParser) ColdImportNodeBuilder {
|
||||
return ColdImportNodeBuilder{reader: reader, parser: parser}
|
||||
}
|
||||
|
||||
func (cinb ColdImportNodeBuilder) GetNode(genesisBlock []byte, levelPath string) (core.Node, error) {
|
||||
var coldNode core.Node
|
||||
nodeKeyPath, err := getNodeKeyPath(levelPath)
|
||||
if err != nil {
|
||||
return coldNode, err
|
||||
}
|
||||
nodeKey, err := cinb.reader.Read(nodeKeyPath)
|
||||
if err != nil {
|
||||
return coldNode, err
|
||||
}
|
||||
nodeId, err := cinb.parser.ParsePublicKey(string(nodeKey))
|
||||
if err != nil {
|
||||
return coldNode, err
|
||||
}
|
||||
genesisBlockHash := common.BytesToHash(genesisBlock).String()
|
||||
coldNode = core.Node{
|
||||
GenesisBlock: genesisBlockHash,
|
||||
NetworkID: ColdImportNetworkId,
|
||||
ID: nodeId,
|
||||
ClientName: ColdImportClientName,
|
||||
}
|
||||
return coldNode, nil
|
||||
}
|
||||
|
||||
func getNodeKeyPath(levelPath string) (string, error) {
|
||||
chaindataExtension := "chaindata"
|
||||
if !strings.Contains(levelPath, chaindataExtension) {
|
||||
return "", NoChainDataErr
|
||||
}
|
||||
chaindataExtensionLength := len(chaindataExtension)
|
||||
gethRootPathLength := len(levelPath) - chaindataExtensionLength
|
||||
if gethRootPathLength <= chaindataExtensionLength {
|
||||
return "", NoGethRootErr
|
||||
}
|
||||
gethRootPath := levelPath[:gethRootPathLength]
|
||||
nodeKeyPath := gethRootPath + "nodekey"
|
||||
return nodeKeyPath, nil
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_import_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/cold_import"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("Cold importer node builder", func() {
|
||||
Describe("when level path is not valid", func() {
|
||||
It("returns error if no chaindata extension", func() {
|
||||
gethPath := "path/to/geth"
|
||||
mockReader := fakes.NewMockFsReader()
|
||||
mockParser := fakes.NewMockCryptoParser()
|
||||
nodeBuilder := cold_import.NewColdImportNodeBuilder(mockReader, mockParser)
|
||||
|
||||
_, err := nodeBuilder.GetNode([]byte{1, 2, 3, 4, 5}, gethPath)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(cold_import.NoChainDataErr))
|
||||
})
|
||||
|
||||
It("returns error if no root geth path", func() {
|
||||
chaindataPath := "chaindata"
|
||||
mockReader := fakes.NewMockFsReader()
|
||||
mockParser := fakes.NewMockCryptoParser()
|
||||
nodeBuilder := cold_import.NewColdImportNodeBuilder(mockReader, mockParser)
|
||||
|
||||
_, err := nodeBuilder.GetNode([]byte{1, 2, 3, 4, 5}, chaindataPath)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(cold_import.NoGethRootErr))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("when reader fails", func() {
|
||||
It("returns err", func() {
|
||||
mockReader := fakes.NewMockFsReader()
|
||||
fakeError := errors.New("Failed")
|
||||
mockReader.SetReturnErr(fakeError)
|
||||
mockParser := fakes.NewMockCryptoParser()
|
||||
nodeBuilder := cold_import.NewColdImportNodeBuilder(mockReader, mockParser)
|
||||
|
||||
_, err := nodeBuilder.GetNode([]byte{1, 2, 3, 4, 5}, "path/to/geth/chaindata")
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakeError))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("when parser fails", func() {
|
||||
It("returns err", func() {
|
||||
mockReader := fakes.NewMockFsReader()
|
||||
mockParser := fakes.NewMockCryptoParser()
|
||||
fakeErr := errors.New("Failed")
|
||||
mockParser.SetReturnErr(fakeErr)
|
||||
nodeBuilder := cold_import.NewColdImportNodeBuilder(mockReader, mockParser)
|
||||
|
||||
_, err := nodeBuilder.GetNode([]byte{1, 2, 3, 4, 5}, "path/to/geth/chaindata")
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err).To(MatchError(fakeErr))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("when path is valid and reader and parser succeed", func() {
|
||||
It("builds a node", func() {
|
||||
fakeGenesisBlock := []byte{1, 2, 3, 4, 5}
|
||||
fakeRootGethPath := "root/path/to/geth/"
|
||||
fakeLevelPath := fakeRootGethPath + "chaindata"
|
||||
fakeNodeKeyPath := fakeRootGethPath + "nodekey"
|
||||
fakePublicKeyBytes := []byte{5, 4, 3, 2, 1}
|
||||
fakePublicKeyString := "public_key"
|
||||
mockReader := fakes.NewMockFsReader()
|
||||
mockReader.SetReturnBytes(fakePublicKeyBytes)
|
||||
mockParser := fakes.NewMockCryptoParser()
|
||||
mockParser.SetReturnVal(fakePublicKeyString)
|
||||
nodeBuilder := cold_import.NewColdImportNodeBuilder(mockReader, mockParser)
|
||||
|
||||
result, err := nodeBuilder.GetNode(fakeGenesisBlock, fakeLevelPath)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
mockReader.AssertReadCalledWith(fakeNodeKeyPath)
|
||||
mockParser.AssertParsePublicKeyCalledWith(string(fakePublicKeyBytes))
|
||||
Expect(result).NotTo(BeNil())
|
||||
Expect(result.ClientName).To(Equal(cold_import.ColdImportClientName))
|
||||
expectedGenesisBlock := common.BytesToHash(fakeGenesisBlock).String()
|
||||
Expect(result.GenesisBlock).To(Equal(expectedGenesisBlock))
|
||||
Expect(result.ID).To(Equal(fakePublicKeyString))
|
||||
Expect(result.NetworkID).To(Equal(cold_import.ColdImportNetworkId))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidStateAttribute = errors.New("invalid state attribute")
|
||||
)
|
||||
|
||||
func (blockChain *BlockChain) FetchContractData(abiJSON string, address string, method string, methodArgs []interface{}, result interface{}, blockNumber int64) error {
|
||||
parsed, err := ParseAbi(abiJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var input []byte
|
||||
if methodArgs != nil {
|
||||
input, err = parsed.Pack(method, methodArgs...)
|
||||
} else {
|
||||
input, err = parsed.Pack(method)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var bn *big.Int
|
||||
if blockNumber > 0 {
|
||||
bn = big.NewInt(blockNumber)
|
||||
}
|
||||
output, err := blockChain.callContract(address, input, bn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return parsed.Unpack(result, method, output)
|
||||
}
|
||||
|
||||
func (blockChain *BlockChain) callContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) {
|
||||
to := common.HexToAddress(contractHash)
|
||||
msg := ethereum.CallMsg{To: &to, Data: input}
|
||||
return blockChain.ethClient.CallContract(context.Background(), msg, blockNumber)
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 cold_db
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ColdDbTransactionConverter struct{}
|
||||
|
||||
func NewColdDbTransactionConverter() *ColdDbTransactionConverter {
|
||||
return &ColdDbTransactionConverter{}
|
||||
}
|
||||
|
||||
func (cdtc *ColdDbTransactionConverter) ConvertBlockTransactionsToCore(gethBlock *types.Block) ([]core.TransactionModel, error) {
|
||||
var g errgroup.Group
|
||||
coreTransactions := make([]core.TransactionModel, len(gethBlock.Transactions()))
|
||||
|
||||
for gethTransactionIndex, gethTransaction := range gethBlock.Transactions() {
|
||||
transaction := gethTransaction
|
||||
transactionIndex := uint(gethTransactionIndex)
|
||||
g.Go(func() error {
|
||||
signer := getSigner(transaction)
|
||||
sender, err := signer.Sender(transaction)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
coreTransaction := transToCoreTrans(transaction, &sender)
|
||||
coreTransactions[transactionIndex] = coreTransaction
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
return coreTransactions, err
|
||||
}
|
||||
return coreTransactions, nil
|
||||
}
|
||||
|
||||
func (cdtc *ColdDbTransactionConverter) ConvertRpcTransactionsToModels(transactions []core.RpcTransaction) ([]core.TransactionModel, error) {
|
||||
panic("converting transaction indexes to integer not supported for cold import")
|
||||
}
|
||||
|
||||
func getSigner(tx *types.Transaction) types.Signer {
|
||||
v, _, _ := tx.RawSignatureValues()
|
||||
if v.Sign() != 0 && tx.Protected() {
|
||||
return types.NewEIP155Signer(tx.ChainId())
|
||||
}
|
||||
return types.HomesteadSigner{}
|
||||
}
|
||||
|
||||
func transToCoreTrans(transaction *types.Transaction, from *common.Address) core.TransactionModel {
|
||||
return core.TransactionModel{
|
||||
Hash: transaction.Hash().Hex(),
|
||||
Nonce: transaction.Nonce(),
|
||||
To: strings.ToLower(addressToHex(transaction.To())),
|
||||
From: strings.ToLower(addressToHex(from)),
|
||||
GasLimit: transaction.Gas(),
|
||||
GasPrice: transaction.GasPrice().Int64(),
|
||||
Value: transaction.Value().String(),
|
||||
Data: transaction.Data(),
|
||||
}
|
||||
}
|
||||
|
||||
func addressToHex(to *common.Address) string {
|
||||
if to == nil {
|
||||
return ""
|
||||
}
|
||||
return to.Hex()
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
type BlockConverter struct {
|
||||
transactionConverter TransactionConverter
|
||||
}
|
||||
|
||||
func NewBlockConverter(transactionConverter TransactionConverter) BlockConverter {
|
||||
return BlockConverter{transactionConverter: transactionConverter}
|
||||
}
|
||||
|
||||
func (bc BlockConverter) ToCoreBlock(gethBlock *types.Block) (core.Block, error) {
|
||||
transactions, err := bc.transactionConverter.ConvertBlockTransactionsToCore(gethBlock)
|
||||
if err != nil {
|
||||
return core.Block{}, err
|
||||
}
|
||||
|
||||
coreBlock := core.Block{
|
||||
Difficulty: gethBlock.Difficulty().Int64(),
|
||||
ExtraData: hexutil.Encode(gethBlock.Extra()),
|
||||
GasLimit: gethBlock.GasLimit(),
|
||||
GasUsed: gethBlock.GasUsed(),
|
||||
Hash: gethBlock.Hash().Hex(),
|
||||
Miner: strings.ToLower(gethBlock.Coinbase().Hex()),
|
||||
Nonce: hexutil.Encode(gethBlock.Header().Nonce[:]),
|
||||
Number: gethBlock.Number().Int64(),
|
||||
ParentHash: gethBlock.ParentHash().Hex(),
|
||||
Size: gethBlock.Size().String(),
|
||||
Time: gethBlock.Time(),
|
||||
Transactions: transactions,
|
||||
UncleHash: gethBlock.UncleHash().Hex(),
|
||||
}
|
||||
coreBlock.Reward = CalcBlockReward(coreBlock, gethBlock.Uncles()).String()
|
||||
totalUncleReward, uncles := bc.ToCoreUncle(coreBlock, gethBlock.Uncles())
|
||||
|
||||
coreBlock.UnclesReward = totalUncleReward.String()
|
||||
coreBlock.Uncles = uncles
|
||||
return coreBlock, nil
|
||||
}
|
||||
|
||||
// Rewards for the miners of uncles is calculated as (U_n + 8 - B_n) * R / 8
|
||||
// Where U_n is the uncle block number, B_n is the parent block number and R is the static block reward at B_n
|
||||
// https://github.com/ethereum/go-ethereum/issues/1591
|
||||
// https://ethereum.stackexchange.com/questions/27172/different-uncles-reward
|
||||
// https://github.com/ethereum/homestead-guide/issues/399
|
||||
// Returns the total uncle reward and the individual processed uncles
|
||||
func (bc BlockConverter) ToCoreUncle(block core.Block, uncles []*types.Header) (*big.Int, []core.Uncle) {
|
||||
totalUncleRewards := new(big.Int)
|
||||
coreUncles := make([]core.Uncle, 0, len(uncles))
|
||||
for _, uncle := range uncles {
|
||||
thisUncleReward := calcUncleMinerReward(block.Number, uncle.Number.Int64())
|
||||
raw, _ := json.Marshal(uncle)
|
||||
coreUncle := core.Uncle{
|
||||
Miner: uncle.Coinbase.Hex(),
|
||||
Hash: uncle.Hash().Hex(),
|
||||
Raw: raw,
|
||||
Reward: thisUncleReward.String(),
|
||||
Timestamp: strconv.FormatUint(uncle.Time, 10),
|
||||
}
|
||||
coreUncles = append(coreUncles, coreUncle)
|
||||
totalUncleRewards.Add(totalUncleRewards, thisUncleReward)
|
||||
}
|
||||
return totalUncleRewards, coreUncles
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/converters/rpc"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("Conversion of GethBlock to core.Block", func() {
|
||||
|
||||
It("converts basic Block metadata", func() {
|
||||
difficulty := big.NewInt(1)
|
||||
gasLimit := uint64(100000)
|
||||
gasUsed := uint64(100000)
|
||||
miner := common.HexToAddress("0x0000000000000000000000000000000000000123")
|
||||
extraData, _ := hexutil.Decode("0xe4b883e5bda9e7a59ee4bb99e9b1bc")
|
||||
nonce := types.BlockNonce{10}
|
||||
number := int64(1)
|
||||
time := uint64(140000000)
|
||||
|
||||
header := types.Header{
|
||||
Difficulty: difficulty,
|
||||
GasLimit: uint64(gasLimit),
|
||||
GasUsed: uint64(gasUsed),
|
||||
Extra: extraData,
|
||||
Coinbase: miner,
|
||||
Nonce: nonce,
|
||||
Number: big.NewInt(number),
|
||||
ParentHash: common.Hash{64},
|
||||
Time: time,
|
||||
UncleHash: common.Hash{128},
|
||||
}
|
||||
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
|
||||
client := fakes.NewMockEthClient()
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(coreBlock.Difficulty).To(Equal(difficulty.Int64()))
|
||||
Expect(coreBlock.GasLimit).To(Equal(gasLimit))
|
||||
Expect(coreBlock.Miner).To(Equal(miner.Hex()))
|
||||
Expect(coreBlock.GasUsed).To(Equal(gasUsed))
|
||||
Expect(coreBlock.Hash).To(Equal(block.Hash().Hex()))
|
||||
Expect(coreBlock.Nonce).To(Equal(hexutil.Encode(header.Nonce[:])))
|
||||
Expect(coreBlock.Number).To(Equal(number))
|
||||
Expect(coreBlock.ParentHash).To(Equal(block.ParentHash().Hex()))
|
||||
Expect(coreBlock.ExtraData).To(Equal(hexutil.Encode(block.Extra())))
|
||||
Expect(coreBlock.Size).To(Equal(block.Size().String()))
|
||||
Expect(coreBlock.Time).To(Equal(time))
|
||||
Expect(coreBlock.UncleHash).To(Equal(block.UncleHash().Hex()))
|
||||
Expect(coreBlock.IsFinal).To(BeFalse())
|
||||
})
|
||||
|
||||
Describe("The block and uncle rewards calculations", func() {
|
||||
It("calculates block rewards for a block", func() {
|
||||
transaction := types.NewTransaction(
|
||||
uint64(226823),
|
||||
common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f"),
|
||||
big.NewInt(1080900090000000000),
|
||||
uint64(90000),
|
||||
big.NewInt(50000000000),
|
||||
[]byte{},
|
||||
)
|
||||
transactions := []*types.Transaction{transaction}
|
||||
|
||||
txHash := transaction.Hash()
|
||||
receipt := types.Receipt{
|
||||
TxHash: txHash,
|
||||
GasUsed: uint64(21000),
|
||||
CumulativeGasUsed: uint64(21000),
|
||||
}
|
||||
receipts := []*types.Receipt{&receipt}
|
||||
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceipts(receipts)
|
||||
|
||||
number := int64(1071819)
|
||||
header := types.Header{
|
||||
Number: big.NewInt(number),
|
||||
}
|
||||
uncles := []*types.Header{{Number: big.NewInt(1071817)}, {Number: big.NewInt(1071818)}}
|
||||
block := types.NewBlock(&header, transactions, uncles, []*types.Receipt{&receipt})
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
expectedBlockReward := new(big.Int)
|
||||
expectedBlockReward.SetString("5313550000000000000", 10)
|
||||
|
||||
blockReward := vulcCommon.CalcBlockReward(coreBlock, block.Uncles())
|
||||
Expect(blockReward.String()).To(Equal(expectedBlockReward.String()))
|
||||
})
|
||||
|
||||
It("calculates the uncles reward for a block", func() {
|
||||
transaction := types.NewTransaction(
|
||||
uint64(226823),
|
||||
common.HexToAddress("0x108fedb097c1dcfed441480170144d8e19bb217f"),
|
||||
big.NewInt(1080900090000000000),
|
||||
uint64(90000),
|
||||
big.NewInt(50000000000),
|
||||
[]byte{})
|
||||
transactions := []*types.Transaction{transaction}
|
||||
|
||||
receipt := types.Receipt{
|
||||
TxHash: transaction.Hash(),
|
||||
GasUsed: uint64(21000),
|
||||
CumulativeGasUsed: uint64(21000),
|
||||
}
|
||||
receipts := []*types.Receipt{&receipt}
|
||||
|
||||
header := types.Header{
|
||||
Number: big.NewInt(int64(1071819)),
|
||||
}
|
||||
uncles := []*types.Header{
|
||||
{Number: big.NewInt(1071816)},
|
||||
{Number: big.NewInt(1071817)},
|
||||
}
|
||||
block := types.NewBlock(&header, transactions, uncles, receipts)
|
||||
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceipts(receipts)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
expectedTotalReward := new(big.Int)
|
||||
expectedTotalReward.SetString("6875000000000000000", 10)
|
||||
totalReward, coreUncles := blockConverter.ToCoreUncle(coreBlock, block.Uncles())
|
||||
Expect(totalReward.String()).To(Equal(expectedTotalReward.String()))
|
||||
|
||||
Expect(len(coreUncles)).To(Equal(2))
|
||||
Expect(coreUncles[0].Reward).To(Equal("3125000000000000000"))
|
||||
Expect(coreUncles[0].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
|
||||
Expect(coreUncles[0].Hash).To(Equal("0xb629de4014b6e30cf9555ee833f1806fa0d8b8516fde194405f9c98c2deb8772"))
|
||||
Expect(coreUncles[1].Reward).To(Equal("3750000000000000000"))
|
||||
Expect(coreUncles[1].Miner).To(Equal("0x0000000000000000000000000000000000000000"))
|
||||
Expect(coreUncles[1].Hash).To(Equal("0x673f5231e4888a951e0bc8a25b5774b982e6e9e258362c21affaff6e02dd5a2b"))
|
||||
})
|
||||
|
||||
It("decreases the static block reward from 5 to 3 for blocks after block 4,269,999", func() {
|
||||
transactionOne := types.NewTransaction(
|
||||
uint64(8072),
|
||||
common.HexToAddress("0xebd17720aeb7ac5186c5dfa7bafeb0bb14c02551 "),
|
||||
big.NewInt(0),
|
||||
uint64(500000),
|
||||
big.NewInt(42000000000),
|
||||
[]byte{},
|
||||
)
|
||||
|
||||
transactionTwo := types.NewTransaction(uint64(8071),
|
||||
common.HexToAddress("0x3cdab63d764c8c5048ed5e8f0a4e95534ba7e1ea"),
|
||||
big.NewInt(0),
|
||||
uint64(500000),
|
||||
big.NewInt(42000000000),
|
||||
[]byte{})
|
||||
|
||||
transactions := []*types.Transaction{transactionOne, transactionTwo}
|
||||
|
||||
receiptOne := types.Receipt{
|
||||
TxHash: transactionOne.Hash(),
|
||||
GasUsed: uint64(297508),
|
||||
CumulativeGasUsed: uint64(0),
|
||||
}
|
||||
receiptTwo := types.Receipt{
|
||||
TxHash: transactionTwo.Hash(),
|
||||
GasUsed: uint64(297508),
|
||||
CumulativeGasUsed: uint64(0),
|
||||
}
|
||||
receipts := []*types.Receipt{&receiptOne, &receiptTwo}
|
||||
|
||||
number := int64(4370055)
|
||||
header := types.Header{
|
||||
Number: big.NewInt(number),
|
||||
}
|
||||
var uncles []*types.Header
|
||||
block := types.NewBlock(&header, transactions, uncles, receipts)
|
||||
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceipts(receipts)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
expectedRewards := new(big.Int)
|
||||
expectedRewards.SetString("3024990672000000000", 10)
|
||||
rewards := vulcCommon.CalcBlockReward(coreBlock, block.Uncles())
|
||||
Expect(rewards.String()).To(Equal(expectedRewards.String()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("the converted transactions", func() {
|
||||
It("is empty", func() {
|
||||
header := types.Header{}
|
||||
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
|
||||
client := fakes.NewMockEthClient()
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(coreBlock.Transactions)).To(Equal(0))
|
||||
})
|
||||
|
||||
It("converts a single transaction", func() {
|
||||
gethTransaction := types.NewTransaction(
|
||||
uint64(10000), common.Address{1},
|
||||
big.NewInt(10),
|
||||
uint64(5000),
|
||||
big.NewInt(3),
|
||||
hexutil.MustDecode("0xf7d8c8830000000000000000000000000000000000000000000000000000000000037788000000000000000000000000000000000000000000000000000000000003bd14"),
|
||||
)
|
||||
var rawTransaction bytes.Buffer
|
||||
encodeErr := gethTransaction.EncodeRLP(&rawTransaction)
|
||||
Expect(encodeErr).NotTo(HaveOccurred())
|
||||
|
||||
gethReceipt := &types.Receipt{
|
||||
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
|
||||
ContractAddress: common.HexToAddress("x123"),
|
||||
CumulativeGasUsed: uint64(7996119),
|
||||
GasUsed: uint64(21000),
|
||||
Logs: []*types.Log{},
|
||||
Status: uint64(1),
|
||||
TxHash: gethTransaction.Hash(),
|
||||
}
|
||||
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceipts([]*types.Receipt{gethReceipt})
|
||||
|
||||
header := types.Header{}
|
||||
block := types.NewBlock(
|
||||
&header,
|
||||
[]*types.Transaction{gethTransaction},
|
||||
[]*types.Header{},
|
||||
[]*types.Receipt{gethReceipt},
|
||||
)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(coreBlock.Transactions)).To(Equal(1))
|
||||
coreTransaction := coreBlock.Transactions[0]
|
||||
expectedData := common.FromHex("0xf7d8c8830000000000000000000000000000000000000000000000000000000000037788000000000000000000000000000000000000000000000000000000000003bd14")
|
||||
Expect(coreTransaction.Data).To(Equal(expectedData))
|
||||
Expect(coreTransaction.To).To(Equal(gethTransaction.To().Hex()))
|
||||
Expect(coreTransaction.From).To(Equal("0x0000000000000000000000000000000000000123"))
|
||||
Expect(coreTransaction.GasLimit).To(Equal(gethTransaction.Gas()))
|
||||
Expect(coreTransaction.GasPrice).To(Equal(gethTransaction.GasPrice().Int64()))
|
||||
Expect(coreTransaction.Raw).To(Equal(rawTransaction.Bytes()))
|
||||
Expect(coreTransaction.TxIndex).To(Equal(int64(0)))
|
||||
Expect(coreTransaction.Value).To(Equal(gethTransaction.Value().String()))
|
||||
Expect(coreTransaction.Nonce).To(Equal(gethTransaction.Nonce()))
|
||||
|
||||
coreReceipt := coreTransaction.Receipt
|
||||
expectedReceipt, err := vulcCommon.ToCoreReceipt(gethReceipt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(coreReceipt).To(Equal(expectedReceipt))
|
||||
})
|
||||
|
||||
It("has an empty 'To' field when transaction creates a new contract", func() {
|
||||
gethTransaction := types.NewContractCreation(
|
||||
uint64(10000),
|
||||
big.NewInt(10),
|
||||
uint64(5000),
|
||||
big.NewInt(3),
|
||||
[]byte("1234"),
|
||||
)
|
||||
|
||||
gethReceipt := &types.Receipt{
|
||||
CumulativeGasUsed: uint64(1),
|
||||
GasUsed: uint64(1),
|
||||
TxHash: gethTransaction.Hash(),
|
||||
ContractAddress: common.HexToAddress("0x1023342345"),
|
||||
}
|
||||
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceipts([]*types.Receipt{gethReceipt})
|
||||
|
||||
block := types.NewBlock(
|
||||
&types.Header{},
|
||||
[]*types.Transaction{gethTransaction},
|
||||
[]*types.Header{},
|
||||
[]*types.Receipt{gethReceipt},
|
||||
)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
coreBlock, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
coreTransaction := coreBlock.Transactions[0]
|
||||
Expect(coreTransaction.To).To(Equal(""))
|
||||
|
||||
coreReceipt := coreTransaction.Receipt
|
||||
expectedReceipt, err := vulcCommon.ToCoreReceipt(gethReceipt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(coreReceipt).To(Equal(expectedReceipt))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("transaction error handling", func() {
|
||||
var gethTransaction *types.Transaction
|
||||
var gethReceipt *types.Receipt
|
||||
var header *types.Header
|
||||
var block *types.Block
|
||||
|
||||
BeforeEach(func() {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
gethTransaction = types.NewTransaction(
|
||||
uint64(0),
|
||||
common.Address{},
|
||||
big.NewInt(0),
|
||||
uint64(0),
|
||||
big.NewInt(0),
|
||||
[]byte{},
|
||||
)
|
||||
gethReceipt = &types.Receipt{}
|
||||
header = &types.Header{}
|
||||
block = types.NewBlock(
|
||||
header,
|
||||
[]*types.Transaction{gethTransaction},
|
||||
[]*types.Header{},
|
||||
[]*types.Receipt{gethReceipt},
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
defer log.SetOutput(os.Stdout)
|
||||
})
|
||||
|
||||
It("returns an error when transaction sender call fails", func() {
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionSenderErr(fakes.FakeError)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
_, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
|
||||
It("returns an error when transaction receipt call fails", func() {
|
||||
client := fakes.NewMockEthClient()
|
||||
client.SetTransactionReceiptErr(fakes.FakeError)
|
||||
transactionConverter := rpc.NewRpcTransactionConverter(client)
|
||||
blockConverter := vulcCommon.NewBlockConverter(transactionConverter)
|
||||
|
||||
_, err := blockConverter.ToCoreBlock(block)
|
||||
|
||||
Expect(err).To(MatchError(fakes.FakeError))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1,77 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
func CalcBlockReward(block core.Block, uncles []*types.Header) *big.Int {
|
||||
staticBlockReward := staticRewardByBlockNumber(block.Number)
|
||||
transactionFees := calcTransactionFees(block)
|
||||
uncleInclusionRewards := calcUncleInclusionRewards(block, uncles)
|
||||
tmp := transactionFees.Add(transactionFees, uncleInclusionRewards)
|
||||
return tmp.Add(tmp, staticBlockReward)
|
||||
}
|
||||
|
||||
func calcUncleMinerReward(blockNumber, uncleBlockNumber int64) *big.Int {
|
||||
staticBlockReward := staticRewardByBlockNumber(blockNumber)
|
||||
rewardDiv8 := staticBlockReward.Div(staticBlockReward, big.NewInt(8))
|
||||
mainBlock := big.NewInt(blockNumber)
|
||||
uncleBlock := big.NewInt(uncleBlockNumber)
|
||||
uncleBlockPlus8 := uncleBlock.Add(uncleBlock, big.NewInt(8))
|
||||
uncleBlockPlus8MinusMainBlock := uncleBlockPlus8.Sub(uncleBlockPlus8, mainBlock)
|
||||
return rewardDiv8.Mul(rewardDiv8, uncleBlockPlus8MinusMainBlock)
|
||||
}
|
||||
|
||||
func calcTransactionFees(block core.Block) *big.Int {
|
||||
transactionFees := new(big.Int)
|
||||
for _, transaction := range block.Transactions {
|
||||
receipt := transaction.Receipt
|
||||
gasPrice := big.NewInt(transaction.GasPrice)
|
||||
gasUsed := big.NewInt(int64(receipt.GasUsed))
|
||||
transactionFee := gasPrice.Mul(gasPrice, gasUsed)
|
||||
transactionFees = transactionFees.Add(transactionFees, transactionFee)
|
||||
}
|
||||
return transactionFees
|
||||
}
|
||||
|
||||
func calcUncleInclusionRewards(block core.Block, uncles []*types.Header) *big.Int {
|
||||
uncleInclusionRewards := new(big.Int)
|
||||
for range uncles {
|
||||
staticBlockReward := staticRewardByBlockNumber(block.Number)
|
||||
staticBlockReward.Div(staticBlockReward, big.NewInt(32))
|
||||
uncleInclusionRewards.Add(uncleInclusionRewards, staticBlockReward)
|
||||
}
|
||||
return uncleInclusionRewards
|
||||
}
|
||||
|
||||
func staticRewardByBlockNumber(blockNumber int64) *big.Int {
|
||||
staticBlockReward := new(big.Int)
|
||||
//https://blog.ethereum.org/2017/10/12/byzantium-hf-announcement/
|
||||
if blockNumber >= 7280000 {
|
||||
staticBlockReward.SetString("2000000000000000000", 10)
|
||||
} else if blockNumber >= 4370000 {
|
||||
staticBlockReward.SetString("3000000000000000000", 10)
|
||||
} else {
|
||||
staticBlockReward.SetString("5000000000000000000", 10)
|
||||
}
|
||||
return staticBlockReward
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestCommon(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Common Suite")
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func ToFullSyncLogs(gethLogs []types.Log) []core.FullSyncLog {
|
||||
var logs []core.FullSyncLog
|
||||
for _, log := range gethLogs {
|
||||
log := ToCoreLog(log)
|
||||
logs = append(logs, log)
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
func makeTopics(topics []common.Hash) core.Topics {
|
||||
var hexTopics core.Topics
|
||||
for i, topic := range topics {
|
||||
hexTopics[i] = topic.Hex()
|
||||
}
|
||||
return hexTopics
|
||||
}
|
||||
|
||||
func ToCoreLog(gethLog types.Log) core.FullSyncLog {
|
||||
topics := gethLog.Topics
|
||||
hexTopics := makeTopics(topics)
|
||||
return core.FullSyncLog{
|
||||
Address: strings.ToLower(gethLog.Address.Hex()),
|
||||
BlockNumber: int64(gethLog.BlockNumber),
|
||||
Topics: hexTopics,
|
||||
TxHash: gethLog.TxHash.Hex(),
|
||||
Index: int64(gethLog.Index),
|
||||
Data: hexutil.Encode(gethLog.Data),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
)
|
||||
|
||||
var _ = Describe("Conversion of GethLog to core.FullSyncLog", func() {
|
||||
|
||||
It("converts geth log to internal log format", func() {
|
||||
gethLog := types.Log{
|
||||
Address: common.HexToAddress("0x448a5065aeBB8E423F0896E6c5D525C040f59af3"),
|
||||
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.FullSyncLog{
|
||||
Address: strings.ToLower(gethLog.Address.Hex()),
|
||||
BlockNumber: int64(gethLog.BlockNumber),
|
||||
Data: hexutil.Encode(gethLog.Data),
|
||||
TxHash: gethLog.TxHash.Hex(),
|
||||
Index: 2,
|
||||
Topics: core.Topics{
|
||||
gethLog.Topics[0].Hex(),
|
||||
gethLog.Topics[1].Hex(),
|
||||
},
|
||||
}
|
||||
|
||||
coreLog := vulcCommon.ToCoreLog(gethLog)
|
||||
|
||||
Expect(coreLog.Address).To(Equal(expected.Address))
|
||||
Expect(coreLog.BlockNumber).To(Equal(expected.BlockNumber))
|
||||
Expect(coreLog.Data).To(Equal(expected.Data))
|
||||
Expect(coreLog.Index).To(Equal(expected.Index))
|
||||
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 := vulcCommon.ToCoreLog(gethLogOne)
|
||||
expectedTwo := vulcCommon.ToCoreLog(gethLogTwo)
|
||||
|
||||
coreLogs := vulcCommon.ToFullSyncLogs([]types.Log{gethLogOne, gethLogTwo})
|
||||
|
||||
Expect(len(coreLogs)).To(Equal(2))
|
||||
Expect(coreLogs[0]).To(Equal(expectedOne))
|
||||
Expect(coreLogs[1]).To(Equal(expectedTwo))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
type HeaderConverter struct{}
|
||||
|
||||
func (converter HeaderConverter) Convert(gethHeader *types.Header, blockHash string) core.Header {
|
||||
rawHeader, err := json.Marshal(gethHeader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
coreHeader := core.Header{
|
||||
Hash: blockHash,
|
||||
BlockNumber: gethHeader.Number.Int64(),
|
||||
Raw: rawHeader,
|
||||
Timestamp: strconv.FormatUint(gethHeader.Time, 10),
|
||||
}
|
||||
return coreHeader
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
common2 "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("Block header converter", func() {
|
||||
It("converts geth header to core header", func() {
|
||||
gethHeader := &types.Header{
|
||||
Difficulty: big.NewInt(1),
|
||||
Number: big.NewInt(2),
|
||||
ParentHash: common.HexToHash("0xParent"),
|
||||
ReceiptHash: common.HexToHash("0xReceipt"),
|
||||
Root: common.HexToHash("0xRoot"),
|
||||
Time: uint64(123456789),
|
||||
TxHash: common.HexToHash("0xTransaction"),
|
||||
UncleHash: common.HexToHash("0xUncle"),
|
||||
}
|
||||
converter := common2.HeaderConverter{}
|
||||
hash := fakes.FakeHash.String()
|
||||
|
||||
coreHeader := converter.Convert(gethHeader, hash)
|
||||
|
||||
Expect(coreHeader.BlockNumber).To(Equal(gethHeader.Number.Int64()))
|
||||
Expect(coreHeader.Hash).To(Equal(hash))
|
||||
Expect(coreHeader.Timestamp).To(Equal(strconv.FormatUint(gethHeader.Time, 10)))
|
||||
})
|
||||
|
||||
It("includes raw bytes for header as JSON", func() {
|
||||
gethHeader := types.Header{Number: big.NewInt(123)}
|
||||
converter := common2.HeaderConverter{}
|
||||
|
||||
coreHeader := converter.Convert(&gethHeader, fakes.FakeHash.String())
|
||||
|
||||
expectedJSON, err := json.Marshal(gethHeader)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(coreHeader.Raw).To(Equal(expectedJSON))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,89 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func ToCoreReceipts(gethReceipts types.Receipts) ([]core.Receipt, error) {
|
||||
var coreReceipts []core.Receipt
|
||||
for _, receipt := range gethReceipts {
|
||||
coreReceipt, err := ToCoreReceipt(receipt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coreReceipts = append(coreReceipts, coreReceipt)
|
||||
}
|
||||
return coreReceipts, nil
|
||||
}
|
||||
|
||||
func ToCoreReceipt(gethReceipt *types.Receipt) (core.Receipt, error) {
|
||||
bloom := hexutil.Encode(gethReceipt.Bloom.Bytes())
|
||||
var postState string
|
||||
var status int
|
||||
postState, status = postStateOrStatus(gethReceipt)
|
||||
logs := dereferenceLogs(gethReceipt)
|
||||
contractAddress := setContractAddress(gethReceipt)
|
||||
|
||||
rlpBuff := new(bytes.Buffer)
|
||||
receiptForStorage := types.ReceiptForStorage(*gethReceipt)
|
||||
err := receiptForStorage.EncodeRLP(rlpBuff)
|
||||
if err != nil {
|
||||
return core.Receipt{}, err
|
||||
}
|
||||
return core.Receipt{
|
||||
Bloom: bloom,
|
||||
ContractAddress: contractAddress,
|
||||
CumulativeGasUsed: gethReceipt.CumulativeGasUsed,
|
||||
GasUsed: gethReceipt.GasUsed,
|
||||
Logs: logs,
|
||||
StateRoot: postState,
|
||||
TxHash: gethReceipt.TxHash.Hex(),
|
||||
Status: status,
|
||||
Rlp: rlpBuff.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContractAddress(gethReceipt *types.Receipt) string {
|
||||
emptyAddress := common.Address{}.Bytes()
|
||||
if bytes.Equal(gethReceipt.ContractAddress.Bytes(), emptyAddress) {
|
||||
return ""
|
||||
}
|
||||
return gethReceipt.ContractAddress.Hex()
|
||||
}
|
||||
|
||||
func dereferenceLogs(gethReceipt *types.Receipt) []core.FullSyncLog {
|
||||
logs := []core.FullSyncLog{}
|
||||
for _, log := range gethReceipt.Logs {
|
||||
logs = append(logs, ToCoreLog(*log))
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
func postStateOrStatus(gethReceipts *types.Receipt) (string, int) {
|
||||
if len(gethReceipts.PostState) != 0 {
|
||||
return hexutil.Encode(gethReceipts.PostState), -99
|
||||
}
|
||||
return "", int(gethReceipts.Status)
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
)
|
||||
|
||||
var _ = Describe("Conversion of GethReceipt to core.Receipt", func() {
|
||||
|
||||
It(`converts geth receipt to internal receipt format (pre Byzantium has post-transaction stateroot)`, func() {
|
||||
receipt := types.Receipt{
|
||||
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
|
||||
ContractAddress: common.Address{},
|
||||
CumulativeGasUsed: uint64(25000),
|
||||
GasUsed: uint64(21000),
|
||||
Logs: []*types.Log{},
|
||||
PostState: hexutil.MustDecode("0x88abf7e73128227370aa7baa3dd4e18d0af70e92ef1f9ef426942fbe2dddb733"),
|
||||
TxHash: common.HexToHash("0x97d99bc7729211111a21b12c933c949d4f31684f1d6954ff477d0477538ff017"),
|
||||
}
|
||||
|
||||
rlpBuff := new(bytes.Buffer)
|
||||
receiptForStorage := types.ReceiptForStorage(receipt)
|
||||
err := receiptForStorage.EncodeRLP(rlpBuff)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
expected := core.Receipt{
|
||||
Bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
ContractAddress: "",
|
||||
CumulativeGasUsed: 25000,
|
||||
GasUsed: 21000,
|
||||
Logs: []core.FullSyncLog{},
|
||||
StateRoot: "0x88abf7e73128227370aa7baa3dd4e18d0af70e92ef1f9ef426942fbe2dddb733",
|
||||
Status: -99,
|
||||
TxHash: receipt.TxHash.Hex(),
|
||||
Rlp: rlpBuff.Bytes(),
|
||||
}
|
||||
|
||||
coreReceipt, err := vulcCommon.ToCoreReceipt(&receipt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(coreReceipt.Bloom).To(Equal(expected.Bloom))
|
||||
Expect(coreReceipt.ContractAddress).To(Equal(expected.ContractAddress))
|
||||
Expect(coreReceipt.CumulativeGasUsed).To(Equal(expected.CumulativeGasUsed))
|
||||
Expect(coreReceipt.GasUsed).To(Equal(expected.GasUsed))
|
||||
Expect(coreReceipt.Logs).To(Equal(expected.Logs))
|
||||
Expect(coreReceipt.StateRoot).To(Equal(expected.StateRoot))
|
||||
Expect(coreReceipt.Status).To(Equal(expected.Status))
|
||||
Expect(coreReceipt.TxHash).To(Equal(expected.TxHash))
|
||||
Expect(bytes.Compare(coreReceipt.Rlp, expected.Rlp)).To(Equal(0))
|
||||
})
|
||||
|
||||
It("converts geth receipt to internal receipt format (post Byzantium has status", func() {
|
||||
receipt := types.Receipt{
|
||||
Bloom: types.BytesToBloom(hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")),
|
||||
ContractAddress: common.HexToAddress("x0123"),
|
||||
CumulativeGasUsed: uint64(7996119),
|
||||
GasUsed: uint64(21000),
|
||||
Logs: []*types.Log{},
|
||||
Status: uint64(1),
|
||||
TxHash: common.HexToHash("0xe340558980f89d5f86045ac11e5cc34e4bcec20f9f1e2a427aa39d87114e8223"),
|
||||
}
|
||||
|
||||
rlpBuff := new(bytes.Buffer)
|
||||
receiptForStorage := types.ReceiptForStorage(receipt)
|
||||
err := receiptForStorage.EncodeRLP(rlpBuff)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
expected := core.Receipt{
|
||||
Bloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
ContractAddress: receipt.ContractAddress.Hex(),
|
||||
CumulativeGasUsed: 7996119,
|
||||
GasUsed: 21000,
|
||||
Logs: []core.FullSyncLog{},
|
||||
StateRoot: "",
|
||||
Status: 1,
|
||||
TxHash: receipt.TxHash.Hex(),
|
||||
Rlp: rlpBuff.Bytes(),
|
||||
}
|
||||
|
||||
coreReceipt, err := vulcCommon.ToCoreReceipt(&receipt)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(coreReceipt.Bloom).To(Equal(expected.Bloom))
|
||||
Expect(coreReceipt.ContractAddress).To(Equal(""))
|
||||
Expect(coreReceipt.CumulativeGasUsed).To(Equal(expected.CumulativeGasUsed))
|
||||
Expect(coreReceipt.GasUsed).To(Equal(expected.GasUsed))
|
||||
Expect(coreReceipt.Logs).To(Equal(expected.Logs))
|
||||
Expect(coreReceipt.StateRoot).To(Equal(expected.StateRoot))
|
||||
Expect(coreReceipt.Status).To(Equal(expected.Status))
|
||||
Expect(coreReceipt.TxHash).To(Equal(expected.TxHash))
|
||||
Expect(bytes.Compare(coreReceipt.Rlp, expected.Rlp)).To(Equal(0))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 common
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
)
|
||||
|
||||
type TransactionConverter interface {
|
||||
ConvertBlockTransactionsToCore(gethBlock *types.Block) ([]core.TransactionModel, error)
|
||||
ConvertRpcTransactionsToModels(transactions []core.RpcTransaction) ([]core.TransactionModel, error)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package rpc_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestRpc(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Rpc Suite")
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 rpc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"log"
|
||||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
vulcCommon "github.com/vulcanize/vulcanizedb/pkg/eth/converters/common"
|
||||
)
|
||||
|
||||
type RpcTransactionConverter struct {
|
||||
client core.EthClient
|
||||
}
|
||||
|
||||
// raw transaction data, required for generating RLP
|
||||
type transactionData struct {
|
||||
AccountNonce uint64
|
||||
Price *big.Int
|
||||
GasLimit uint64
|
||||
Recipient *common.Address `rlp:"nil"` // nil means contract creation
|
||||
Amount *big.Int
|
||||
Payload []byte
|
||||
V *big.Int
|
||||
R *big.Int
|
||||
S *big.Int
|
||||
}
|
||||
|
||||
func NewRpcTransactionConverter(client core.EthClient) *RpcTransactionConverter {
|
||||
return &RpcTransactionConverter{client: client}
|
||||
}
|
||||
|
||||
func (converter *RpcTransactionConverter) ConvertRpcTransactionsToModels(transactions []core.RpcTransaction) ([]core.TransactionModel, error) {
|
||||
var results []core.TransactionModel
|
||||
for _, transaction := range transactions {
|
||||
txData, convertErr := getTransactionData(transaction)
|
||||
if convertErr != nil {
|
||||
return nil, convertErr
|
||||
}
|
||||
txRLP, rlpErr := getTransactionRLP(txData)
|
||||
if rlpErr != nil {
|
||||
return nil, rlpErr
|
||||
}
|
||||
txIndex, txIndexErr := hexToBigInt(transaction.TransactionIndex)
|
||||
if txIndexErr != nil {
|
||||
return nil, txIndexErr
|
||||
}
|
||||
transactionModel := core.TransactionModel{
|
||||
Data: txData.Payload,
|
||||
From: transaction.From,
|
||||
GasLimit: txData.GasLimit,
|
||||
GasPrice: txData.Price.Int64(),
|
||||
Hash: transaction.Hash,
|
||||
Nonce: txData.AccountNonce,
|
||||
Raw: txRLP,
|
||||
// NOTE: Header Sync transactions don't include receipt; would require separate RPC call
|
||||
To: transaction.Recipient,
|
||||
TxIndex: txIndex.Int64(),
|
||||
Value: txData.Amount.String(),
|
||||
}
|
||||
results = append(results, transactionModel)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (converter *RpcTransactionConverter) ConvertBlockTransactionsToCore(gethBlock *types.Block) ([]core.TransactionModel, error) {
|
||||
var g errgroup.Group
|
||||
coreTransactions := make([]core.TransactionModel, len(gethBlock.Transactions()))
|
||||
|
||||
for gethTransactionIndex, gethTransaction := range gethBlock.Transactions() {
|
||||
//https://golang.org/doc/faq#closures_and_goroutines
|
||||
transaction := gethTransaction
|
||||
transactionIndex := uint(gethTransactionIndex)
|
||||
g.Go(func() error {
|
||||
from, err := converter.client.TransactionSender(context.Background(), transaction, gethBlock.Hash(), transactionIndex)
|
||||
if err != nil {
|
||||
log.Println("transaction sender: ", err)
|
||||
return err
|
||||
}
|
||||
coreTransaction, convertErr := convertGethTransactionToModel(transaction, &from, int64(gethTransactionIndex))
|
||||
if convertErr != nil {
|
||||
return convertErr
|
||||
}
|
||||
coreTransaction, err = converter.appendReceiptToTransaction(coreTransaction)
|
||||
if err != nil {
|
||||
log.Println("receipt: ", err)
|
||||
return err
|
||||
}
|
||||
coreTransactions[transactionIndex] = coreTransaction
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := g.Wait(); err != nil {
|
||||
log.Println("transactions: ", err)
|
||||
return coreTransactions, err
|
||||
}
|
||||
return coreTransactions, nil
|
||||
}
|
||||
|
||||
func (rtc *RpcTransactionConverter) appendReceiptToTransaction(transaction core.TransactionModel) (core.TransactionModel, error) {
|
||||
gethReceipt, err := rtc.client.TransactionReceipt(context.Background(), common.HexToHash(transaction.Hash))
|
||||
if err != nil {
|
||||
return transaction, err
|
||||
}
|
||||
receipt, err := vulcCommon.ToCoreReceipt(gethReceipt)
|
||||
if err != nil {
|
||||
return transaction, err
|
||||
}
|
||||
transaction.Receipt = receipt
|
||||
return transaction, nil
|
||||
}
|
||||
|
||||
func convertGethTransactionToModel(transaction *types.Transaction, from *common.Address, transactionIndex int64) (core.TransactionModel, error) {
|
||||
raw := bytes.Buffer{}
|
||||
encodeErr := transaction.EncodeRLP(&raw)
|
||||
if encodeErr != nil {
|
||||
return core.TransactionModel{}, encodeErr
|
||||
}
|
||||
return core.TransactionModel{
|
||||
Data: transaction.Data(),
|
||||
From: strings.ToLower(addressToHex(from)),
|
||||
GasLimit: transaction.Gas(),
|
||||
GasPrice: transaction.GasPrice().Int64(),
|
||||
Hash: transaction.Hash().Hex(),
|
||||
Nonce: transaction.Nonce(),
|
||||
Raw: raw.Bytes(),
|
||||
To: strings.ToLower(addressToHex(transaction.To())),
|
||||
TxIndex: transactionIndex,
|
||||
Value: transaction.Value().String(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTransactionData(transaction core.RpcTransaction) (transactionData, error) {
|
||||
nonce, nonceErr := hexToBigInt(transaction.Nonce)
|
||||
if nonceErr != nil {
|
||||
return transactionData{}, nonceErr
|
||||
}
|
||||
gasPrice, gasPriceErr := hexToBigInt(transaction.GasPrice)
|
||||
if gasPriceErr != nil {
|
||||
return transactionData{}, gasPriceErr
|
||||
}
|
||||
gasLimit, gasLimitErr := hexToBigInt(transaction.GasLimit)
|
||||
if gasLimitErr != nil {
|
||||
return transactionData{}, gasLimitErr
|
||||
}
|
||||
recipient := common.HexToAddress(transaction.Recipient)
|
||||
amount, amountErr := hexToBigInt(transaction.Amount)
|
||||
if amountErr != nil {
|
||||
return transactionData{}, amountErr
|
||||
}
|
||||
v, vErr := hexToBigInt(transaction.V)
|
||||
if vErr != nil {
|
||||
return transactionData{}, vErr
|
||||
}
|
||||
r, rErr := hexToBigInt(transaction.R)
|
||||
if rErr != nil {
|
||||
return transactionData{}, rErr
|
||||
}
|
||||
s, sErr := hexToBigInt(transaction.S)
|
||||
if sErr != nil {
|
||||
return transactionData{}, sErr
|
||||
}
|
||||
return transactionData{
|
||||
AccountNonce: nonce.Uint64(),
|
||||
Price: gasPrice,
|
||||
GasLimit: gasLimit.Uint64(),
|
||||
Recipient: &recipient,
|
||||
Amount: amount,
|
||||
Payload: hexutil.MustDecode(transaction.Payload),
|
||||
V: v,
|
||||
R: r,
|
||||
S: s,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTransactionRLP(txData transactionData) ([]byte, error) {
|
||||
transactionRlp := bytes.Buffer{}
|
||||
encodeErr := rlp.Encode(&transactionRlp, txData)
|
||||
if encodeErr != nil {
|
||||
return nil, encodeErr
|
||||
}
|
||||
return transactionRlp.Bytes(), nil
|
||||
}
|
||||
|
||||
func addressToHex(to *common.Address) string {
|
||||
if to == nil {
|
||||
return ""
|
||||
}
|
||||
return to.Hex()
|
||||
}
|
||||
|
||||
func hexToBigInt(hex string) (*big.Int, error) {
|
||||
result := big.NewInt(0)
|
||||
_, scanErr := fmt.Sscan(hex, result)
|
||||
if scanErr != nil {
|
||||
return nil, scanErr
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 rpc_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/converters/rpc"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var _ = Describe("RPC transaction converter", func() {
|
||||
var converter rpc.RpcTransactionConverter
|
||||
|
||||
BeforeEach(func() {
|
||||
converter = rpc.RpcTransactionConverter{}
|
||||
})
|
||||
|
||||
It("converts hex fields to integers", func() {
|
||||
rpcTransaction := getFakeRpcTransaction("0x1")
|
||||
|
||||
transactionModels, err := converter.ConvertRpcTransactionsToModels([]core.RpcTransaction{rpcTransaction})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(transactionModels)).To(Equal(1))
|
||||
Expect(transactionModels[0].GasLimit).To(Equal(uint64(1)))
|
||||
Expect(transactionModels[0].GasPrice).To(Equal(int64(1)))
|
||||
Expect(transactionModels[0].Nonce).To(Equal(uint64(1)))
|
||||
Expect(transactionModels[0].TxIndex).To(Equal(int64(1)))
|
||||
Expect(transactionModels[0].Value).To(Equal("1"))
|
||||
})
|
||||
|
||||
It("returns error if invalid hex cannot be converted", func() {
|
||||
invalidTransaction := getFakeRpcTransaction("invalid")
|
||||
|
||||
_, err := converter.ConvertRpcTransactionsToModels([]core.RpcTransaction{invalidTransaction})
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("copies RPC transaction hash, from, and to values to model", func() {
|
||||
rpcTransaction := getFakeRpcTransaction("0x1")
|
||||
|
||||
transactionModels, err := converter.ConvertRpcTransactionsToModels([]core.RpcTransaction{rpcTransaction})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(transactionModels)).To(Equal(1))
|
||||
Expect(transactionModels[0].Hash).To(Equal(rpcTransaction.Hash))
|
||||
Expect(transactionModels[0].From).To(Equal(rpcTransaction.From))
|
||||
Expect(transactionModels[0].To).To(Equal(rpcTransaction.Recipient))
|
||||
})
|
||||
|
||||
It("derives transaction RLP", func() {
|
||||
// actual transaction: https://kovan.etherscan.io/tx/0x3b29ef265425d304069c57e5145cd1c7558568b06d231775f50a693bee1aad4f
|
||||
rpcTransaction := core.RpcTransaction{
|
||||
Nonce: "0x7aa9",
|
||||
GasPrice: "0x3b9aca00",
|
||||
GasLimit: "0x7a120",
|
||||
Recipient: "0xf88bbdc1e2718f8857f30a180076ec38d53cf296",
|
||||
Amount: "0x0",
|
||||
Payload: "0x18178358",
|
||||
V: "0x78",
|
||||
R: "0x79f6a78ababfdb37b87a4d52795a49b08b5b5171443d1f2fb8f373431e77439c",
|
||||
S: "0x3f1a210dd3b59d161735a314b88568fa91552dfe207c00a2fdbcd52ccb081409",
|
||||
Hash: "0x3b29ef265425d304069c57e5145cd1c7558568b06d231775f50a693bee1aad4f",
|
||||
From: "0x694032e172d9b0ee6aff5d36749bad4947a36e4e",
|
||||
TransactionIndex: "0xa",
|
||||
}
|
||||
|
||||
transactionModels, err := converter.ConvertRpcTransactionsToModels([]core.RpcTransaction{rpcTransaction})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(transactionModels)).To(Equal(1))
|
||||
model := transactionModels[0]
|
||||
expectedRLP := []byte{248, 106, 130, 122, 169, 132, 59, 154, 202, 0, 131, 7, 161, 32, 148, 248, 139, 189, 193,
|
||||
226, 113, 143, 136, 87, 243, 10, 24, 0, 118, 236, 56, 213, 60, 242, 150, 128, 132, 24, 23, 131, 88, 120, 160,
|
||||
121, 246, 167, 138, 186, 191, 219, 55, 184, 122, 77, 82, 121, 90, 73, 176, 139, 91, 81, 113, 68, 61, 31, 47,
|
||||
184, 243, 115, 67, 30, 119, 67, 156, 160, 63, 26, 33, 13, 211, 181, 157, 22, 23, 53, 163, 20, 184, 133, 104,
|
||||
250, 145, 85, 45, 254, 32, 124, 0, 162, 253, 188, 213, 44, 203, 8, 20, 9}
|
||||
Expect(model.Raw).To(Equal(expectedRLP))
|
||||
})
|
||||
|
||||
It("does not include transaction receipt", func() {
|
||||
rpcTransaction := getFakeRpcTransaction("0x1")
|
||||
|
||||
transactionModels, err := converter.ConvertRpcTransactionsToModels([]core.RpcTransaction{rpcTransaction})
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(transactionModels)).To(Equal(1))
|
||||
Expect(transactionModels[0].Receipt).To(Equal(core.Receipt{}))
|
||||
})
|
||||
})
|
||||
|
||||
func getFakeRpcTransaction(hex string) core.RpcTransaction {
|
||||
return core.RpcTransaction{
|
||||
Hash: "0x2",
|
||||
Amount: hex,
|
||||
GasLimit: hex,
|
||||
GasPrice: hex,
|
||||
Nonce: hex,
|
||||
From: fakes.FakeAddress.Hex(),
|
||||
Recipient: fakes.FakeAddress.Hex(),
|
||||
V: "0x2",
|
||||
R: "0x2",
|
||||
S: "0x2",
|
||||
Payload: "0x12",
|
||||
TransactionIndex: hex,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 eth_test
|
||||
|
||||
import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGeth(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "eth Suite")
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 node
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"strconv"
|
||||
|
||||
"regexp"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/p2p"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type IPropertiesReader interface {
|
||||
NodeInfo() (id string, name string)
|
||||
NetworkId() float64
|
||||
GenesisBlock() string
|
||||
}
|
||||
|
||||
type PropertiesReader struct {
|
||||
client core.RpcClient
|
||||
}
|
||||
|
||||
type ParityClient struct {
|
||||
PropertiesReader
|
||||
}
|
||||
|
||||
type GethClient struct {
|
||||
PropertiesReader
|
||||
}
|
||||
|
||||
type InfuraClient struct {
|
||||
PropertiesReader
|
||||
}
|
||||
|
||||
type GanacheClient struct {
|
||||
PropertiesReader
|
||||
}
|
||||
|
||||
func MakeNode(rpcClient core.RpcClient) core.Node {
|
||||
pr := makePropertiesReader(rpcClient)
|
||||
id, name := pr.NodeInfo()
|
||||
return core.Node{
|
||||
GenesisBlock: pr.GenesisBlock(),
|
||||
NetworkID: pr.NetworkId(),
|
||||
ID: id,
|
||||
ClientName: name,
|
||||
}
|
||||
}
|
||||
|
||||
func makePropertiesReader(client core.RpcClient) IPropertiesReader {
|
||||
switch getNodeType(client) {
|
||||
case core.GETH:
|
||||
return GethClient{PropertiesReader: PropertiesReader{client: client}}
|
||||
case core.PARITY:
|
||||
return ParityClient{PropertiesReader: PropertiesReader{client: client}}
|
||||
case core.INFURA:
|
||||
return InfuraClient{PropertiesReader: PropertiesReader{client: client}}
|
||||
case core.GANACHE:
|
||||
return GanacheClient{PropertiesReader: PropertiesReader{client: client}}
|
||||
default:
|
||||
return PropertiesReader{client: client}
|
||||
}
|
||||
}
|
||||
|
||||
func getNodeType(client core.RpcClient) core.NodeType {
|
||||
if strings.Contains(client.IpcPath(), "infura") {
|
||||
return core.INFURA
|
||||
}
|
||||
if strings.Contains(client.IpcPath(), "127.0.0.1") || strings.Contains(client.IpcPath(), "localhost") {
|
||||
return core.GANACHE
|
||||
}
|
||||
modules, _ := client.SupportedModules()
|
||||
if _, ok := modules["admin"]; ok {
|
||||
return core.GETH
|
||||
}
|
||||
return core.PARITY
|
||||
}
|
||||
|
||||
func (reader PropertiesReader) NetworkId() float64 {
|
||||
var version string
|
||||
err := reader.client.CallContext(context.Background(), &version, "net_version")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
networkId, _ := strconv.ParseFloat(version, 64)
|
||||
return networkId
|
||||
}
|
||||
|
||||
func (reader PropertiesReader) GenesisBlock() string {
|
||||
var header *types.Header
|
||||
blockZero := "0x0"
|
||||
includeTransactions := false
|
||||
reader.client.CallContext(context.Background(), &header, "eth_getBlockByNumber", blockZero, includeTransactions)
|
||||
return header.Hash().Hex()
|
||||
}
|
||||
|
||||
func (reader PropertiesReader) NodeInfo() (string, string) {
|
||||
var info p2p.NodeInfo
|
||||
reader.client.CallContext(context.Background(), &info, "admin_nodeInfo")
|
||||
return info.ID, info.Name
|
||||
}
|
||||
|
||||
func (client ParityClient) NodeInfo() (string, string) {
|
||||
nodeInfo := client.parityNodeInfo()
|
||||
id := client.parityID()
|
||||
return id, nodeInfo
|
||||
}
|
||||
|
||||
func (client InfuraClient) NodeInfo() (string, string) {
|
||||
return "infura", "infura"
|
||||
}
|
||||
|
||||
func (client GanacheClient) NodeInfo() (string, string) {
|
||||
return "ganache", "ganache"
|
||||
}
|
||||
|
||||
func (client ParityClient) parityNodeInfo() string {
|
||||
var nodeInfo core.ParityNodeInfo
|
||||
client.client.CallContext(context.Background(), &nodeInfo, "parity_versionInfo")
|
||||
return nodeInfo.String()
|
||||
}
|
||||
|
||||
func (client ParityClient) parityID() string {
|
||||
var enodeId = regexp.MustCompile(`^enode://(.+)@.+$`)
|
||||
var enodeURL string
|
||||
client.client.CallContext(context.Background(), &enodeURL, "parity_enode")
|
||||
enode := enodeId.FindStringSubmatch(enodeURL)
|
||||
if len(enode) < 2 {
|
||||
return ""
|
||||
}
|
||||
return enode[1]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 node_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestNode(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Node Suite")
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 node_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth/node"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
)
|
||||
|
||||
var EmpytHeaderHash = "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"
|
||||
|
||||
var _ = Describe("Node Info", func() {
|
||||
Describe("Parity Node Info", func() {
|
||||
It("verifies parity_versionInfo can be unmarshalled into ParityNodeInfo", func() {
|
||||
var parityNodeInfo core.ParityNodeInfo
|
||||
nodeInfoJSON := []byte(
|
||||
`{
|
||||
"hash": "0x2ae8b4ca278dd7b896090366615fef81cbbbc0e0",
|
||||
"track": "null",
|
||||
"version": {
|
||||
"major": 1,
|
||||
"minor": 6,
|
||||
"patch": 0
|
||||
}
|
||||
}`)
|
||||
json.Unmarshal(nodeInfoJSON, &parityNodeInfo)
|
||||
Expect(parityNodeInfo.Hash).To(Equal("0x2ae8b4ca278dd7b896090366615fef81cbbbc0e0"))
|
||||
Expect(parityNodeInfo.Track).To(Equal("null"))
|
||||
Expect(parityNodeInfo.Major).To(Equal(1))
|
||||
Expect(parityNodeInfo.Minor).To(Equal(6))
|
||||
Expect(parityNodeInfo.Patch).To(Equal(0))
|
||||
})
|
||||
|
||||
It("Creates client string", func() {
|
||||
parityNodeInfo := core.ParityNodeInfo{
|
||||
Track: "null",
|
||||
ParityVersion: core.ParityVersion{
|
||||
Major: 1,
|
||||
Minor: 6,
|
||||
Patch: 0,
|
||||
},
|
||||
Hash: "0x1232144j",
|
||||
}
|
||||
Expect(parityNodeInfo.String()).To(Equal("Parity/v1.6.0/"))
|
||||
})
|
||||
|
||||
It("returns parity ID and client name for parity node", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.ID).To(Equal("ParityNode"))
|
||||
Expect(n.ClientName).To(Equal("Parity/v1.2.3/"))
|
||||
})
|
||||
})
|
||||
|
||||
It("returns the genesis block for any client", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.GenesisBlock).To(Equal(EmpytHeaderHash))
|
||||
})
|
||||
|
||||
It("returns the network id for any client", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.NetworkID).To(Equal(float64(1234)))
|
||||
})
|
||||
|
||||
It("returns geth ID and client name for geth node", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
supportedModules := make(map[string]string)
|
||||
supportedModules["admin"] = "ok"
|
||||
client.SetSupporedModules(supportedModules)
|
||||
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.ID).To(Equal("enode://GethNode@172.17.0.1:30303"))
|
||||
Expect(n.ClientName).To(Equal("Geth/v1.7"))
|
||||
})
|
||||
|
||||
It("returns infura ID and client name for infura node", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
client.SetIpcPath("infura/path")
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.ID).To(Equal("infura"))
|
||||
Expect(n.ClientName).To(Equal("infura"))
|
||||
})
|
||||
|
||||
It("returns local id and client name for Local node", func() {
|
||||
client := fakes.NewMockRpcClient()
|
||||
client.SetIpcPath("127.0.0.1")
|
||||
n := node.MakeNode(client)
|
||||
Expect(n.ID).To(Equal("ganache"))
|
||||
Expect(n.ClientName).To(Equal("ganache"))
|
||||
|
||||
client.SetIpcPath("localhost")
|
||||
n = node.MakeNode(client)
|
||||
Expect(n.ID).To(Equal("ganache"))
|
||||
Expect(n.ClientName).To(Equal("ganache"))
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,40 @@
|
||||
// VulcanizeDB
|
||||
// Copyright © 2019 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 testing
|
||||
|
||||
import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/eth"
|
||||
"github.com/vulcanize/vulcanizedb/test_config"
|
||||
)
|
||||
|
||||
func SampleContract() core.Contract {
|
||||
return core.Contract{
|
||||
Abi: sampleAbiFileContents(),
|
||||
Hash: "0xd26114cd6EE289AccF82350c8d8487fedB8A0C07",
|
||||
}
|
||||
}
|
||||
|
||||
func sampleAbiFileContents() string {
|
||||
abiFileContents, err := eth.ReadAbiFile(test_config.ABIFilePath + "sample_abi.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return abiFileContents
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
bad json
|
||||
@@ -0,0 +1 @@
|
||||
[{"constant":true,"inputs":[],"name":"mintingFinished","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"}],"name":"mint","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"finishMinting","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_amount","type":"uint256"},{"name":"_releaseTime","type":"uint256"}],"name":"mintTimelocked","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[],"name":"MintFinished","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]
|
||||
@@ -0,0 +1 @@
|
||||
[{"foo": "bar"}]
|
||||
Reference in New Issue
Block a user