diff options
author | bojie <bojie@dexon.org> | 2019-01-07 15:40:56 +0800 |
---|---|---|
committer | Wei-Ning Huang <w@dexon.org> | 2019-03-12 12:19:09 +0800 |
commit | 7285c3b0af76e2e470ba3917b006264a06644246 (patch) | |
tree | 97e0e582ff951d5bedddb22cf3f4201e44bbb358 | |
parent | f8bd72b9e430960790db8972826a541425b4c552 (diff) | |
download | dexon-7285c3b0af76e2e470ba3917b006264a06644246.tar.gz dexon-7285c3b0af76e2e470ba3917b006264a06644246.tar.zst dexon-7285c3b0af76e2e470ba3917b006264a06644246.zip |
app: implement logic for prepare/verify correctly when chain number change (#118)
-rw-r--r-- | core/blockchain.go | 116 | ||||
-rw-r--r-- | core/blockchain_test.go | 6 | ||||
-rw-r--r-- | core/genesis.go | 2 | ||||
-rw-r--r-- | core/rawdb/accessors_chain.go | 15 | ||||
-rw-r--r-- | core/rawdb/schema.go | 2 | ||||
-rw-r--r-- | dex/app.go | 56 | ||||
-rw-r--r-- | dex/app_test.go | 207 | ||||
-rw-r--r-- | eth/helper_test.go | 4 |
8 files changed, 383 insertions, 25 deletions
diff --git a/core/blockchain.go b/core/blockchain.go index b4a2882dd..73e820c32 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -160,6 +160,7 @@ type BlockChain struct { pendingBlocks map[uint64]struct { block *types.Block receipts types.Receipts + proctime time.Duration } } @@ -199,6 +200,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par pendingBlocks: make(map[uint64]struct { block *types.Block receipts types.Receipts + proctime time.Duration }), confirmedBlocks: make(map[uint32]map[coreCommon.Hash]*blockInfo), addressNonce: make(map[uint32]map[common.Address]uint64), @@ -1073,6 +1075,13 @@ func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain [ bytes += batch.ValueSize() batch.Reset() } + + if i == len(blockChain)-1 { + err := bc.updateLastRoundNumber(block.Round()) + if err != nil { + return 0, err + } + } } if batch.ValueSize() > 0 { bytes += batch.ValueSize() @@ -1350,7 +1359,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] case err == consensus.ErrPrunedAncestor: return bc.insertSidechain(block, it) - // First block is future, shove it (and all children) to the future queue (unknown ancestor) + // First block is future, shove it (and all children) to the future queue (unknown ancestor) case err == consensus.ErrFutureBlock || (err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(it.first().ParentHash())): for block != nil && (it.index == 0 || err == consensus.ErrUnknownAncestor) { if err := bc.addFutureBlock(block); err != nil { @@ -1364,10 +1373,10 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] // If there are any still remaining, mark as ignored return it.index, events, coalescedLogs, err - // First block (and state) is known - // 1. We did a roll-back, and should now do a re-import - // 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot - // from the canonical chain, which has not been verified. + // First block (and state) is known + // 1. We did a roll-back, and should now do a re-import + // 2. The block is stored as a sidechain, and is lying about it's stateroot, and passes a stateroot + // from the canonical chain, which has not been verified. case err == ErrKnownBlock: // Skip all known blocks that behind us current := bc.CurrentBlock().NumberU64() @@ -1378,7 +1387,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] } // Falls through to the block import - // Some other error occurred, abort + // Some other error occurred, abort case err != nil: stats.ignored += len(it.chain) bc.reportBlock(block, nil, err) @@ -1786,6 +1795,13 @@ func (bc *BlockChain) insertDexonChain(chain types.Blocks) (int, []interface{}, cache, _ := bc.stateCache.TrieDB().Size() stats.report(chain, i, cache) + + if i == len(chain)-1 { + err = bc.updateLastRoundNumber(block.Round()) + if err != nil { + return 0, nil, nil, err + } + } } // Append a single chain head event if we've progressed the chain if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { @@ -1823,15 +1839,7 @@ func (bc *BlockChain) processPendingBlock( coalescedLogs []*types.Log ) - // Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss) - senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, block.Number()), []*types.Block{block}) - - if atomic.LoadInt32(&bc.procInterrupt) == 1 { - log.Debug("Premature abort during blocks processing") - return nil, nil, nil, fmt.Errorf("interrupt") - } bstart := time.Now() - var witnessData types.WitnessData if err := rlp.Decode(bytes.NewReader(witness.Data), &witnessData); err != nil { log.Error("Witness rlp decode failed", "error", err) @@ -1898,13 +1906,18 @@ func (bc *BlockChain) processPendingBlock( } // Add into pending blocks. - bc.addPendingBlock(newPendingBlock, receipts) + bc.addPendingBlock(newPendingBlock, receipts, proctime) events = append(events, BlockConfirmedEvent{newPendingBlock}) log.Debug("Inserted pending block", "height", newPendingBlock.Number(), "hash", newPendingBlock.Hash()) // Start insert available pending blocks into db for pendingHeight := bc.CurrentBlock().NumberU64() + 1; pendingHeight <= witness.Height; pendingHeight++ { + if atomic.LoadInt32(&bc.procInterrupt) == 1 { + log.Debug("Premature abort during blocks processing") + return nil, nil, nil, fmt.Errorf("interrupt") + } + pendingIns, exist := bc.pendingBlocks[pendingHeight] if !exist { log.Error("Block has already inserted", "height", pendingHeight) @@ -1917,6 +1930,7 @@ func (bc *BlockChain) processPendingBlock( } // Write the block to the chain and get the status. + insertTime := time.Now() status, err := bc.WriteBlockWithState(pendingIns.block, pendingIns.receipts, s) if err != nil { return nil, events, coalescedLogs, fmt.Errorf("WriteBlockWithState error: %v", err) @@ -1933,12 +1947,12 @@ func (bc *BlockChain) processPendingBlock( allLogs = append(allLogs, r.Logs...) } coalescedLogs = append(coalescedLogs, allLogs...) - blockInsertTimer.UpdateSince(bstart) + blockInsertTimer.UpdateSince(insertTime) events = append(events, ChainEvent{pendingIns.block, pendingIns.block.Hash(), allLogs}) lastCanon = pendingIns.block // Only count canonical blocks for GC processing time - bc.gcproc += proctime + bc.gcproc += pendingIns.proctime case SideStatTy: return nil, nil, nil, fmt.Errorf("insert pending block and fork found") @@ -1950,6 +1964,13 @@ func (bc *BlockChain) processPendingBlock( cache, _ := bc.stateCache.TrieDB().Size() stats.report([]*types.Block{pendingIns.block}, 0, cache) + + if pendingHeight == witness.Height { + err = bc.updateLastRoundNumber(pendingIns.block.Round()) + if err != nil { + return nil, nil, nil, err + } + } } // Append a single chain head event if we've progressed the chain if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { @@ -1960,6 +1981,46 @@ func (bc *BlockChain) processPendingBlock( return &root, events, coalescedLogs, nil } +func (bc *BlockChain) ProcessEmptyBlock(block *types.Block) error { + bstart := time.Now() + var header = block.Header() + var parentBlock *types.Block + var pendingState *state.StateDB + var err error + parent, exist := bc.pendingBlocks[block.NumberU64()-1] + if !exist { + parentBlock = bc.GetBlockByNumber(block.NumberU64() - 1) + if parentBlock == nil { + return fmt.Errorf("parent block %d not exist", block.NumberU64()-1) + } + } else { + parentBlock = parent.block + } + + pendingState, err = state.New(parentBlock.Root(), bc.stateCache) + if err != nil { + return err + } + + header.ParentHash = parentBlock.Hash() + header.GasUsed = 0 + header.Root = pendingState.IntermediateRoot(true) + if header.Root != parentBlock.Root() { + return fmt.Errorf("empty block state root must same as parent") + } + + newPendingBlock := types.NewBlock(header, nil, nil, nil) + if _, ok := bc.GetRoundHeight(newPendingBlock.Round()); !ok { + bc.storeRoundHeight(newPendingBlock.Round(), newPendingBlock.NumberU64()) + } + + proctime := time.Since(bstart) + bc.addPendingBlock(newPendingBlock, nil, proctime) + bc.PostChainEvents([]interface{}{BlockConfirmedEvent{newPendingBlock}}, nil) + + return nil +} + func (bc *BlockChain) removePendingBlock(height uint64) { bc.pendingBlockMu.Lock() defer bc.pendingBlockMu.Unlock() @@ -1967,14 +2028,15 @@ func (bc *BlockChain) removePendingBlock(height uint64) { delete(bc.pendingBlocks, height) } -func (bc *BlockChain) addPendingBlock(block *types.Block, receipts types.Receipts) { +func (bc *BlockChain) addPendingBlock(block *types.Block, receipts types.Receipts, proctime time.Duration) { bc.pendingBlockMu.Lock() defer bc.pendingBlockMu.Unlock() bc.pendingBlocks[block.NumberU64()] = struct { block *types.Block receipts types.Receipts - }{block: block, receipts: receipts} + proctime time.Duration + }{block: block, receipts: receipts, proctime: proctime} bc.lastPendingHeight = block.NumberU64() } @@ -2418,3 +2480,19 @@ func (bc *BlockChain) GetRoundHeight(round uint64) (uint64, bool) { func (bc *BlockChain) storeRoundHeight(round uint64, height uint64) { bc.roundHeightMap.Store(round, height) } + +func (bc *BlockChain) updateLastRoundNumber(round uint64) error { + currentLastRound, err := rawdb.ReadLastRoundNumber(bc.db) + if err != nil { + return err + } + + if round > currentLastRound { + err = rawdb.WriteLastRoundNumber(bc.db, round) + if err != nil { + return err + } + } + + return nil +} diff --git a/core/blockchain_test.go b/core/blockchain_test.go index ba0c8480f..516e43998 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -654,6 +654,9 @@ func TestFastVsFullChains(t *testing.T) { // Fast import the chain as a non-archive node to test fastDb := ethdb.NewMemDatabase() gspec.MustCommit(fastDb) + if err := rawdb.WriteLastRoundNumber(fastDb, 0); err != nil { + t.Fatalf("failed to write last round: %v", err) + } fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) defer fast.Stop() @@ -745,6 +748,9 @@ func TestLightVsFastVsFullChainHeads(t *testing.T) { // Import the chain as a non-archive node and ensure all pointers are updated fastDb := ethdb.NewMemDatabase() gspec.MustCommit(fastDb) + if err := rawdb.WriteLastRoundNumber(fastDb, 0); err != nil { + t.Fatalf("failed to write last round: %v", err) + } fast, _ := NewBlockChain(fastDb, nil, gspec.Config, ethash.NewFaker(), vm.Config{}, nil) defer fast.Stop() diff --git a/core/genesis.go b/core/genesis.go index d0875d36d..f07b7a270 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -170,6 +170,8 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig return SetupGenesisBlockWithOverride(db, genesis, nil) } func SetupGenesisBlockWithOverride(db ethdb.Database, genesis *Genesis, constantinopleOverride *big.Int) (*params.ChainConfig, common.Hash, error) { + rawdb.WriteLastRoundNumber(db, 0) + if genesis != nil && genesis.Config == nil { return params.AllEthashProtocolChanges, common.Hash{}, errGenesisNoConfig } diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index 801ad9362..5e23bef8f 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -348,6 +348,21 @@ func WriteBlock(db DatabaseWriter, block *types.Block) { WriteHeader(db, block.Header()) } +// ReadLastRoundNumber get current round number. +func ReadLastRoundNumber(db DatabaseReader) (uint64, error) { + number, err := db.Get(lastRoundNum) + if err != nil { + return 0, err + } + + return new(big.Int).SetBytes(number).Uint64(), nil +} + +// WriteLastRoundNumber write current round number. +func WriteLastRoundNumber(db DatabaseWriter, number uint64) error { + return db.Put(lastRoundNum, new(big.Int).SetUint64(number).Bytes()) +} + // DeleteBlock removes all block data associated with a hash. func DeleteBlock(db DatabaseDeleter, hash common.Hash, number uint64) { DeleteReceipts(db, hash, number) diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 87a56c0ba..954ebfc1d 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -63,6 +63,8 @@ var ( // Chain index prefixes (use `i` + single byte to avoid mixing data types). BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress + lastRoundNum = []byte("LastRoundNum") + preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) ) diff --git a/dex/app.go b/dex/app.go index c52a6c79b..b1a0b4548 100644 --- a/dex/app.go +++ b/dex/app.go @@ -29,6 +29,7 @@ import ( "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/rawdb" "github.com/dexon-foundation/dexon/core/types" "github.com/dexon-foundation/dexon/ethdb" "github.com/dexon-foundation/dexon/event" @@ -175,6 +176,22 @@ func (d *DexconApp) preparePayload(ctx context.Context, position coreTypes.Posit default: } + if position.Round > 0 { + // If round chain number changed but new round is not delivered yet, payload must be nil. + previousNumChains := d.gov.Configuration(position.Round - 1).NumChains + currentNumChains := d.gov.Configuration(position.Round).NumChains + if previousNumChains != currentNumChains { + deliveredRound, err := rawdb.ReadLastRoundNumber(d.chainDB) + if err != nil { + panic(fmt.Errorf("read current round error: %v", err)) + } + + if deliveredRound < position.Round { + return nil, nil + } + } + } + if position.Height != 0 { // Check if chain block height is strictly increamental. chainLastHeight, ok := d.blockchain.GetChainLastConfirmedHeight(position.ChainID) @@ -186,8 +203,7 @@ func (d *DexconApp) preparePayload(ctx context.Context, position coreTypes.Posit } b, latestState := d.blockchain.GetPending() - log.Debug("Prepare payload", "chain", position.ChainID, "height", position.Height, "state", - b.Root().String()) + log.Debug("Prepare payload", "chain", position.ChainID, "height", position.Height, "state", b.Root().String()) txsMap, err := d.txPool.Pending() if err != nil { @@ -345,6 +361,26 @@ func (d *DexconApp) VerifyBlock(block *coreTypes.Block) coreTypes.BlockVerifySta } } + if block.Position.Round > 0 { + // If round chain number changed but new round is not delivered yet, payload must be nil. + previousNumChains := d.gov.Configuration(block.Position.Round - 1).NumChains + currentNumChains := d.gov.Configuration(block.Position.Round).NumChains + if previousNumChains != currentNumChains { + deliveredRound, err := rawdb.ReadLastRoundNumber(d.chainDB) + if err != nil { + panic(fmt.Errorf("read current round error: %v", err)) + } + + if deliveredRound < block.Position.Round { + if len(block.Payload) > 0 { + return coreTypes.VerifyInvalidBlock + } + + return coreTypes.VerifyOK + } + } + } + // Get latest pending state. b, latestState := d.blockchain.GetPending() log.Debug("Verify block", "chain", block.Position.ChainID, "height", block.Position.Height, "state", @@ -482,10 +518,18 @@ func (d *DexconApp) BlockDelivered( }, txs, nil, nil) h := d.blockchain.CurrentBlock().NumberU64() + 1 - _, err = d.blockchain.ProcessPendingBlock(newBlock, &block.Witness) - if err != nil { - log.Error("Failed to process pending block", "error", err) - panic(err) + if block.IsEmpty() { + err = d.blockchain.ProcessEmptyBlock(newBlock) + if err != nil { + log.Error("Failed to process empty block", "error", err) + panic(err) + } + } else { + _, err = d.blockchain.ProcessPendingBlock(newBlock, &block.Witness) + if err != nil { + log.Error("Failed to process pending block", "error", err) + panic(err) + } } d.blockchain.RemoveConfirmedBlock(chainID, blockHash) diff --git a/dex/app_test.go b/dex/app_test.go index 7fc4933de..f4cc2fd9a 100644 --- a/dex/app_test.go +++ b/dex/app_test.go @@ -4,12 +4,14 @@ import ( "crypto/ecdsa" "fmt" "math/big" + "strings" "testing" "time" coreCommon "github.com/dexon-foundation/dexon-consensus/common" coreTypes "github.com/dexon-foundation/dexon-consensus/core/types" + "github.com/dexon-foundation/dexon/accounts/abi" "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/consensus/dexcon" "github.com/dexon-foundation/dexon/core" @@ -158,6 +160,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -174,6 +177,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 1, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -190,6 +194,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 2 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 100) if err != nil { t.Errorf("prepare data error: %v", err) @@ -206,6 +211,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Payload, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 10000) if err != nil { t.Errorf("prepare data error: %v", err) @@ -222,6 +228,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -256,6 +263,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -290,6 +298,7 @@ func TestVerifyBlock(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Position.ChainID = uint32(chainID.Uint64()) block.Position.Height = 1 + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} _, block.Witness, err = prepareDataWithoutTxPool(dex, key, 0, 0) if err != nil { t.Errorf("prepare data error: %v", err) @@ -368,6 +377,7 @@ func TestBlockConfirmed(t *testing.T) { block.Hash = coreCommon.NewRandomHash() block.Witness = witness block.Payload = payload + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} block.Position.ChainID = uint32(chainID.Uint64()) dex.app.BlockConfirmed(*block) @@ -463,6 +473,147 @@ func TestBlockDelivered(t *testing.T) { } } +func TestNumChainsChange(t *testing.T) { + key, err := crypto.GenerateKey() + if err != nil { + t.Errorf("hex to ecdsa error: %v", err) + } + + params.TestnetChainConfig.Dexcon.Owner = crypto.PubkeyToAddress(key.PublicKey) + + dex, err := newTestDexonWithGenesis(key) + if err != nil { + t.Errorf("new test dexon error: %v", err) + } + + abiObject, err := abi.JSON(strings.NewReader(vm.GovernanceABIJSON)) + if err != nil { + t.Errorf("get abi object fail: %v", err) + } + + // Update config in round 1 and height 1. + // Config will affect in round 3. + input, err := abiObject.Pack("updateConfiguration", + new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1e4)), big.NewInt(2000), + big.NewInt(1e17), big.NewInt(9000000), big.NewInt(3), big.NewInt(500), big.NewInt(5000), + big.NewInt(1), big.NewInt(700000), big.NewInt(5), big.NewInt(5), big.NewInt(700000), big.NewInt(1000), + []*big.Int{big.NewInt(1), big.NewInt(1), big.NewInt(1)}) + if err != nil { + t.Errorf("updateConfiguration abiObject pack error: %v", err) + } + + block, err := prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{input}, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 1, + }) + + // Snapshot round on round 1 and height 2. + input, err = abiObject.Pack("snapshotRound", big.NewInt(1), big.NewInt(1)) + if err != nil { + t.Errorf("abiObject pack error: %v", err) + } + + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{input}, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 2, + }) + + // Make round 1 height 2 block write into chain. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, nil, 1) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 3, + }) + + // Round 2 will keep prepare tx as usual. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 2) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload == nil { + t.Errorf("payload should not be nil") + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 4, + }) + + // It will prepare empty payload until witness any block in round 3. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload != nil { + t.Errorf("payload should be nil but %v", block.Payload) + } + + // Test non-empty payload. + block.Payload = []byte{1} + if status := dex.app.VerifyBlock(block); status != coreTypes.VerifyInvalidBlock { + t.Errorf("unexpected verify status %v", status) + } + block.Payload = nil + + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 5, + }) + + // Still empty payload because round 3 is not witness yet. + // This block just for witness round 3 height 5. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload != nil { + t.Errorf("payload should be nil but %v", block.Payload) + } + + // Test non-empty payload. + block.Payload = []byte{1} + if status := dex.app.VerifyBlock(block); status != coreTypes.VerifyInvalidBlock { + t.Errorf("unexpected verify status %v", status) + } + block.Payload = nil + + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 6, + }) + + // Empty block in round 3 has been delivered. + // Now can prepare payload as usual. + block, err = prepareConfirmedBlockWithTxAndData(dex, key, [][]byte{{1}}, 3) + if err != nil { + t.Errorf("prepare block error: %v", err) + } + if block.Payload == nil { + t.Errorf("payload should not be nil") + } + dex.app.BlockDelivered(block.Hash, block.Position, + coreTypes.FinalizationResult{ + Timestamp: time.Now(), + Height: 7, + }) +} + func BenchmarkBlockDeliveredFlow(b *testing.B) { key, err := crypto.GenerateKey() if err != nil { @@ -594,6 +745,61 @@ func prepareData(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) ( return } +func prepareConfirmedBlockWithTxAndData(dex *Dexon, key *ecdsa.PrivateKey, data [][]byte, round uint64) ( + Block *coreTypes.Block, err error) { + address := crypto.PubkeyToAddress(key.PublicKey) + numChains := dex.governance.GetConfigHelper(round).Configuration().NumChains + chainID := new(big.Int).Mod(address.Big(), big.NewInt(int64(numChains))) + + for _, d := range data { + // Prepare one block for pending. + nonce := dex.txPool.State().GetNonce(address) + signer := types.NewEIP155Signer(dex.chainConfig.ChainID) + tx := types.NewTransaction(uint64(nonce), vm.GovernanceContractAddress, big.NewInt(0), params.TxGas*2, + big.NewInt(1), d) + tx, err = types.SignTx(tx, signer, key) + if err != nil { + return nil, err + } + + dex.txPool.AddRemote(tx) + if err != nil { + return nil, err + } + } + var ( + payload []byte + witness coreTypes.Witness + ) + + payload, err = dex.app.PreparePayload(coreTypes.Position{Round: round, ChainID: uint32(chainID.Uint64())}) + if err != nil { + return + } + + witness, err = dex.app.PrepareWitness(0) + if err != nil { + return nil, err + } + + block := &coreTypes.Block{} + block.Hash = coreCommon.NewRandomHash() + block.Witness = witness + block.Payload = payload + block.Position.ChainID = uint32(chainID.Uint64()) + block.Position.Round = round + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} + + status := dex.app.VerifyBlock(block) + if status != coreTypes.VerifyOK { + err = fmt.Errorf("verify fail: %v", status) + return nil, err + } + + dex.app.BlockConfirmed(*block) + return block, nil +} + func prepareDataWithoutTxPool(dex *Dexon, key *ecdsa.PrivateKey, startNonce, txNum int) ( payload []byte, witness coreTypes.Witness, err error) { signer := types.NewEIP155Signer(dex.chainConfig.ChainID) @@ -656,6 +862,7 @@ func prepareConfirmedBlocks(dex *Dexon, keys []*ecdsa.PrivateKey, txNum int) (bl block.Witness = witness block.Payload = payload block.Position.ChainID = uint32(chainID.Uint64()) + block.ProposerID = coreTypes.NodeID{coreCommon.Hash{1, 2, 3}} status := dex.app.VerifyBlock(block) if status != coreTypes.VerifyOK { diff --git a/eth/helper_test.go b/eth/helper_test.go index 9a30afef3..e4d7a28fb 100644 --- a/eth/helper_test.go +++ b/eth/helper_test.go @@ -30,6 +30,7 @@ import ( "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/consensus/ethash" "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/rawdb" "github.com/dexon-foundation/dexon/core/types" "github.com/dexon-foundation/dexon/core/vm" "github.com/dexon-foundation/dexon/crypto" @@ -61,6 +62,9 @@ func newTestProtocolManager(mode downloader.SyncMode, blocks int, generator func genesis = gspec.MustCommit(db) blockchain, _ = core.NewBlockChain(db, nil, gspec.Config, engine, vm.Config{}, nil) ) + if err := rawdb.WriteLastRoundNumber(db, 0); err != nil { + panic(err) + } chain, _ := core.GenerateChain(gspec.Config, genesis, ethash.NewFaker(), db, blocks, generator) if _, err := blockchain.InsertChain(chain); err != nil { panic(err) |