Add unit test.

This commit is contained in:
Arijit Das 2022-01-18 22:44:38 +05:30
parent 7afb77c18d
commit 530aa2b62f
5 changed files with 268 additions and 6 deletions

View File

@ -7,4 +7,4 @@
[validate]
block-height = 1
trail = 3100
trail = 10

View File

@ -2,12 +2,17 @@ package validator
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/statediff/indexer/node"
"github.com/ethereum/go-ethereum/statediff/indexer/postgres"
"github.com/spf13/viper"
)
var TestChainConfig = &params.ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(100), big.NewInt(0), nil, nil, new(params.EthashConfig), nil}
type Config struct {
dbParams postgres.ConnectionParams
dbConfig postgres.ConnectionConfig

View File

@ -4,8 +4,13 @@ import (
"context"
"errors"
"fmt"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
@ -21,6 +26,12 @@ import (
ethServerShared "github.com/vulcanize/ipld-eth-server/pkg/shared"
)
var (
big8 = big.NewInt(8)
big32 = big.NewInt(32)
testHash = common.HexToHash("0x1283a0bca5cce009bcf3e5a860eccdc202d1345f464024f2ee8ea1e1254349e7")
)
type service struct {
db *postgres.DB
blockNum, trail uint64
@ -85,7 +96,7 @@ func NewDB(connectString string, config postgres.ConnectionConfig, node node.Inf
// Start is used to begin the service
func (s *service) Start(ctx context.Context) (uint64, error) {
api, err := ethAPI(s.db)
api, err := ethAPI(ctx, s.db)
if err != nil {
return 0, err
}
@ -106,6 +117,7 @@ func (s *service) Start(ctx context.Context) (uint64, error) {
}
blockStateRoot := validateBlock.Header().Root.String()
dbStateRoot := stateDB.IntermediateRoot(true).String()
if blockStateRoot != dbStateRoot {
s.logger.Errorf("failed to verify state root at block %d", idxBlockNum)
@ -129,10 +141,9 @@ func (s *service) Start(ctx context.Context) (uint64, error) {
return idxBlockNum, nil
}
func ethAPI(db *postgres.DB) (*ipldEth.PublicEthAPI, error) {
// TODO: decide network for chainConfig.
func ethAPI(ctx context.Context, db *postgres.DB) (*ipldEth.PublicEthAPI, error) {
// TODO: decide network for custom chainConfig.
backend, err := NewEthBackend(db, &ipldEth.Config{
ChainConfig: params.RinkebyChainConfig,
GroupCacheConfig: &ethServerShared.GroupCacheConfig{
StateDB: ethServerShared.GroupConfig{
Name: "vulcanize_validator",
@ -144,6 +155,16 @@ func ethAPI(db *postgres.DB) (*ipldEth.PublicEthAPI, error) {
return nil, err
}
var genesisBlock *types.Block
if backend.Config.ChainConfig == nil {
genesisBlock, err = backend.BlockByNumber(ctx, rpc.BlockNumber(0))
if err != nil {
return nil, err
}
backend.Config.ChainConfig = setChainConfig(genesisBlock.Hash())
}
return ipldEth.NewPublicEthAPI(backend, nil, false, false, false)
}
@ -177,7 +198,7 @@ func applyTransaction(block *types.Block, backend *ipldEth.Backend) (*state.Stat
// Assemble the transaction call message and return if the requested offset
msg, _ := tx.AsMessage(signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg)
ctx := core.NewEVMBlockContext(block.Header(), backend, nil)
ctx := core.NewEVMBlockContext(block.Header(), backend, getAuthor(backend, block.Header()))
// Not yet the searched for transaction, execute on top of the current state
newEVM := vm.NewEVM(ctx, txContext, stateDB, backend.Config.ChainConfig, vm.Config{})
@ -187,5 +208,79 @@ func applyTransaction(block *types.Block, backend *ipldEth.Backend) (*state.Stat
return nil, fmt.Errorf("transaction %#x failed: %w", tx.Hash(), err)
}
}
if backend.Config.ChainConfig.Ethash != nil {
accumulateRewards(backend.Config.ChainConfig, stateDB, block.Header(), block.Uncles())
}
return stateDB, nil
}
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
blockReward := ethash.FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ethash.ByzantiumBlockReward
}
if config.IsConstantinople(header.Number) {
blockReward = ethash.ConstantinopleBlockReward
}
// Accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
for _, uncle := range uncles {
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32)
reward.Add(reward, r)
}
state.AddBalance(header.Coinbase, reward)
}
func setChainConfig(ghash common.Hash) *params.ChainConfig {
switch {
case ghash == params.MainnetGenesisHash:
return params.MainnetChainConfig
case ghash == params.RopstenGenesisHash:
return params.RopstenChainConfig
case ghash == params.SepoliaGenesisHash:
return params.SepoliaChainConfig
case ghash == params.RinkebyGenesisHash:
return params.RinkebyChainConfig
case ghash == params.GoerliGenesisHash:
return params.GoerliChainConfig
case ghash == testHash:
return TestChainConfig
default:
return params.AllEthashProtocolChanges
}
}
func getAuthor(b *ipldEth.Backend, header *types.Header) *common.Address {
author, err := getEngine(b).Author(header)
if err != nil {
return nil
}
return &author
}
func getEngine(b *ipldEth.Backend) consensus.Engine {
// TODO: add logic for other engines
if b.Config.ChainConfig.Clique != nil {
engine := clique.New(b.Config.ChainConfig.Clique, nil)
return engine
}
return ethash.NewFaker()
}

View File

@ -0,0 +1,19 @@
package test
import (
"io/ioutil"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func TestETHSuite(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "eth ipld validator eth suite test")
}
var _ = BeforeSuite(func() {
logrus.SetOutput(ioutil.Discard)
})

143
test/validator_test.go Normal file
View File

@ -0,0 +1,143 @@
package test
import (
"context"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/statediff"
"github.com/ethereum/go-ethereum/statediff/indexer"
"github.com/ethereum/go-ethereum/statediff/indexer/node"
"github.com/ethereum/go-ethereum/statediff/indexer/postgres"
sdtypes "github.com/ethereum/go-ethereum/statediff/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/vulcanize/ipld-eth-server/pkg/eth"
"github.com/vulcanize/ipld-eth-server/pkg/eth/test_helpers"
"github.com/Vulcanize/ipld-eth-db-validator/pkg/validator"
)
const (
chainLength = 20
blockHeight = 1
trail = 15
)
// SetupDB is use to setup a db for watcher tests
func setupDB() (*postgres.DB, error) {
uri := postgres.DbConnectionString(postgres.ConnectionParams{
User: "vdbm",
Password: "password",
Hostname: "localhost",
Name: "vulcanize_public",
Port: 5432,
})
return postgres.NewDB(uri, postgres.ConnectionConfig{}, node.Info{})
}
var _ = Describe("eth state reading tests", func() {
var (
blocks []*types.Block
receipts []types.Receipts
chain *core.BlockChain
db *postgres.DB
chainConfig = params.TestChainConfig
mockTD = big.NewInt(1337)
err error
)
It("test init", func() {
db, err = setupDB()
Expect(err).ToNot(HaveOccurred())
transformer, err := indexer.NewStateDiffIndexer(chainConfig, db)
Expect(err).ToNot(HaveOccurred())
// make the test blockchain (and state)
blocks, receipts, chain = test_helpers.MakeChain(chainLength, test_helpers.Genesis, test_helpers.TestChainGen)
params := statediff.Params{
IntermediateStateNodes: true,
IntermediateStorageNodes: true,
}
// iterate over the blocks, generating statediff payloads, and transforming the data into Postgres
builder := statediff.NewBuilder(chain.StateCache())
for i, block := range blocks {
var args statediff.Args
var rcts types.Receipts
if i == 0 {
args = statediff.Args{
OldStateRoot: common.Hash{},
NewStateRoot: block.Root(),
BlockNumber: block.Number(),
BlockHash: block.Hash(),
}
} else {
args = statediff.Args{
OldStateRoot: blocks[i-1].Root(),
NewStateRoot: block.Root(),
BlockNumber: block.Number(),
BlockHash: block.Hash(),
}
rcts = receipts[i-1]
}
diff, err := builder.BuildStateDiffObject(args, params)
Expect(err).ToNot(HaveOccurred())
tx, err := transformer.PushBlock(block, rcts, mockTD)
Expect(err).ToNot(HaveOccurred())
for _, node := range diff.Nodes {
err = transformer.PushStateNode(tx, node)
Expect(err).ToNot(HaveOccurred())
}
err = tx.Close(err)
Expect(err).ToNot(HaveOccurred())
}
// Insert some non-canonical data into the database so that we test our ability to discern canonicity
indexAndPublisher, err := indexer.NewStateDiffIndexer(chainConfig, db)
Expect(err).ToNot(HaveOccurred())
tx, err := indexAndPublisher.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
Expect(err).ToNot(HaveOccurred())
err = tx.Close(err)
Expect(err).ToNot(HaveOccurred())
// The non-canonical header has a child
tx, err = indexAndPublisher.PushBlock(test_helpers.MockChild, test_helpers.MockReceipts, test_helpers.MockChild.Difficulty())
Expect(err).ToNot(HaveOccurred())
hash := sdtypes.CodeAndCodeHash{
Hash: test_helpers.CodeHash,
Code: test_helpers.ContractCode,
}
err = indexAndPublisher.PushCodeAndCodeHash(tx, hash)
Expect(err).ToNot(HaveOccurred())
err = tx.Close(err)
Expect(err).ToNot(HaveOccurred())
})
defer It("test teardown", func() {
eth.TearDownDB(db)
chain.Stop()
})
Describe("state_validation", func() {
It("Validator", func() {
srvc := validator.NewService(db, blockHeight, trail)
_, err := srvc.Start(context.Background())
Expect(err).ToNot(HaveOccurred())
})
})
})