aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbojie <bojie@dexon.org>2018-12-03 16:30:53 +0800
committerWei-Ning Huang <w@dexon.org>2019-03-12 12:19:09 +0800
commitc7f9479a5c1fa013598da59cc56f60aa4d3bd5cb (patch)
treecb83a48c10489e93e66cb3a3258fe3346252b89d
parentc334a14b56c5a9239e9839c8bf870cb2425dc08f (diff)
downloaddexon-c7f9479a5c1fa013598da59cc56f60aa4d3bd5cb.tar.gz
dexon-c7f9479a5c1fa013598da59cc56f60aa4d3bd5cb.tar.zst
dexon-c7f9479a5c1fa013598da59cc56f60aa4d3bd5cb.zip
app: add app test case and benchmark (#66)
-rw-r--r--core/block_validator.go26
-rw-r--r--core/blockchain.go16
-rw-r--r--core/blockchain_test.go239
-rw-r--r--dex/app.go51
-rw-r--r--dex/app_test.go689
-rw-r--r--dex/backend.go2
6 files changed, 991 insertions, 32 deletions
diff --git a/core/block_validator.go b/core/block_validator.go
index 09539790b..660bd09f8 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -102,19 +102,23 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
}
func (v *BlockValidator) ValidateWitnessData(height uint64, data types.WitnessData) error {
- currentBlock := v.bc.CurrentBlock()
- if height > currentBlock.NumberU64() && height != 0 {
- pendingBlock := v.bc.GetPendingBlockByNumber(height)
-
- if pendingBlock.Root() != data.Root {
- return fmt.Errorf("invalid witness root %s vs %s",
- pendingBlock.Root().String(), data.Root.String())
- }
- if pendingBlock.ReceiptHash() != data.ReceiptHash {
- return fmt.Errorf("invalid witness receipt hash %s vs %s",
- pendingBlock.ReceiptHash().String(), data.ReceiptHash.String())
+ b := v.bc.GetPendingBlockByNumber(height)
+ if b == nil {
+ b = v.bc.GetBlockByNumber(height)
+ if b == nil {
+ return fmt.Errorf("can not find block %v either pending or confirmed block", height)
}
}
+
+ if b.Root() != data.Root {
+ return fmt.Errorf("invalid witness root %s vs %s",
+ b.Root().String(), data.Root.String())
+ }
+
+ if b.ReceiptHash() != data.ReceiptHash {
+ return fmt.Errorf("invalid witness receipt hash %s vs %s",
+ b.ReceiptHash().String(), data.ReceiptHash.String())
+ }
return nil
}
diff --git a/core/blockchain.go b/core/blockchain.go
index 878ed0a56..2adad1d74 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -356,6 +356,18 @@ func (bc *BlockChain) GetChainLastConfirmedHeight(chainID uint32) uint64 {
return val.(uint64)
}
+func (bc *BlockChain) GetAddressInfo(chainID uint32, address common.Address) (
+ info struct {
+ Nonce uint64
+ Cost *big.Int
+ Counter uint64
+ }) {
+ info.Nonce = bc.addressNonce[chainID][address]
+ info.Cost = bc.addressCost[chainID][address]
+ info.Counter = bc.addressCounter[chainID][address]
+ return
+}
+
// loadLastState loads the last known chain state from the database. This method
// assumes that the chain manager mutex is held.
func (bc *BlockChain) loadLastState() error {
@@ -1775,11 +1787,11 @@ func (bc *BlockChain) processPendingBlock(
var witnessData types.WitnessData
if err := rlp.Decode(bytes.NewReader(witness.Data), &witnessData); err != nil {
log.Error("Witness rlp decode failed", "error", err)
- panic(err)
+ return nil, nil, nil, fmt.Errorf("rlp decode fail: %v", err)
}
if err := bc.Validator().ValidateWitnessData(witness.Height, witnessData); err != nil {
- return nil, nil, nil, fmt.Errorf("valiadte witness data error: %v", err)
+ return nil, nil, nil, fmt.Errorf("validate witness data error: %v", err)
}
currentBlock := bc.CurrentBlock()
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 11bb1317a..9ee688f1c 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -20,12 +20,16 @@ import (
"fmt"
"math/big"
"math/rand"
+ "strings"
"sync"
"testing"
"time"
+ coreTypes "github.com/dexon-foundation/dexon-consensus/core/types"
+
"github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/consensus"
+ "github.com/dexon-foundation/dexon/consensus/dexcon"
"github.com/dexon-foundation/dexon/consensus/ethash"
"github.com/dexon-foundation/dexon/core/rawdb"
"github.com/dexon-foundation/dexon/core/state"
@@ -34,6 +38,7 @@ import (
"github.com/dexon-foundation/dexon/crypto"
"github.com/dexon-foundation/dexon/ethdb"
"github.com/dexon-foundation/dexon/params"
+ "github.com/dexon-foundation/dexon/rlp"
)
// So we can deterministically seed different blockchains
@@ -42,6 +47,8 @@ var (
forkSeed = 2
)
+const processNum = 9
+
// newCanonical creates a chain database, and injects a deterministic canonical
// chain. Depending on the full flag, if creates either a full block chain or a
// header only chain.
@@ -1388,6 +1395,238 @@ func TestLargeReorgTrieGC(t *testing.T) {
}
}
+type dexconTest struct {
+ dexcon.Dexcon
+
+ blockReward *big.Int
+ numChains uint32
+}
+
+// Finalize for skip governance access.
+func (d *dexconTest) Finalize(chain consensus.ChainReader, header *types.Header,
+ state *state.StateDB, txs []*types.Transaction, uncles []*types.Header,
+ receipts []*types.Receipt) (*types.Block, error) {
+ reward := new(big.Int).Div(d.blockReward, big.NewInt(int64(d.numChains)))
+ state.AddBalance(header.Coinbase, reward)
+
+ header.Reward = reward
+ header.Root = state.IntermediateRoot(true)
+ return types.NewBlock(header, txs, uncles, receipts), nil
+}
+
+func TestProcessPendingBlock(t *testing.T) {
+ db := ethdb.NewMemDatabase()
+
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Fatalf("new private key error: %v", err)
+ }
+
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+ gspec := &Genesis{
+ Config: params.TestnetChainConfig,
+ Alloc: GenesisAlloc{
+ addr: {
+ Balance: big.NewInt(10000000000),
+ Staked: big.NewInt(0),
+ }},
+ }
+
+ chainConfig, _, genesisErr := SetupGenesisBlock(db, gspec)
+ if genesisErr != nil {
+ t.Fatalf("set up genesis block error: %v", genesisErr)
+ }
+
+ engine := &dexconTest{
+ blockReward: chainConfig.Dexcon.BlockReward,
+ numChains: chainConfig.Dexcon.NumChains,
+ }
+ chain, err := NewBlockChain(db, nil, chainConfig, engine, vm.Config{}, nil)
+ if err != nil {
+ t.Fatalf("failed to create tester chain: %v", err)
+ }
+
+ // Process 1 ~ N success blocks.
+ for i := 0; i < processNum; i++ {
+ var witnessDataBytes []byte
+ if i == 0 {
+ witnessData := types.WitnessData{
+ Root: gspec.ToBlock(nil).Root(),
+ TxHash: gspec.ToBlock(nil).TxHash(),
+ ReceiptHash: gspec.ToBlock(nil).ReceiptHash(),
+ }
+ witnessDataBytes, err = rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+ } else {
+ witnessData := types.WitnessData{
+ Root: chain.pendingBlocks[uint64(i)].block.Root(),
+ TxHash: chain.pendingBlocks[uint64(i)].block.TxHash(),
+ ReceiptHash: chain.pendingBlocks[uint64(i)].block.ReceiptHash(),
+ }
+ witnessDataBytes, err = rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+ }
+
+ tx := types.NewTransaction(uint64(i), common.Address{9}, big.NewInt(1),
+ 21000, big.NewInt(1), nil)
+ signer := types.NewEIP155Signer(chainConfig.ChainID)
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ t.Fatalf("sign tx error: %v", err)
+ }
+
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(uint64(i) + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, types.Transactions{tx}, nil, nil), &coreTypes.Witness{
+ Height: uint64(i),
+ Data: witnessDataBytes,
+ })
+ if err != nil {
+ t.Fatalf("process pending block error: %v", err)
+ }
+
+ if chain.CurrentBlock().NumberU64() != uint64(i) {
+ t.Fatalf("expect current height %v but %v", uint64(i), chain.CurrentBlock().NumberU64())
+ }
+ }
+
+ // Witness rlp decode fail.
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(processNum + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, nil, nil, nil), &coreTypes.Witness{
+ Height: processNum,
+ })
+ if err == nil || !strings.Contains(err.Error(), "rlp decode fail") {
+ t.Fatalf("not expected fail: %v", err)
+ }
+
+ // Validate witness fail with unknown block.
+ witnessData := types.WitnessData{
+ Root: chain.pendingBlocks[processNum].block.Root(),
+ TxHash: chain.pendingBlocks[processNum].block.TxHash(),
+ ReceiptHash: chain.pendingBlocks[processNum].block.ReceiptHash(),
+ }
+ witnessDataBytes, err := rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(processNum + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, nil, nil, nil), &coreTypes.Witness{
+ Height: processNum + 1,
+ Data: witnessDataBytes,
+ })
+ if err == nil || !strings.Contains(err.Error(), "can not find block") {
+ t.Fatalf("not expected fail: %v", err)
+ }
+
+ // Validate witness fail with unexpected root.
+ witnessData = types.WitnessData{
+ Root: chain.pendingBlocks[processNum].block.Root(),
+ TxHash: chain.pendingBlocks[processNum].block.TxHash(),
+ ReceiptHash: chain.pendingBlocks[processNum].block.ReceiptHash(),
+ }
+ witnessDataBytes, err = rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(processNum + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, nil, nil, nil), &coreTypes.Witness{
+ Height: processNum - 1,
+ Data: witnessDataBytes,
+ })
+ if err == nil || !strings.Contains(err.Error(), "invalid witness root") {
+ t.Fatalf("not expected fail: %v", err)
+ }
+
+ // Apply transaction fail with insufficient fund.
+ witnessData = types.WitnessData{
+ Root: chain.pendingBlocks[processNum].block.Root(),
+ TxHash: chain.pendingBlocks[processNum].block.TxHash(),
+ ReceiptHash: chain.pendingBlocks[processNum].block.ReceiptHash(),
+ }
+ witnessDataBytes, err = rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+
+ tx := types.NewTransaction(processNum, common.Address{9}, big.NewInt(99999999999999),
+ 21000, big.NewInt(1), nil)
+ signer := types.NewEIP155Signer(chainConfig.ChainID)
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ t.Fatalf("sign tx error: %v", err)
+ }
+
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(processNum + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, types.Transactions{tx}, nil, nil), &coreTypes.Witness{
+ Height: processNum,
+ Data: witnessDataBytes,
+ })
+ if err == nil || !strings.Contains(err.Error(), "apply transaction error") {
+ t.Fatalf("not expected fail: %v", err)
+ }
+
+ // Apply transaction fail with nonce too height.
+ witnessData = types.WitnessData{
+ Root: chain.pendingBlocks[processNum].block.Root(),
+ TxHash: chain.pendingBlocks[processNum].block.TxHash(),
+ ReceiptHash: chain.pendingBlocks[processNum].block.ReceiptHash(),
+ }
+ witnessDataBytes, err = rlp.EncodeToBytes(&witnessData)
+ if err != nil {
+ t.Fatalf("rlp encode fail: %v", err)
+ }
+
+ tx = types.NewTransaction(999, common.Address{9}, big.NewInt(1),
+ 21000, big.NewInt(1), nil)
+ signer = types.NewEIP155Signer(chainConfig.ChainID)
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ t.Fatalf("sign tx error: %v", err)
+ }
+
+ _, err = chain.ProcessPendingBlock(types.NewBlock(&types.Header{
+ Number: new(big.Int).SetUint64(processNum + 1),
+ Time: big.NewInt(time.Now().UnixNano() / 1000000),
+ GasLimit: 10000,
+ Difficulty: big.NewInt(1),
+ Round: 0,
+ }, types.Transactions{tx}, nil, nil), &coreTypes.Witness{
+ Height: processNum,
+ Data: witnessDataBytes,
+ })
+ if err == nil || !strings.Contains(err.Error(), "apply transaction error") {
+ t.Fatalf("not expected fail: %v", err)
+ }
+}
+
// Benchmarks large blocks with value transfers to non-existing accounts
func benchmarkLargeNumberOfValueToNonexisting(b *testing.B, numTxs, numBlocks int, recipientFn func(uint64) common.Address, dataFn func(uint64) []byte) {
var (
diff --git a/dex/app.go b/dex/app.go
index 02e0a484f..cf36ad816 100644
--- a/dex/app.go
+++ b/dex/app.go
@@ -29,7 +29,6 @@ import (
"github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/core"
"github.com/dexon-foundation/dexon/core/types"
- "github.com/dexon-foundation/dexon/core/vm"
"github.com/dexon-foundation/dexon/ethdb"
"github.com/dexon-foundation/dexon/log"
"github.com/dexon-foundation/dexon/rlp"
@@ -46,20 +45,13 @@ type DexconApp struct {
gov *DexconGovernance
chainDB ethdb.Database
config *Config
- vmConfig vm.Config
chainLocksInitMu sync.Mutex
chainLocks map[uint32]*sync.RWMutex
- notifyMu sync.Mutex
- notifyChan sync.Map
chainLatestRoot sync.Map
}
-type notify struct {
- results []chan uint64
-}
-
type witnessData struct {
Root common.Hash
TxHash common.Hash
@@ -67,14 +59,13 @@ type witnessData struct {
}
func NewDexconApp(txPool *core.TxPool, blockchain *core.BlockChain, gov *DexconGovernance,
- chainDB ethdb.Database, config *Config, vmConfig vm.Config) *DexconApp {
+ chainDB ethdb.Database, config *Config) *DexconApp {
return &DexconApp{
txPool: txPool,
blockchain: blockchain,
gov: gov,
chainDB: chainDB,
config: config,
- vmConfig: vmConfig,
chainLocks: make(map[uint32]*sync.RWMutex),
}
}
@@ -275,17 +266,39 @@ func (d *DexconApp) VerifyBlock(block *coreTypes.Block) coreTypes.BlockVerifySta
}
// Wait until the witnessed root is seen on our local chain.
- for i := 0; i < verifyBlockMaxRetries && err != nil; i++ {
+ for i := 0; i < verifyBlockMaxRetries; i++ {
+ if d.blockchain.GetPendingHeight() < block.Witness.Height {
+ log.Debug("Pending height < witness height")
+ time.Sleep(500 * time.Millisecond)
+ continue
+ }
+
+ b := d.blockchain.GetPendingBlockByNumber(block.Witness.Height)
+ if b == nil {
+ b = d.blockchain.GetBlockByNumber(block.Witness.Height)
+ if b == nil {
+ log.Error("Can not get block by height %v", block.Witness.Height)
+ return coreTypes.VerifyInvalidBlock
+ }
+ }
+
+ if b.Root() != witnessData.Root {
+ log.Error("Witness root not correct expect %v but %v", b.Root(), witnessData.Root)
+ return coreTypes.VerifyInvalidBlock
+ }
+
+ if b.ReceiptHash() != witnessData.ReceiptHash {
+ log.Error("Witness receipt hash not correct expect %v but %v", b.ReceiptHash(), witnessData.ReceiptHash)
+ return coreTypes.VerifyInvalidBlock
+ }
+
_, err = d.blockchain.StateAt(witnessData.Root)
if err != nil {
- log.Debug("Witness root not found, retry in 500ms", "error", err)
- time.Sleep(500 * time.Millisecond)
+ log.Error("Get state by root %v error: %v", witnessData.Root, err)
+ return coreTypes.VerifyInvalidBlock
}
- }
- if err != nil {
- log.Error("Expected witness root not in stateDB", "err", err)
- return coreTypes.VerifyRetryLater
+ break
}
d.chainRLock(block.Position.ChainID)
@@ -457,5 +470,7 @@ func (d *DexconApp) BlockConfirmed(block coreTypes.Block) {
d.chainLock(block.Position.ChainID)
defer d.chainUnlock(block.Position.ChainID)
- d.blockchain.AddConfirmedBlock(&block)
+ if err := d.blockchain.AddConfirmedBlock(&block); err != nil {
+ panic(err)
+ }
}
diff --git a/dex/app_test.go b/dex/app_test.go
new file mode 100644
index 000000000..cbd29d0dc
--- /dev/null
+++ b/dex/app_test.go
@@ -0,0 +1,689 @@
+package dex
+
+import (
+ "crypto/ecdsa"
+ "fmt"
+ "math/big"
+ "testing"
+ "time"
+
+ coreCommon "github.com/dexon-foundation/dexon-consensus/common"
+ coreTypes "github.com/dexon-foundation/dexon-consensus/core/types"
+
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/consensus/dexcon"
+ "github.com/dexon-foundation/dexon/core"
+ "github.com/dexon-foundation/dexon/core/types"
+ "github.com/dexon-foundation/dexon/core/vm"
+ "github.com/dexon-foundation/dexon/crypto"
+ "github.com/dexon-foundation/dexon/ethdb"
+ "github.com/dexon-foundation/dexon/params"
+ "github.com/dexon-foundation/dexon/rlp"
+)
+
+func TestPreparePayload(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("hex to ecdsa error: %v", err)
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ t.Errorf("new test dexon error: %v", err)
+ }
+
+ signer := types.NewEIP155Signer(dex.chainConfig.ChainID)
+
+ var expectTx types.Transactions
+ for i := 0; i < 5; i++ {
+ tx, err := addTx(dex, i, signer, key)
+ if err != nil {
+ t.Errorf("add tx error: %v", err)
+ }
+ expectTx = append(expectTx, tx)
+ }
+
+ // This transaction will not be included.
+ _, err = addTx(dex, 100, signer, key)
+ if err != nil {
+ t.Errorf("add tx error: %v", err)
+ }
+
+ chainID := new(big.Int).Mod(crypto.PubkeyToAddress(key.PublicKey).Big(),
+ big.NewInt(int64(dex.chainConfig.Dexcon.NumChains)))
+ var chainNum uint32
+ for chainNum = 0; chainNum < dex.chainConfig.Dexcon.NumChains; chainNum++ {
+
+ payload, err := dex.app.PreparePayload(coreTypes.Position{ChainID: chainNum})
+ if err != nil {
+ t.Errorf("prepare payload error: %v", err)
+ }
+
+ var transactions types.Transactions
+ err = rlp.DecodeBytes(payload, &transactions)
+ if err != nil {
+ t.Errorf("rlp decode error: %v", err)
+ }
+
+ // Only one chain id allow prepare transactions.
+ if chainID.Uint64() == uint64(chainNum) && len(transactions) != 5 {
+ t.Errorf("incorrect transaction num expect %v but %v", 5, len(transactions))
+ } else if chainID.Uint64() != uint64(chainNum) && len(transactions) != 0 {
+ t.Errorf("expect empty slice but %v", len(transactions))
+ }
+
+ for i, tx := range transactions {
+ if expectTx[i].Gas() != tx.Gas() {
+ t.Errorf("unexpected gas expect %v but %v", expectTx[i].Gas(), tx.Gas())
+ }
+
+ if expectTx[i].Hash() != tx.Hash() {
+ t.Errorf("unexpected hash expect %v but %v", expectTx[i].Hash(), tx.Hash())
+ }
+
+ if expectTx[i].Nonce() != tx.Nonce() {
+ t.Errorf("unexpected nonce expect %v but %v", expectTx[i].Nonce(), tx.Nonce())
+ }
+
+ if expectTx[i].GasPrice().Uint64() != tx.GasPrice().Uint64() {
+ t.Errorf("unexpected gas price expect %v but %v",
+ expectTx[i].GasPrice().Uint64(), tx.GasPrice().Uint64())
+ }
+ }
+ }
+}
+
+func TestPrepareWitness(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("hex to ecdsa error: %v", err)
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ t.Errorf("new test dexon error: %v", err)
+ }
+
+ currentBlock := dex.blockchain.CurrentBlock()
+
+ witness, err := dex.app.PrepareWitness(0)
+ if err != nil {
+ t.Errorf("prepare witness error: %v", err)
+ }
+
+ if witness.Height != currentBlock.NumberU64() {
+ t.Errorf("unexpeted witness height %v", witness.Height)
+ }
+
+ var witnessData witnessData
+ err = rlp.DecodeBytes(witness.Data, &witnessData)
+ if err != nil {
+ t.Errorf("rlp decode error: %v", err)
+ }
+
+ if witnessData.TxHash != currentBlock.TxHash() {
+ t.Errorf("expect tx hash %v but %v", currentBlock.TxHash(), witnessData.TxHash)
+ }
+
+ if witnessData.Root != currentBlock.Root() {
+ t.Errorf("expect root %v but %v", currentBlock.Root(), witnessData.Root)
+ }
+
+ if witnessData.ReceiptHash != currentBlock.ReceiptHash() {
+ t.Errorf("expect receipt hash %v but %v", currentBlock.ReceiptHash(), witnessData.ReceiptHash)
+ }
+
+ if _, err := dex.app.PrepareWitness(999); err == nil {
+ t.Errorf("it must be get error from prepare")
+ } else {
+ t.Logf("Nice error: %v", err)
+ }
+}
+
+func TestVerifyBlock(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("hex to ecdsa error: %v", err)
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ t.Errorf("new test dexon error: %v", err)
+ }
+
+ // Prepare first confirmed block.
+ prepareConfirmedBlocks(dex, []*ecdsa.PrivateKey{key}, 0)
+
+ chainID := new(big.Int).Mod(crypto.PubkeyToAddress(key.PublicKey).Big(),
+ big.NewInt(int64(dex.chainConfig.Dexcon.NumChains)))
+
+ // Prepare normal block.
+ block := coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ // Expect ok.
+ status := dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyOK {
+ t.Errorf("verify fail: %v", status)
+ }
+
+ // Prepare invalid nonce tx.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 1, 100)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ // Expect invalid block.
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyInvalidBlock {
+ t.Errorf("verify fail: %v", status)
+ }
+
+ // Prepare invalid block height.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 2
+ block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ // Expect retry later.
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyRetryLater {
+ t.Errorf("verify fail expect retry later but get %v", status)
+ }
+
+ // Prepare reach block limit.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 10000)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ // Expect invalid block.
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyInvalidBlock {
+ t.Errorf("verify fail expect invalid block but get %v", status)
+ }
+
+ // Prepare insufficient funds.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ signer := types.NewEIP155Signer(dex.chainConfig.ChainID)
+ tx := types.NewTransaction(
+ 0,
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(50000000000000001),
+ params.TxGas,
+ big.NewInt(1),
+ nil)
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ return
+ }
+
+ block.Payload, err = rlp.EncodeToBytes(types.Transactions{tx})
+ if err != nil {
+ return
+ }
+
+ // expect invalid block
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyInvalidBlock {
+ t.Errorf("verify fail expect invalid block but get %v", status)
+ }
+
+ // Prepare invalid intrinsic gas.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ signer = types.NewEIP155Signer(dex.chainConfig.ChainID)
+ tx = types.NewTransaction(
+ 0,
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(1),
+ params.TxGas,
+ big.NewInt(1),
+ make([]byte, 1))
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ return
+ }
+
+ block.Payload, err = rlp.EncodeToBytes(types.Transactions{tx})
+ if err != nil {
+ return
+ }
+
+ // Expect invalid block.
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyInvalidBlock {
+ t.Errorf("verify fail expect invalid block but get %v", status)
+ }
+
+ // Prepare invalid transactions with nonce.
+ block = coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Position.ChainID = uint32(chainID.Uint64())
+ block.Position.Height = 1
+ _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+
+ signer = types.NewEIP155Signer(dex.chainConfig.ChainID)
+ tx1 := types.NewTransaction(
+ 0,
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(1),
+ params.TxGas,
+ big.NewInt(1),
+ make([]byte, 1))
+ tx1, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ return
+ }
+
+ // Invalid nonce.
+ tx2 := types.NewTransaction(
+ 2,
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(1),
+ params.TxGas,
+ big.NewInt(1),
+ make([]byte, 1))
+ tx2, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ return
+ }
+
+ block.Payload, err = rlp.EncodeToBytes(types.Transactions{tx1, tx2})
+ if err != nil {
+ return
+ }
+
+ // Expect invalid block.
+ status = dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyInvalidBlock {
+ t.Errorf("verify fail expect invalid block but get %v", status)
+ }
+}
+
+func TestBlockConfirmed(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("hex to ecdsa error: %v", err)
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ t.Errorf("new test dexon error: %v", err)
+ }
+
+ chainID := new(big.Int).Mod(crypto.PubkeyToAddress(key.PublicKey).Big(),
+ big.NewInt(int64(dex.chainConfig.Dexcon.NumChains)))
+
+ var (
+ expectCost big.Int
+ expectNonce uint64
+ expectCounter uint64
+ )
+ for i := 0; i < 10; i++ {
+ var startNonce int
+ if expectNonce != 0 {
+ startNonce = int(expectNonce) + 1
+ }
+ payload, witness, cost, nonce, err := prepareData(dex, key, startNonce, 50)
+ if err != nil {
+ t.Errorf("prepare data error: %v", err)
+ }
+ expectCost.Add(&expectCost, &cost)
+ expectNonce = nonce
+
+ block := coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Witness = witness
+ block.Payload = payload
+ block.Position.ChainID = uint32(chainID.Uint64())
+
+ dex.app.BlockConfirmed(*block)
+ expectCounter++
+ }
+
+ info := dex.app.blockchain.GetAddressInfo(uint32(chainID.Uint64()),
+ crypto.PubkeyToAddress(key.PublicKey))
+
+ if info.Counter != expectCounter {
+ t.Errorf("expect address counter is %v but %v", expectCounter, info.Counter)
+ }
+
+ if info.Cost.Cmp(&expectCost) != 0 {
+ t.Errorf("expect address cost is %v but %v", expectCost.Uint64(), info.Cost.Uint64())
+ }
+
+ if info.Nonce != expectNonce {
+ t.Errorf("expect address nonce is %v but %v", expectNonce, info.Nonce)
+ }
+}
+
+func TestBlockDelivered(t *testing.T) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ t.Errorf("hex to ecdsa error: %v", err)
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ t.Errorf("new test dexon error: %v", err)
+ }
+
+ address := crypto.PubkeyToAddress(key.PublicKey)
+ firstBlocksInfo, err := prepareConfirmedBlocks(dex, []*ecdsa.PrivateKey{key}, 50)
+ if err != nil {
+ t.Errorf("preapare confirmed block error: %v", err)
+ }
+
+ dex.app.BlockDelivered(firstBlocksInfo[0].Block.Hash, firstBlocksInfo[0].Block.Position,
+ coreTypes.FinalizationResult{
+ Timestamp: time.Now(),
+ Height: 1,
+ })
+
+ pendingBlock, pendingState := dex.blockchain.GetPending()
+
+ r, ok := dex.app.chainLatestRoot.Load(firstBlocksInfo[0].Block.Position.ChainID)
+ if !ok {
+ t.Errorf("lastest root cache not exist")
+ }
+
+ if *r.(*common.Hash) != pendingBlock.Root() {
+ t.Errorf("incorrect pending root")
+ }
+
+ currentBlock := dex.blockchain.CurrentBlock()
+ if currentBlock.NumberU64() != 0 {
+ t.Errorf("unexpected current block number %v", currentBlock.NumberU64())
+ }
+
+ pendingNonce := pendingState.GetNonce(address)
+ if pendingNonce != firstBlocksInfo[0].Nonce+1 {
+ t.Errorf("unexpected pending state nonce %v", pendingNonce)
+ }
+
+ balance := pendingState.GetBalance(address)
+ if new(big.Int).Add(balance, &firstBlocksInfo[0].Cost).Cmp(big.NewInt(50000000000000000)) != 0 {
+ t.Errorf("unexpected pending state balance %v", balance)
+ }
+
+ // prepare second block to witness first block
+ secondBlocksInfo, err := prepareConfirmedBlocks(dex, []*ecdsa.PrivateKey{key}, 25)
+ if err != nil {
+ t.Errorf("preapare confirmed block error: %v", err)
+ }
+
+ dex.app.BlockDelivered(secondBlocksInfo[0].Block.Hash, secondBlocksInfo[0].Block.Position,
+ coreTypes.FinalizationResult{
+ Timestamp: time.Now(),
+ Height: 2,
+ })
+
+ // second block witness first block, so current block number should be 1
+ currentBlock = dex.blockchain.CurrentBlock()
+ if currentBlock.NumberU64() != 1 {
+ t.Errorf("unexpected current block number %v", currentBlock.NumberU64())
+ }
+
+ currentState, err := dex.blockchain.State()
+ if err != nil {
+ t.Errorf("current state error: %v", err)
+ }
+
+ currentNonce := currentState.GetNonce(address)
+ if currentNonce != firstBlocksInfo[0].Nonce+1 {
+ t.Errorf("unexpected current state nonce %v", currentNonce)
+ }
+
+ balance = currentState.GetBalance(address)
+ if new(big.Int).Add(balance, &firstBlocksInfo[0].Cost).Cmp(big.NewInt(50000000000000000)) != 0 {
+ t.Errorf("unexpected current state balance %v", balance)
+ }
+}
+
+func BenchmarkBlockDeliveredFlow(b *testing.B) {
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ b.Errorf("hex to ecdsa error: %v", err)
+ return
+ }
+
+ dex, err := newTestDexonWithGenesis(key)
+ if err != nil {
+ b.Errorf("new test dexon error: %v", err)
+ }
+
+ b.ResetTimer()
+ for i := 1; i <= b.N; i++ {
+ blocksInfo, err := prepareConfirmedBlocks(dex, []*ecdsa.PrivateKey{key}, 100)
+ if err != nil {
+ b.Errorf("preapare confirmed block error: %v", err)
+ return
+ }
+
+ dex.app.BlockDelivered(blocksInfo[0].Block.Hash, blocksInfo[0].Block.Position,
+ coreTypes.FinalizationResult{
+ Timestamp: time.Now(),
+ Height: uint64(i),
+ })
+ }
+}
+
+func newTestDexonWithGenesis(allocKey *ecdsa.PrivateKey) (*Dexon, error) {
+ db := ethdb.NewMemDatabase()
+
+ testBankAddress := crypto.PubkeyToAddress(allocKey.PublicKey)
+ genesis := core.DefaultTestnetGenesisBlock()
+ genesis.Alloc = core.GenesisAlloc{
+ testBankAddress: {
+ Balance: big.NewInt(100000000000000000),
+ Staked: big.NewInt(50000000000000000),
+ },
+ }
+ chainConfig, _, err := core.SetupGenesisBlock(db, genesis)
+ if err != nil {
+ return nil, err
+ }
+
+ key, err := crypto.GenerateKey()
+ if err != nil {
+ return nil, err
+ }
+
+ config := Config{PrivateKey: key}
+ vmConfig := vm.Config{IsBlockProposer: true}
+
+ engine := dexcon.New()
+
+ dex := &Dexon{
+ chainDb: db,
+ chainConfig: chainConfig,
+ networkID: config.NetworkId,
+ engine: engine,
+ }
+
+ dex.blockchain, err = core.NewBlockChain(db, nil, chainConfig, engine, vmConfig, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ txPoolConfig := core.DefaultTxPoolConfig
+ dex.txPool = core.NewTxPool(txPoolConfig, chainConfig, dex.blockchain, true)
+
+ dex.APIBackend = &DexAPIBackend{dex, nil}
+ dex.governance = NewDexconGovernance(dex.APIBackend, dex.chainConfig, config.PrivateKey)
+ engine.SetConfigFetcher(dex.governance)
+ dex.app = NewDexconApp(dex.txPool, dex.blockchain, dex.governance, db, &config)
+
+ return dex, nil
+}
+
+// Add tx into tx pool.
+func addTx(dex *Dexon, nonce int, signer types.Signer, key *ecdsa.PrivateKey) (
+ *types.Transaction, error) {
+ tx := types.NewTransaction(
+ uint64(nonce),
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(int64(nonce*1)),
+ params.TxGas,
+ big.NewInt(1),
+ nil)
+ tx, err := types.SignTx(tx, signer, key)
+ if err != nil {
+ return nil, err
+ }
+
+ if err := dex.txPool.AddRemote(tx); err != nil {
+ return nil, err
+ }
+
+ return tx, nil
+}
+
+// Prepare data with given transaction number and start nonce.
+func prepareData(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) (
+ payload []byte, witness coreTypes.Witness, cost big.Int, nonce uint64, err error) {
+ signer := types.NewEIP155Signer(dex.chainConfig.ChainID)
+ chainID := new(big.Int).Mod(crypto.PubkeyToAddress(key.PublicKey).Big(),
+ big.NewInt(int64(dex.chainConfig.Dexcon.NumChains)))
+
+ for n := startNonce; n < startNonce+txNum; n++ {
+ var tx *types.Transaction
+ tx, err = addTx(dex, n, signer, key)
+ if err != nil {
+ return
+ }
+
+ cost.Add(&cost, tx.Cost())
+ nonce = uint64(n)
+ }
+
+ payload, err = dex.app.PreparePayload(coreTypes.Position{ChainID: uint32(chainID.Uint64())})
+ if err != nil {
+ return
+ }
+
+ witness, err = dex.app.PrepareWitness(0)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func prepareDataWithoutTxPool(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) (
+ payload []byte, witness coreTypes.Witness, err error) {
+ signer := types.NewEIP155Signer(dex.chainConfig.ChainID)
+
+ var transactions types.Transactions
+ for n := startNonce; n < startNonce+txNum; n++ {
+ tx := types.NewTransaction(
+ uint64(n),
+ common.BytesToAddress([]byte{9}),
+ big.NewInt(int64(n*1)),
+ params.TxGas,
+ big.NewInt(1),
+ nil)
+ tx, err = types.SignTx(tx, signer, key)
+ if err != nil {
+ return
+ }
+ transactions = append(transactions, tx)
+ }
+
+ payload, err = rlp.EncodeToBytes(&transactions)
+ if err != nil {
+ return
+ }
+
+ witness, err = dex.app.PrepareWitness(0)
+ if err != nil {
+ return
+ }
+
+ return
+}
+
+func prepareConfirmedBlocks(dex *Dexon, keys []*ecdsa.PrivateKey, txNum int) (blocksInfo []struct {
+ Block *coreTypes.Block
+ Cost big.Int
+ Nonce uint64
+}, err error) {
+ for _, key := range keys {
+ address := crypto.PubkeyToAddress(key.PublicKey)
+ chainID := new(big.Int).Mod(address.Big(),
+ big.NewInt(int64(dex.chainConfig.Dexcon.NumChains)))
+
+ // Prepare one block for pending.
+ var (
+ payload []byte
+ witness coreTypes.Witness
+ cost big.Int
+ nonce uint64
+ )
+ startNonce := dex.txPool.State().GetNonce(address)
+ payload, witness, cost, nonce, err = prepareData(dex, key, int(startNonce), txNum)
+ if err != nil {
+ err = fmt.Errorf("prepare data error: %v", err)
+ return
+ }
+
+ block := coreTypes.NewBlock()
+ block.Hash = coreCommon.NewRandomHash()
+ block.Witness = witness
+ block.Payload = payload
+ block.Position.ChainID = uint32(chainID.Uint64())
+
+ status := dex.app.VerifyBlock(block)
+ if status != coreTypes.VerifyOK {
+ err = fmt.Errorf("verify fail: %v", status)
+ return
+ }
+
+ dex.app.BlockConfirmed(*block)
+
+ blocksInfo = append(blocksInfo, struct {
+ Block *coreTypes.Block
+ Cost big.Int
+ Nonce uint64
+ }{Block: block, Cost: cost, Nonce: nonce})
+ }
+
+ return
+}
diff --git a/dex/backend.go b/dex/backend.go
index 550b1051a..eb9d8f765 100644
--- a/dex/backend.go
+++ b/dex/backend.go
@@ -150,7 +150,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) {
// Dexcon related objects.
dex.governance = NewDexconGovernance(dex.APIBackend, dex.chainConfig, config.PrivateKey)
- dex.app = NewDexconApp(dex.txPool, dex.blockchain, dex.governance, chainDb, config, vmConfig)
+ dex.app = NewDexconApp(dex.txPool, dex.blockchain, dex.governance, chainDb, config)
// Set config fetcher so engine can fetch current system configuration from state.
engine.SetConfigFetcher(dex.governance)