diff options
-rw-r--r-- | core/block_validator.go | 26 | ||||
-rw-r--r-- | core/blockchain.go | 16 | ||||
-rw-r--r-- | core/blockchain_test.go | 239 | ||||
-rw-r--r-- | dex/app.go | 51 | ||||
-rw-r--r-- | dex/app_test.go | 689 | ||||
-rw-r--r-- | dex/backend.go | 2 |
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) |