aboutsummaryrefslogtreecommitdiffstats
path: root/dex/downloader
diff options
context:
space:
mode:
Diffstat (limited to 'dex/downloader')
-rw-r--r--dex/downloader/downloader.go6
-rw-r--r--dex/downloader/downloader_test.go68
-rw-r--r--dex/downloader/testchain_test.go175
3 files changed, 217 insertions, 32 deletions
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
+}