From bbb1ebede10cd691de4ef2fe4bf276d2fa357a31 Mon Sep 17 00:00:00 2001 From: Sonic Date: Thu, 24 Jan 2019 10:38:28 +0800 Subject: core, dex/downloader: polish headers verification and blocks insertion logic (#168) Refactor GenerateDexonChain function, move governance tx logic to the user of GenerateDexonChain (testchain_test.go) and move fake node set code to FakeDexcon. --- dex/downloader/downloader.go | 6 +- dex/downloader/downloader_test.go | 68 ++++++++++++++- dex/downloader/testchain_test.go | 175 +++++++++++++++++++++++++++++++------- 3 files changed, 217 insertions(+), 32 deletions(-) (limited to 'dex/downloader') diff --git a/dex/downloader/downloader.go b/dex/downloader/downloader.go index 6ff8c122e..e15844668 100644 --- a/dex/downloader/downloader.go +++ b/dex/downloader/downloader.go @@ -27,6 +27,7 @@ import ( ethereum "github.com/dexon-foundation/dexon" "github.com/dexon-foundation/dexon/common" + "github.com/dexon-foundation/dexon/consensus/dexcon" "github.com/dexon-foundation/dexon/core/rawdb" "github.com/dexon-foundation/dexon/core/state" "github.com/dexon-foundation/dexon/core/types" @@ -177,7 +178,8 @@ type LightChain interface { GetGovStateByNumber(number uint64) (*types.GovState, error) // InsertDexonHeaderChain inserts a batch of headers into the local chain. - InsertDexonHeaderChain([]*types.HeaderWithGovState, *dexCore.TSigVerifierCache) (int, error) + InsertDexonHeaderChain([]*types.HeaderWithGovState, + dexcon.GovernanceStateFetcher, *dexCore.TSigVerifierCache) (int, error) // Rollback removes a few recently added elements from the local chain. Rollback([]common.Hash) @@ -1428,7 +1430,7 @@ func (d *Downloader) processHeaders(origin uint64, pivot uint64, number uint64) } } - if n, err := d.lightchain.InsertDexonHeaderChain(chunk, d.verifierCache); err != nil { + if n, err := d.lightchain.InsertDexonHeaderChain(chunk, d.gov, d.verifierCache); err != nil { // If some headers were inserted, add them too to the rollback list if n > 0 { for _, h := range chunk[:n] { diff --git a/dex/downloader/downloader_test.go b/dex/downloader/downloader_test.go index 0d92ad97f..80993bd75 100644 --- a/dex/downloader/downloader_test.go +++ b/dex/downloader/downloader_test.go @@ -26,12 +26,16 @@ import ( ethereum "github.com/dexon-foundation/dexon" dexCore "github.com/dexon-foundation/dexon-consensus/core" + 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/state" "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/event" + "github.com/dexon-foundation/dexon/rlp" "github.com/dexon-foundation/dexon/trie" ) @@ -200,7 +204,8 @@ func (dl *downloadTester) FastSyncCommitHead(hash common.Hash) error { } // InsertDexonHeaderChain injects a new batch of headers into the simulated chain. -func (dl *downloadTester) InsertDexonHeaderChain(headers []*types.HeaderWithGovState, verifierCache *dexCore.TSigVerifierCache) (i int, err error) { +func (dl *downloadTester) InsertDexonHeaderChain(headers []*types.HeaderWithGovState, + gov dexcon.GovernanceStateFetcher, verifierCache *dexCore.TSigVerifierCache) (i int, err error) { dl.lock.Lock() defer dl.lock.Unlock() @@ -221,6 +226,29 @@ func (dl *downloadTester) InsertDexonHeaderChain(headers []*types.HeaderWithGovS if _, ok := dl.ownHeaders[header.ParentHash]; !ok { return i, errors.New("unknown parent") } + + // Verify witness + var coreBlock coreTypes.Block + if err := rlp.DecodeBytes(header.DexconMeta, &coreBlock); err != nil { + return i, err + } + + var witnessBlockHash common.Hash + if err := rlp.DecodeBytes(coreBlock.Witness.Data, &witnessBlockHash); err != nil { + return i, err + } + + if uint64(len(dl.ownHashes)) < coreBlock.Witness.Height+1 { + return i, errors.New("unknown witness") + } + h := dl.ownHeaders[dl.ownHashes[coreBlock.Witness.Height]] + if h == nil { + return i, errors.New("unknown witness") + } + + if h.Hash() != witnessBlockHash { + return i, errors.New("witness root mismatch") + } dl.ownHashes = append(dl.ownHashes, header.Hash()) dl.ownHeaders[header.Hash()] = header.Header } @@ -807,6 +835,7 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { missing := fsHeaderSafetyNet + MaxHeaderFetch + 1 fastAttackChain := chain.shorten(chain.len()) delete(fastAttackChain.headerm, fastAttackChain.chain[missing]) + tester.newPeer("fast-attack", protocol, fastAttackChain) if err := tester.sync("fast-attack", 0, mode); err == nil { @@ -860,6 +889,43 @@ func testInvalidHeaderRollback(t *testing.T, protocol int, mode SyncMode) { } } + // witness mismatch + realChain := chain.shorten(chain.len()) + fakeChain := chain.shorten(chain.len()) + + for i := chain.len() - 100; i < chain.len(); i++ { + realHash := realChain.chain[i] + realHeader := realChain.headerm[realHash] + realBlock := realChain.blockm[realHash] + realReceipt := realChain.receiptm[realHash] + fakeHeader := types.CopyHeader(realHeader) + if i == chain.len()-100 { + fakeHeader.Root = common.Hash{} + } else { + fakeHeader.ParentHash = fakeChain.chain[i-1] + } + + fakeBlock := types.NewBlock(fakeHeader, realBlock.Transactions(), realBlock.Uncles(), realReceipt) + + fakeChain.chain[i] = fakeBlock.Hash() + fakeChain.blockm[fakeBlock.Hash()] = fakeBlock + fakeChain.headerm[fakeBlock.Hash()] = fakeBlock.Header() + fakeChain.receiptm[fakeBlock.Hash()] = realReceipt + } + + tester.newPeer("mismatch-attack", protocol, fakeChain) + if err := tester.sync("mismatch-attack", 0, mode); err == nil { + t.Fatalf("succeeded block attacker synchronisation") + } + if head := tester.CurrentHeader().Number.Int64(); int(head) > 2*fsHeaderSafetyNet+MaxHeaderFetch { + t.Errorf("rollback head mismatch: have %v, want at most %v", head, 2*fsHeaderSafetyNet+MaxHeaderFetch) + } + if mode == FastSync { + if head := tester.CurrentBlock().NumberU64(); head != 0 { + t.Errorf("fast sync pivot block #%d not rolled back", head) + } + } + // synchronise with the valid peer and make sure sync succeeds. Since the last rollback // should also disable fast syncing for this process, verify that we did a fresh full // sync. Note, we can't assert anything about the receipts since we won't purge the diff --git a/dex/downloader/testchain_test.go b/dex/downloader/testchain_test.go index 810fb41b3..d96ebcfbf 100644 --- a/dex/downloader/testchain_test.go +++ b/dex/downloader/testchain_test.go @@ -17,11 +17,12 @@ package downloader import ( + "crypto/ecdsa" "fmt" "math/big" "github.com/dexon-foundation/dexon/common" - "github.com/dexon-foundation/dexon/consensus/ethash" + "github.com/dexon-foundation/dexon/consensus/dexcon" "github.com/dexon-foundation/dexon/core" "github.com/dexon-foundation/dexon/core/state" "github.com/dexon-foundation/dexon/core/types" @@ -33,17 +34,17 @@ import ( // Test chain parameters. var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddress = crypto.PubkeyToAddress(testKey.PublicKey) - testDB = ethdb.NewMemDatabase() - testGenesis = genesisBlockForTesting(testDB, testAddress, big.NewInt(1000000000)) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddress = crypto.PubkeyToAddress(testKey.PublicKey) + testDB = ethdb.NewMemDatabase() + testGenesis, testNodes = genesisBlockForTesting(testDB, testAddress, big.NewInt(1000000000)) ) // The common prefix of all test chains: -var testChainBase = newTestChain(blockCacheItems+200, testGenesis) +var testChainBase = newTestChain(blockCacheItems+200, testGenesis, testNodes) func genesisBlockForTesting(db ethdb.Database, - addr common.Address, balance *big.Int) *types.Block { + addr common.Address, balance *big.Int) (*types.Block, *dexcon.NodeSet) { var ( // genesis node set nodekey1, _ = crypto.HexToECDSA("3cf5bdee098cc34536a7b0e80d85e07a380efca76fc12136299b9e5ba24193c8") @@ -87,11 +88,17 @@ func genesisBlockForTesting(db ethdb.Database, }, } genesis := gspec.MustCommit(db) - return genesis + + signedCRS := []byte(gspec.Config.Dexcon.GenesisCRSText) + signer := types.NewEIP155Signer(gspec.Config.ChainID) + nodeSet := dexcon.NewNodeSet(uint64(0), signedCRS, signer, + []*ecdsa.PrivateKey{nodekey1, nodekey2, nodekey3, nodekey4}) + return genesis, nodeSet } type testChain struct { genesis *types.Block + nodes *dexcon.NodeSet chain []common.Hash headerm map[common.Hash]*types.Header blockm map[common.Hash]*types.Block @@ -99,23 +106,16 @@ type testChain struct { } // newTestChain creates a blockchain of the given length. -func newTestChain(length int, genesis *types.Block) *testChain { +func newTestChain(length int, genesis *types.Block, nodes *dexcon.NodeSet) *testChain { tc := new(testChain).copy(length) tc.genesis = genesis tc.chain = append(tc.chain, genesis.Hash()) tc.headerm[tc.genesis.Hash()] = tc.genesis.Header() tc.blockm[tc.genesis.Hash()] = tc.genesis - tc.generate(length-1, 0, genesis, false) + tc.generate(length-1, 0, genesis, nodes, false) return tc } -// makeFork creates a fork on top of the test chain. -func (tc *testChain) makeFork(length int, heavy bool, seed byte) *testChain { - fork := tc.copy(tc.len() + length) - fork.generate(length, seed, tc.headBlock(), heavy) - return fork -} - // shorten creates a copy of the chain with the given length. It panics if the // length is longer than the number of available blocks. func (tc *testChain) shorten(length int) *testChain { @@ -146,16 +146,113 @@ func (tc *testChain) copy(newlen int) *testChain { // the returned hash chain is ordered head->parent. In addition, every 22th block // contains a transaction and every 5th an uncle to allow testing correct block // reassembly. -func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) { +func (tc *testChain) generate(n int, seed byte, parent *types.Block, nodes *dexcon.NodeSet, heavy bool) { // start := time.Now() // defer func() { fmt.Printf("test chain generated in %v\n", time.Since(start)) }() - blocks, receipts := core.GenerateChain(params.TestnetChainConfig, parent, ethash.NewFaker(), testDB, n, func(i int, block *core.BlockGen) { + engine := dexcon.NewFaker(testNodes) + govFetcher := newGovStateFetcher(state.NewDatabase(testDB)) + govFetcher.SnapshotRound(0, tc.genesis.Root()) + engine.SetGovStateFetcher(govFetcher) + + round := uint64(0) + roundInterval := int(100) + + addTx := func(block *core.DexonBlockGen, node *dexcon.Node, data []byte) { + nonce := block.TxNonce(node.Address()) + tx := node.CreateGovTx(nonce, data) + block.AddTx(tx) + } + + blocks, receipts := core.GenerateDexonChain(params.TestnetChainConfig, parent, engine, testDB, n, func(i int, block *core.DexonBlockGen) { block.SetCoinbase(common.Address{seed}) - // If a heavy chain is requested, delay blocks to raise difficulty - if heavy { - block.OffsetTime(-1) + if round == 0 { + switch i { + case 1: + testNodes.RunDKG(round, 2) + // Add DKG MasterPublicKeys + for _, node := range testNodes.Nodes(round) { + data, err := vm.PackAddDKGMasterPublicKey(round, node.MasterPublicKey(round)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case 2: + // Add DKG MPKReady + for _, node := range testNodes.Nodes(round) { + data, err := vm.PackAddDKGMPKReady(round, node.DKGMPKReady(round)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case 3: + // Add DKG Finalize + for _, node := range testNodes.Nodes(round) { + data, err := vm.PackAddDKGFinalize(round, node.DKGFinalize(round)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + } } + + half := roundInterval / 2 + switch i % roundInterval { + case 0: + if round > 0 { + node := testNodes.Nodes(round)[0] + data, err := vm.PackNotifyRoundHeight(round, uint64(i)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case half: + // Sign current CRS to geneate the next round CRS and propose it. + testNodes.SignCRS(round) + node := testNodes.Nodes(round)[0] + data, err := vm.PackProposeCRS(round, testNodes.SignedCRS(round+1)) + if err != nil { + panic(err) + } + addTx(block, node, data) + case half + 1: + // Run the DKG for next round. + testNodes.RunDKG(round+1, 2) + + // Add DKG MasterPublicKeys + for _, node := range testNodes.Nodes(round + 1) { + data, err := vm.PackAddDKGMasterPublicKey(round+1, node.MasterPublicKey(round+1)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case half + 2: + // Add DKG MPKReady + for _, node := range testNodes.Nodes(round + 1) { + data, err := vm.PackAddDKGMPKReady(round+1, node.DKGMPKReady(round+1)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case half + 3: + // Add DKG Finalize + for _, node := range testNodes.Nodes(round + 1) { + data, err := vm.PackAddDKGFinalize(round+1, node.DKGFinalize(round+1)) + if err != nil { + panic(err) + } + addTx(block, node, data) + } + case roundInterval - 1: + round++ + } + // Include transactions to the miner to make blocks more interesting. if parent == tc.genesis && i%22 == 0 { signer := types.MakeSigner(params.TestnetChainConfig, block.Number()) @@ -165,13 +262,6 @@ func (tc *testChain) generate(n int, seed byte, parent *types.Block, heavy bool) } block.AddTx(tx) } - // if the block number is a multiple of 5, add a bonus uncle to the block - if i > 0 && i%5 == 0 { - block.AddUncle(&types.Header{ - ParentHash: block.PrevBlock(i - 1).Hash(), - Number: big.NewInt(block.Number().Int64() - 1), - }) - } }) // Convert the block-chain into a hash-chain and header/block maps @@ -258,3 +348,30 @@ func (tc *testChain) hashToNumber(target common.Hash) (uint64, bool) { } return 0, false } + +type govStateFetcher struct { + db state.Database + rootByRound map[uint64]common.Hash +} + +func newGovStateFetcher(db state.Database) *govStateFetcher { + return &govStateFetcher{ + db: db, + rootByRound: make(map[uint64]common.Hash), + } +} + +func (g *govStateFetcher) SnapshotRound(round uint64, root common.Hash) { + g.rootByRound[round] = root +} + +func (g *govStateFetcher) GetGovStateHelperAtRound(round uint64) *vm.GovernanceStateHelper { + if root, ok := g.rootByRound[round]; ok { + s, err := state.New(root, g.db) + if err != nil { + panic(err) + } + return &vm.GovernanceStateHelper{s} + } + return nil +} -- cgit