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 types.WitnessData err = rlp.DecodeBytes(witness.Data, &witnessData) if err != nil { t.Errorf("rlp decode error: %v", err) } 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 }