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,
})
_, pendingState := dex.blockchain.GetPending()
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
}