aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSonic <sonic@dexon.org>2019-01-24 10:38:28 +0800
committerWei-Ning Huang <w@dexon.org>2019-04-09 21:32:56 +0800
commitbbb1ebede10cd691de4ef2fe4bf276d2fa357a31 (patch)
treec046b5f369c85215761d2db1e4ff890a478ceab4
parentd79158954a8cea9e14311e9783de82fdbd7a8888 (diff)
downloaddexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar.gz
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar.bz2
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar.lz
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar.xz
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.tar.zst
dexon-bbb1ebede10cd691de4ef2fe4bf276d2fa357a31.zip
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.
-rw-r--r--consensus/dexcon/fake_dexcon.go300
-rw-r--r--consensus/errors.go2
-rw-r--r--core/block_validator.go9
-rw-r--r--core/blockchain.go44
-rw-r--r--core/blockchain_test.go4
-rw-r--r--core/chain_makers.go476
-rw-r--r--core/dexon_chain_makers.go312
-rw-r--r--core/headerchain.go144
-rw-r--r--core/types/block.go11
-rw-r--r--core/vm/governance.go123
-rw-r--r--dex/downloader/downloader.go6
-rw-r--r--dex/downloader/downloader_test.go68
-rw-r--r--dex/downloader/testchain_test.go175
-rw-r--r--dex/governance.go94
14 files changed, 1143 insertions, 625 deletions
diff --git a/consensus/dexcon/fake_dexcon.go b/consensus/dexcon/fake_dexcon.go
new file mode 100644
index 000000000..ca48b5275
--- /dev/null
+++ b/consensus/dexcon/fake_dexcon.go
@@ -0,0 +1,300 @@
+package dexcon
+
+import (
+ "crypto/ecdsa"
+ "math/big"
+ "time"
+
+ coreCommon "github.com/dexon-foundation/dexon-consensus/common"
+ coreCrypto "github.com/dexon-foundation/dexon-consensus/core/crypto"
+ coreDKG "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg"
+ coreEcdsa "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa"
+ coreTypes "github.com/dexon-foundation/dexon-consensus/core/types"
+ coreTypesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"
+ coreUtils "github.com/dexon-foundation/dexon-consensus/core/utils"
+
+ "github.com/dexon-foundation/dexon/common"
+ "github.com/dexon-foundation/dexon/consensus"
+ "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/rlp"
+)
+
+type FakeDexcon struct {
+ *Dexcon
+ nodes *NodeSet
+}
+
+func NewFaker(nodes *NodeSet) *FakeDexcon {
+ return &FakeDexcon{
+ Dexcon: New(),
+ nodes: nodes,
+ }
+}
+
+func (f *FakeDexcon) Prepare(chain consensus.ChainReader, header *types.Header) error {
+ var coreBlock coreTypes.Block
+ if err := rlp.DecodeBytes(header.DexconMeta, &coreBlock); err != nil {
+ return err
+ }
+
+ blockHash, err := coreUtils.HashBlock(&coreBlock)
+ if err != nil {
+ return err
+ }
+
+ parentHeader := chain.GetHeaderByNumber(header.Number.Uint64() - 1)
+ var parentCoreBlock coreTypes.Block
+ if parentHeader.Number.Uint64() != 0 {
+ if err := rlp.DecodeBytes(
+ parentHeader.DexconMeta, &parentCoreBlock); err != nil {
+ return err
+ }
+ }
+
+ parentCoreBlockHash, err := coreUtils.HashBlock(&parentCoreBlock)
+ if err != nil {
+ return err
+ }
+ randomness := f.nodes.Randomness(header.Round, common.Hash(blockHash))
+ coreBlock.Finalization.ParentHash = parentCoreBlockHash
+ coreBlock.Finalization.Randomness = randomness
+ coreBlock.Finalization.Timestamp = time.Now().UTC()
+ coreBlock.Finalization.Height = parentHeader.Number.Uint64()
+
+ dexconMeta, err := rlp.EncodeToBytes(&coreBlock)
+ if err != nil {
+ return err
+ }
+ header.DexconMeta = dexconMeta
+ return nil
+}
+
+type Node struct {
+ cryptoKey coreCrypto.PrivateKey
+ ecdsaKey *ecdsa.PrivateKey
+
+ id coreTypes.NodeID
+ dkgid coreDKG.ID
+ address common.Address
+ prvShares *coreDKG.PrivateKeyShares
+ pubShares *coreDKG.PublicKeyShares
+ receivedPrvShares *coreDKG.PrivateKeyShares
+ recoveredPrivateKey *coreDKG.PrivateKey
+ signer *coreUtils.Signer
+ txSigner types.Signer
+
+ mpk *coreTypesDKG.MasterPublicKey
+}
+
+func newNode(privkey *ecdsa.PrivateKey, txSigner types.Signer) *Node {
+ k := coreEcdsa.NewPrivateKeyFromECDSA(privkey)
+ id := coreTypes.NewNodeID(k.PublicKey())
+ return &Node{
+ cryptoKey: k,
+ ecdsaKey: privkey,
+ id: id,
+ dkgid: coreDKG.NewID(id.Bytes()),
+ address: crypto.PubkeyToAddress(privkey.PublicKey),
+ signer: coreUtils.NewSigner(k),
+ txSigner: txSigner,
+ }
+}
+
+func (n *Node) ID() coreTypes.NodeID { return n.id }
+func (n *Node) DKGID() coreDKG.ID { return n.dkgid }
+func (n *Node) Address() common.Address { return n.address }
+
+func (n *Node) MasterPublicKey(round uint64) *coreTypesDKG.MasterPublicKey {
+ mpk := &coreTypesDKG.MasterPublicKey{
+ ProposerID: n.ID(),
+ Round: round,
+ DKGID: n.DKGID(),
+ PublicKeyShares: *n.pubShares,
+ }
+
+ if err := n.signer.SignDKGMasterPublicKey(mpk); err != nil {
+ panic(err)
+ }
+ return mpk
+}
+
+func (n *Node) DKGMPKReady(round uint64) *coreTypesDKG.MPKReady {
+ ready := &coreTypesDKG.MPKReady{
+ ProposerID: n.ID(),
+ Round: round,
+ }
+
+ if err := n.signer.SignDKGMPKReady(ready); err != nil {
+ panic(err)
+ }
+ return ready
+}
+
+func (n *Node) DKGFinalize(round uint64) *coreTypesDKG.Finalize {
+ final := &coreTypesDKG.Finalize{
+ ProposerID: n.ID(),
+ Round: round,
+ }
+
+ if err := n.signer.SignDKGFinalize(final); err != nil {
+ panic(err)
+ }
+ return final
+}
+
+func (n *Node) CreateGovTx(nonce uint64, data []byte) *types.Transaction {
+ tx, err := types.SignTx(types.NewTransaction(
+ nonce,
+ vm.GovernanceContractAddress,
+ big.NewInt(0),
+ uint64(2000000),
+ big.NewInt(1e10),
+ data), n.txSigner, n.ecdsaKey)
+ if err != nil {
+ panic(err)
+ }
+ return tx
+}
+
+type NodeSet struct {
+ signer types.Signer
+ privkeys []*ecdsa.PrivateKey
+ nodes map[uint64][]*Node
+ crs map[uint64]common.Hash
+ signedCRS map[uint64][]byte
+}
+
+func NewNodeSet(round uint64, signedCRS []byte, signer types.Signer,
+ privkeys []*ecdsa.PrivateKey) *NodeSet {
+ n := &NodeSet{
+ signer: signer,
+ privkeys: privkeys,
+ nodes: make(map[uint64][]*Node),
+ crs: make(map[uint64]common.Hash),
+ signedCRS: make(map[uint64][]byte),
+ }
+ n.signedCRS[round] = signedCRS
+ n.crs[round] = crypto.Keccak256Hash(signedCRS)
+ return n
+}
+
+func (n *NodeSet) Nodes(round uint64) []*Node {
+ if nodes, ok := n.nodes[round]; ok {
+ return nodes
+ }
+ panic("dkg not ready")
+}
+
+func (n *NodeSet) CRS(round uint64) common.Hash {
+ if c, ok := n.crs[round]; ok {
+ return c
+ }
+ panic("crs not exist")
+}
+
+func (n *NodeSet) SignedCRS(round uint64) []byte {
+ if c, ok := n.signedCRS[round]; ok {
+ return c
+ }
+ panic("signedCRS not exist")
+}
+
+// Assume All nodes in NodeSet are in DKG Set too.
+func (n *NodeSet) RunDKG(round uint64, threshold int) {
+ var ids coreDKG.IDs
+ var nodes []*Node
+ for _, key := range n.privkeys {
+ node := newNode(key, n.signer)
+ nodes = append(nodes, node)
+ ids = append(ids, node.DKGID())
+ }
+
+ for _, node := range nodes {
+ node.prvShares, node.pubShares = coreDKG.NewPrivateKeyShares(threshold)
+ node.prvShares.SetParticipants(ids)
+ node.receivedPrvShares = coreDKG.NewEmptyPrivateKeyShares()
+ }
+
+ // exchange keys
+ for _, sender := range nodes {
+ for _, receiver := range nodes {
+ // no need to verify
+ prvShare, ok := sender.prvShares.Share(receiver.DKGID())
+ if !ok {
+ panic("not ok")
+ }
+ receiver.receivedPrvShares.AddShare(sender.DKGID(), prvShare)
+ }
+ }
+
+ // recover private key
+ for _, node := range nodes {
+ privKey, err := node.receivedPrvShares.RecoverPrivateKey(ids)
+ if err != nil {
+ panic(err)
+ }
+ node.recoveredPrivateKey = privKey
+ }
+
+ // store these nodes
+ n.nodes[round] = nodes
+}
+
+func (n *NodeSet) Randomness(round uint64, hash common.Hash) []byte {
+ if round == 0 {
+ return []byte{}
+ }
+ return n.TSig(round, hash)
+}
+
+func (n *NodeSet) SignCRS(round uint64) {
+ signedCRS := n.TSig(round, n.crs[round])
+ n.signedCRS[round+1] = signedCRS
+ n.crs[round+1] = crypto.Keccak256Hash(signedCRS)
+}
+
+func (n *NodeSet) TSig(round uint64, hash common.Hash) []byte {
+ var ids coreDKG.IDs
+ var psigs []coreDKG.PartialSignature
+ for _, node := range n.nodes[round] {
+ ids = append(ids, node.DKGID())
+ }
+ for _, node := range n.nodes[round] {
+ sig, err := node.recoveredPrivateKey.Sign(coreCommon.Hash(hash))
+ if err != nil {
+ panic(err)
+ }
+ psigs = append(psigs, coreDKG.PartialSignature(sig))
+ // ids = append(ids, node.DKGID())
+
+ // FIXME: Debug verify signature
+ pk := coreDKG.NewEmptyPublicKeyShares()
+ for _, nnode := range n.nodes[round] {
+ p, err := nnode.pubShares.Share(node.DKGID())
+ if err != nil {
+ panic(err)
+ }
+ err = pk.AddShare(nnode.DKGID(), p)
+ if err != nil {
+ panic(err)
+ }
+ }
+
+ recovered, err := pk.RecoverPublicKey(ids)
+ if err != nil {
+ panic(err)
+ }
+
+ if !recovered.VerifySignature(coreCommon.Hash(hash), sig) {
+ panic("##########can not verify signature")
+ }
+ }
+
+ sig, err := coreDKG.RecoverSignature(psigs, ids)
+ if err != nil {
+ panic(err)
+ }
+ return sig.Signature
+}
diff --git a/consensus/errors.go b/consensus/errors.go
index a005c5f63..204f09f21 100644
--- a/consensus/errors.go
+++ b/consensus/errors.go
@@ -34,4 +34,6 @@ var (
// ErrInvalidNumber is returned if a block's number doesn't equal it's parent's
// plus one.
ErrInvalidNumber = errors.New("invalid block number")
+
+ ErrWitnessMismatch = errors.New("witness mismatch")
)
diff --git a/core/block_validator.go b/core/block_validator.go
index f42ea6b11..856eec27b 100644
--- a/core/block_validator.go
+++ b/core/block_validator.go
@@ -23,6 +23,7 @@ import (
"github.com/dexon-foundation/dexon/consensus"
"github.com/dexon-foundation/dexon/core/state"
"github.com/dexon-foundation/dexon/core/types"
+ "github.com/dexon-foundation/dexon/log"
"github.com/dexon-foundation/dexon/params"
)
@@ -103,14 +104,16 @@ func (v *BlockValidator) ValidateState(block, parent *types.Block, statedb *stat
}
func (v *BlockValidator) ValidateWitnessData(height uint64, blockHash common.Hash) error {
- b := v.bc.GetBlockByNumber(height)
+ b := v.bc.GetHeaderByNumber(height)
if b == nil {
- return fmt.Errorf("can not find block %v either pending or confirmed block", height)
+ log.Error("can not find block %v either pending or confirmed block", height)
+ return consensus.ErrWitnessMismatch
}
if b.Hash() != blockHash {
- return fmt.Errorf("invalid witness block %s vs %s",
+ log.Error("invalid witness block %s vs %s",
b.Hash().String(), blockHash.String())
+ return consensus.ErrWitnessMismatch
}
return nil
}
diff --git a/core/blockchain.go b/core/blockchain.go
index 8bbf6e809..5e3b4b30a 100644
--- a/core/blockchain.go
+++ b/core/blockchain.go
@@ -38,6 +38,7 @@ import (
"github.com/dexon-foundation/dexon/common/mclock"
"github.com/dexon-foundation/dexon/common/prque"
"github.com/dexon-foundation/dexon/consensus"
+ "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"
@@ -145,6 +146,7 @@ type BlockChain struct {
roundHeightMap sync.Map
+ gov *Governance
verifierCache *dexCore.TSigVerifierCache
confirmedBlockInitMu sync.Mutex
@@ -222,8 +224,8 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
}
}
- gov := NewGovernance(NewGovernanceStateDB(bc))
- bc.verifierCache = dexCore.NewTSigVerifierCache(gov, 5)
+ bc.gov = NewGovernance(NewGovernanceStateDB(bc))
+ bc.verifierCache = dexCore.NewTSigVerifierCache(bc.gov, 5)
// Init round height map
curblock := bc.CurrentBlock()
@@ -236,7 +238,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
log.Debug("Init round height", "height", curblock.NumberU64(), "round", curblock.Round())
bc.storeRoundHeight(uint64(0), uint64(0))
} else {
- prevh := gov.GetRoundHeight(r - 1)
+ prevh := bc.gov.GetRoundHeight(r - 1)
if prevh == uint64(0) && (r-1) != uint64(0) {
// Previous round height should be already snapshoted
// in governance state at this moment.
@@ -245,7 +247,7 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par
log.Debug("Init previous round height", "height", prevh, "round", r-1)
bc.storeRoundHeight(r-1, prevh)
- curh := gov.GetRoundHeight(r)
+ curh := bc.gov.GetRoundHeight(r)
// Current round height is not snapshoted in governance state yet,
if curh == uint64(0) {
@@ -1658,7 +1660,8 @@ func (bc *BlockChain) insertDexonChain(chain types.Blocks) (int, []interface{},
// Wait for the block's verification to complete
bstart := time.Now()
- err := bc.hc.verifyTSig(block.Header(), bc.verifierCache)
+ // VerifyDexonHeader will verify tsig, witness and ensure dexon header is correct.
+ err := bc.hc.VerifyDexonHeader(block.Header(), bc.gov, bc.verifierCache, bc.Validator())
if err == nil {
err = bc.Validator().ValidateBody(block)
}
@@ -1753,8 +1756,10 @@ func (bc *BlockChain) insertDexonChain(chain types.Blocks) (int, []interface{},
chainBlock := bc.GetBlockByNumber(block.NumberU64())
if chainBlock != nil {
if chainBlock.Hash() != block.Hash() {
- return i, nil, nil, fmt.Errorf("block %v exist but hash is not equal: exist %v expect %v", block.NumberU64(),
- chainBlock.Hash(), block.Hash())
+ err := fmt.Errorf("block at %d exists but hash is not equal: exist %v expect %v",
+ block.NumberU64(), chainBlock.NumberU64(), block.Hash())
+ bc.reportBlock(block, nil, fmt.Errorf("%v (new block)", err))
+ bc.reportBlock(chainBlock, nil, fmt.Errorf("%v (old block)", err))
}
continue
@@ -1807,7 +1812,7 @@ func (bc *BlockChain) insertDexonChain(chain types.Blocks) (int, []interface{},
}
func (bc *BlockChain) VerifyDexonHeader(header *types.Header) error {
- return bc.hc.verifyTSig(header, bc.verifierCache)
+ return bc.hc.VerifyDexonHeader(header, bc.gov, bc.verifierCache, bc.Validator())
}
func (bc *BlockChain) ProcessBlock(block *types.Block, witness *coreTypes.Witness) (*common.Hash, error) {
@@ -1834,6 +1839,7 @@ func (bc *BlockChain) processBlock(
)
bstart := time.Now()
+
var witnessBlockHash common.Hash
if err := rlp.Decode(bytes.NewReader(witness.Data), &witnessBlockHash); err != nil {
log.Error("Witness rlp decode failed", "error", err)
@@ -1841,7 +1847,7 @@ func (bc *BlockChain) processBlock(
}
if err := bc.Validator().ValidateWitnessData(witness.Height, witnessBlockHash); err != nil {
- return nil, nil, nil, fmt.Errorf("validate witness data error: %v", err)
+ return nil, nil, nil, err
}
var (
@@ -1891,10 +1897,12 @@ func (bc *BlockChain) processBlock(
chainBlock := bc.GetBlockByNumber(newBlock.NumberU64())
if chainBlock != nil {
if chainBlock.Hash() != newBlock.Hash() {
- return nil, nil, nil, fmt.Errorf("block %v exist but hash is not equal: exist %v but get %v", newBlock.NumberU64(),
- chainBlock.Hash(), newBlock.Hash())
+ err := fmt.Errorf("block at %d exists but hash is not equal: exist %v expect %v",
+ newBlock.NumberU64(), chainBlock.NumberU64(), newBlock.Hash())
+ bc.reportBlock(chainBlock, nil, fmt.Errorf("%v (remote inserted block)", err))
+ bc.reportBlock(newBlock, receipts, fmt.Errorf("%v (local delivered block)", err))
+ return nil, nil, nil, err
}
-
return &root, nil, nil, nil
}
@@ -1983,8 +1991,11 @@ func (bc *BlockChain) ProcessEmptyBlock(block *types.Block) (*common.Hash, error
chainBlock := bc.GetBlockByNumber(newBlock.NumberU64())
if chainBlock != nil {
if chainBlock.Hash() != newBlock.Hash() {
- return nil, fmt.Errorf("block %v exist but hash is not equal: exist %v expect %v", newBlock.NumberU64(),
- chainBlock.Hash(), newBlock.Hash())
+ err := fmt.Errorf("block at %d exists but hash is not equal: exist %v expect %v",
+ newBlock.NumberU64(), chainBlock.NumberU64(), newBlock.Hash())
+ bc.reportBlock(chainBlock, nil, fmt.Errorf("%v (remote inserted block)", err))
+ bc.reportBlock(newBlock, nil, fmt.Errorf("%v (local delivered block)", err))
+ return nil, err
}
return &root, nil
@@ -2297,9 +2308,10 @@ func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (i
return bc.hc.InsertHeaderChain(chain, whFunc, start)
}
-func (bc *BlockChain) InsertDexonHeaderChain(chain []*types.HeaderWithGovState, verifierCache *dexCore.TSigVerifierCache) (int, error) {
+func (bc *BlockChain) InsertDexonHeaderChain(chain []*types.HeaderWithGovState,
+ gov dexcon.GovernanceStateFetcher, verifierCache *dexCore.TSigVerifierCache) (int, error) {
start := time.Now()
- if i, err := bc.hc.ValidateDexonHeaderChain(chain, verifierCache); err != nil {
+ if i, err := bc.hc.ValidateDexonHeaderChain(chain, gov, verifierCache, bc.Validator()); err != nil {
return i, err
}
diff --git a/core/blockchain_test.go b/core/blockchain_test.go
index 967d0d3c5..1f5197002 100644
--- a/core/blockchain_test.go
+++ b/core/blockchain_test.go
@@ -1729,7 +1729,7 @@ func TestProcessBlock(t *testing.T) {
Height: processNum + 1,
Data: witnessDataBytes,
})
- if err == nil || !strings.Contains(err.Error(), "can not find block") {
+ if err != consensus.ErrWitnessMismatch {
t.Fatalf("not expected fail: %v", err)
}
@@ -1748,7 +1748,7 @@ func TestProcessBlock(t *testing.T) {
Height: processNum - 1,
Data: witnessDataBytes,
})
- if err == nil || !strings.Contains(err.Error(), "invalid witness block") {
+ if err != consensus.ErrWitnessMismatch {
t.Fatalf("not expected fail: %v", err)
}
diff --git a/core/chain_makers.go b/core/chain_makers.go
index b464555d3..27aff215c 100644
--- a/core/chain_makers.go
+++ b/core/chain_makers.go
@@ -17,30 +17,19 @@
package core
import (
- "crypto/ecdsa"
- "encoding/binary"
"fmt"
"math/big"
"math/rand"
"time"
- coreCommon "github.com/dexon-foundation/dexon-consensus/common"
- coreCrypto "github.com/dexon-foundation/dexon-consensus/core/crypto"
- coreDKG "github.com/dexon-foundation/dexon-consensus/core/crypto/dkg"
- coreEcdsa "github.com/dexon-foundation/dexon-consensus/core/crypto/ecdsa"
- coreTypes "github.com/dexon-foundation/dexon-consensus/core/types"
- coreTypesDKG "github.com/dexon-foundation/dexon-consensus/core/types/dkg"
-
"github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/consensus"
"github.com/dexon-foundation/dexon/consensus/misc"
"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/crypto"
"github.com/dexon-foundation/dexon/ethdb"
"github.com/dexon-foundation/dexon/params"
- "github.com/dexon-foundation/dexon/rlp"
)
func init() {
@@ -265,137 +254,6 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S
}
}
-func GenerateChainWithRoundChange(config *params.ChainConfig, parent *types.Block,
- engine consensus.Engine, db ethdb.Database, n int, gen func(int, *BlockGen),
- nodeSet *NodeSet, roundInterval int) ([]*types.Block, []types.Receipts) {
- if config == nil {
- config = params.TestChainConfig
- }
-
- round := parent.Header().Round
-
- blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
- chainreader := &fakeChainReader{config: config}
- genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
- b := &BlockGen{i: i, parent: parent, chain: blocks, statedb: statedb, config: config, engine: engine}
- b.header = makeHeader(chainreader, parent, statedb, b.engine)
- b.header.DexconMeta = makeDexconMeta(round, parent, nodeSet)
-
- switch i % roundInterval {
- case 0:
- // First block of this round, notify round height
- tx := nodeSet.NotifyRoundHeightTx(round, b.header.Number.Uint64(), b)
- b.AddTx(tx)
- case roundInterval / 2:
- // Run DKG for next round part 1, AddMasterPublicKey
- nodeSet.RunDKG(round, 2)
- for _, node := range nodeSet.nodes[round] {
- tx := node.MasterPublicKeyTx(round, b.TxNonce(node.address))
- b.AddTx(tx)
- }
- case (roundInterval / 2) + 1:
- // Run DKG for next round part 2, DKG finalize
- for _, node := range nodeSet.nodes[round] {
- tx := node.DKGFinalizeTx(round, b.TxNonce(node.address))
- b.AddTx(tx)
- }
- case (roundInterval / 2) + 2:
- // Current DKG set create signed CRS for next round and propose it
- nodeSet.SignedCRS(round)
- tx := nodeSet.CRSTx(round+1, b)
- b.AddTx(tx)
- case roundInterval - 1:
- // Round change
- round++
- }
-
- // Execute any user modifications to the block and finalize it
- if gen != nil {
- gen(i, b)
- }
-
- if b.engine != nil {
- block, _ := b.engine.Finalize(chainreader, b.header, statedb, b.txs, b.uncles, b.receipts)
- // Write state changes to db
- root, err := statedb.Commit(config.IsEIP158(b.header.Number))
- if err != nil {
- panic(fmt.Sprintf("state write error: %v", err))
- }
- if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
- panic(fmt.Sprintf("trie write error: %v", err))
- }
- return block, b.receipts
- }
- return nil, nil
- }
- for i := 0; i < n; i++ {
- statedb, err := state.New(parent.Root(), state.NewDatabase(db))
- if err != nil {
- panic(err)
- }
- block, receipt := genblock(i, parent, statedb)
- blocks[i] = block
- receipts[i] = receipt
- parent = block
- }
- return blocks, receipts
-}
-
-type witnessData struct {
- Root common.Hash
- TxHash common.Hash
- ReceiptHash common.Hash
-}
-
-func makeDexconMeta(round uint64, parent *types.Block, nodeSet *NodeSet) []byte {
- data, err := rlp.EncodeToBytes(&witnessData{
- Root: parent.Root(),
- TxHash: parent.TxHash(),
- ReceiptHash: parent.ReceiptHash(),
- })
- if err != nil {
- panic(err)
- }
-
- // only put required data, ignore information for BA, ex: acks, votes
- coreBlock := coreTypes.Block{
- Witness: coreTypes.Witness{
- Height: parent.Number().Uint64(),
- Data: data,
- },
- }
-
- blockHash, err := hashBlock(&coreBlock)
- if err != nil {
- panic(err)
- }
-
- var parentCoreBlock coreTypes.Block
-
- if parent.Number().Uint64() != 0 {
- if err := rlp.DecodeBytes(
- parent.Header().DexconMeta, &parentCoreBlock); err != nil {
- panic(err)
- }
- }
-
- parentCoreBlockHash, err := hashBlock(&parentCoreBlock)
- if err != nil {
- panic(err)
- }
- randomness := nodeSet.Randomness(round, blockHash)
- coreBlock.Finalization.ParentHash = coreCommon.Hash(parentCoreBlockHash)
- coreBlock.Finalization.Randomness = randomness
- coreBlock.Finalization.Timestamp = time.Now().UTC()
- coreBlock.Finalization.Height = parent.Number().Uint64()
-
- dexconMeta, err := rlp.EncodeToBytes(&coreBlock)
- if err != nil {
- panic(err)
- }
- return dexconMeta
-}
-
// makeHeaderChain creates a deterministic chain of headers rooted at parent.
func makeHeaderChain(parent *types.Header, n int, engine consensus.Engine, db ethdb.Database, seed int) []*types.Header {
blocks := makeBlockChain(types.NewBlockWithHeader(parent), n, engine, db, seed)
@@ -429,337 +287,3 @@ func (cr *fakeChainReader) GetHeaderByNumber(number uint64) *types.Header
func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
-
-type node struct {
- cryptoKey coreCrypto.PrivateKey
- ecdsaKey *ecdsa.PrivateKey
-
- id coreTypes.NodeID
- dkgid coreDKG.ID
- address common.Address
- prvShares *coreDKG.PrivateKeyShares
- pubShares *coreDKG.PublicKeyShares
- receivedPrvShares *coreDKG.PrivateKeyShares
- recoveredPrivateKey *coreDKG.PrivateKey
- signer types.Signer
-
- mpk *coreTypesDKG.MasterPublicKey
-}
-
-func newNode(privkey *ecdsa.PrivateKey, signer types.Signer) *node {
- k := coreEcdsa.NewPrivateKeyFromECDSA(privkey)
- id := coreTypes.NewNodeID(k.PublicKey())
- return &node{
- cryptoKey: k,
- ecdsaKey: privkey,
- id: id,
- dkgid: coreDKG.NewID(id.Bytes()),
- address: crypto.PubkeyToAddress(privkey.PublicKey),
- signer: signer,
- }
-}
-
-func (n *node) ID() coreTypes.NodeID {
- return n.id
-}
-
-func (n *node) DKGID() coreDKG.ID {
- return n.dkgid
-}
-
-// return signed dkg master public key
-func (n *node) MasterPublicKeyTx(round uint64, nonce uint64) *types.Transaction {
- mpk := &coreTypesDKG.MasterPublicKey{
- ProposerID: n.ID(),
- Round: round,
- DKGID: n.DKGID(),
- PublicKeyShares: *n.pubShares,
- }
-
- binaryRound := make([]byte, 8)
- binary.LittleEndian.PutUint64(binaryRound, mpk.Round)
-
- hash := crypto.Keccak256Hash(
- mpk.ProposerID.Hash[:],
- mpk.DKGID.GetLittleEndian(),
- mpk.PublicKeyShares.MasterKeyBytes(),
- binaryRound,
- )
-
- var err error
- mpk.Signature, err = n.cryptoKey.Sign(coreCommon.Hash(hash))
- if err != nil {
- panic(err)
- }
-
- method := vm.GovernanceContractName2Method["addDKGMasterPublicKey"]
- encoded, err := rlp.EncodeToBytes(mpk)
- if err != nil {
- panic(err)
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
- if err != nil {
- panic(err)
- }
- data := append(method.Id(), res...)
- return n.CreateGovTx(nonce, data)
-}
-
-func (n *node) DKGFinalizeTx(round uint64, nonce uint64) *types.Transaction {
- final := coreTypesDKG.Finalize{
- ProposerID: n.ID(),
- Round: round,
- }
- binaryRound := make([]byte, 8)
- binary.LittleEndian.PutUint64(binaryRound, final.Round)
- hash := crypto.Keccak256Hash(
- final.ProposerID.Hash[:],
- binaryRound,
- )
-
- var err error
- final.Signature, err = n.cryptoKey.Sign(coreCommon.Hash(hash))
- if err != nil {
- panic(err)
- }
-
- method := vm.GovernanceContractName2Method["addDKGFinalize"]
-
- encoded, err := rlp.EncodeToBytes(final)
- if err != nil {
- panic(err)
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
- if err != nil {
- panic(err)
- }
-
- data := append(method.Id(), res...)
- return n.CreateGovTx(nonce, data)
-}
-
-func (n *node) CreateGovTx(nonce uint64, data []byte) *types.Transaction {
- tx, err := types.SignTx(types.NewTransaction(
- nonce,
- vm.GovernanceContractAddress,
- big.NewInt(0),
- uint64(2000000),
- big.NewInt(1e10),
- data), n.signer, n.ecdsaKey)
- if err != nil {
- panic(err)
- }
- return tx
-}
-
-type NodeSet struct {
- signer types.Signer
- privkeys []*ecdsa.PrivateKey
- nodes map[uint64][]*node
- crs map[uint64]common.Hash
- signedCRS map[uint64][]byte
-}
-
-func NewNodeSet(round uint64, crs common.Hash, signer types.Signer,
- privkeys []*ecdsa.PrivateKey) *NodeSet {
- n := &NodeSet{
- signer: signer,
- privkeys: privkeys,
- nodes: make(map[uint64][]*node),
- crs: make(map[uint64]common.Hash),
- signedCRS: make(map[uint64][]byte),
- }
- n.crs[round] = crs
- n.RunDKG(round, 2)
- return n
-}
-
-func (n *NodeSet) CRS(round uint64) common.Hash {
- if c, ok := n.crs[round]; ok {
- return c
- }
- panic("crs not exist")
-}
-
-// Assume All nodes in NodeSet are in DKG Set too.
-func (n *NodeSet) RunDKG(round uint64, threshold int) {
- var ids coreDKG.IDs
- var nodes []*node
- for _, key := range n.privkeys {
- node := newNode(key, n.signer)
- nodes = append(nodes, node)
- ids = append(ids, node.DKGID())
- }
-
- for _, node := range nodes {
- node.prvShares, node.pubShares = coreDKG.NewPrivateKeyShares(threshold)
- node.prvShares.SetParticipants(ids)
- node.receivedPrvShares = coreDKG.NewEmptyPrivateKeyShares()
- }
-
- // exchange keys
- for _, sender := range nodes {
- for _, receiver := range nodes {
- // no need to verify
- prvShare, ok := sender.prvShares.Share(receiver.DKGID())
- if !ok {
- panic("not ok")
- }
- receiver.receivedPrvShares.AddShare(sender.DKGID(), prvShare)
- }
- }
-
- // recover private key
- for _, node := range nodes {
- privKey, err := node.receivedPrvShares.RecoverPrivateKey(ids)
- if err != nil {
- panic(err)
- }
- node.recoveredPrivateKey = privKey
- }
-
- // store these nodes
- n.nodes[round] = nodes
-}
-
-func (n *NodeSet) Randomness(round uint64, hash common.Hash) []byte {
- if round == 0 {
- return []byte{}
- }
- return n.TSig(round-1, hash)
-}
-
-func (n *NodeSet) SignedCRS(round uint64) {
- signedCRS := n.TSig(round, n.crs[round])
- n.signedCRS[round+1] = signedCRS
- n.crs[round+1] = crypto.Keccak256Hash(signedCRS)
-}
-
-func (n *NodeSet) TSig(round uint64, hash common.Hash) []byte {
- var ids coreDKG.IDs
- var psigs []coreDKG.PartialSignature
- for _, node := range n.nodes[round] {
- ids = append(ids, node.DKGID())
- }
- for _, node := range n.nodes[round] {
- sig, err := node.recoveredPrivateKey.Sign(coreCommon.Hash(hash))
- if err != nil {
- panic(err)
- }
- psigs = append(psigs, coreDKG.PartialSignature(sig))
- // ids = append(ids, node.DKGID())
-
- // FIXME: Debug verify signature
- pk := coreDKG.NewEmptyPublicKeyShares()
- for _, nnode := range n.nodes[round] {
- p, err := nnode.pubShares.Share(node.DKGID())
- if err != nil {
- panic(err)
- }
- err = pk.AddShare(nnode.DKGID(), p)
- if err != nil {
- panic(err)
- }
- }
-
- recovered, err := pk.RecoverPublicKey(ids)
- if err != nil {
- panic(err)
- }
-
- if !recovered.VerifySignature(coreCommon.Hash(hash), sig) {
- panic("##########can not verify signature")
- }
- }
-
- sig, err := coreDKG.RecoverSignature(psigs, ids)
- if err != nil {
- panic(err)
- }
- return sig.Signature
-}
-
-func (n *NodeSet) CRSTx(round uint64, b *BlockGen) *types.Transaction {
- method := vm.GovernanceContractName2Method["proposeCRS"]
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), n.signedCRS[round])
- if err != nil {
- panic(err)
- }
- data := append(method.Id(), res...)
-
- node := n.nodes[round-1][0]
- return node.CreateGovTx(b.TxNonce(node.address), data)
-}
-
-func (n *NodeSet) NotifyRoundHeightTx(round, height uint64,
- b *BlockGen) *types.Transaction {
- method := vm.GovernanceContractName2Method["snapshotRound"]
- res, err := method.Inputs.Pack(
- big.NewInt(int64(round)), big.NewInt(int64(height)))
- if err != nil {
- panic(err)
- }
- data := append(method.Id(), res...)
-
- var r uint64
- if round < 1 {
- r = 0
- } else {
- r = round - 1
- }
- node := n.nodes[r][0]
- return node.CreateGovTx(b.TxNonce(node.address), data)
-}
-
-// Copy from dexon consensus core
-// TODO(sonic): polish this
-func hashBlock(block *coreTypes.Block) (common.Hash, error) {
- hashPosition := hashPosition(block.Position)
- // Handling Block.Acks.
- binaryAcks := make([][]byte, len(block.Acks))
- for idx, ack := range block.Acks {
- binaryAcks[idx] = ack[:]
- }
- hashAcks := crypto.Keccak256Hash(binaryAcks...)
- binaryTimestamp, err := block.Timestamp.UTC().MarshalBinary()
- if err != nil {
- return common.Hash{}, err
- }
- binaryWitness, err := hashWitness(&block.Witness)
- if err != nil {
- return common.Hash{}, err
- }
-
- hash := crypto.Keccak256Hash(
- block.ProposerID.Hash[:],
- block.ParentHash[:],
- hashPosition[:],
- hashAcks[:],
- binaryTimestamp[:],
- block.PayloadHash[:],
- binaryWitness[:])
- return hash, nil
-}
-
-func hashPosition(position coreTypes.Position) common.Hash {
- binaryChainID := make([]byte, 4)
- binary.LittleEndian.PutUint32(binaryChainID, position.ChainID)
-
- binaryHeight := make([]byte, 8)
- binary.LittleEndian.PutUint64(binaryHeight, position.Height)
-
- return crypto.Keccak256Hash(
- binaryChainID,
- binaryHeight,
- )
-}
-
-func hashWitness(witness *coreTypes.Witness) (common.Hash, error) {
- binaryHeight := make([]byte, 8)
- binary.LittleEndian.PutUint64(binaryHeight, witness.Height)
- return crypto.Keccak256Hash(
- binaryHeight,
- witness.Data), nil
-}
diff --git a/core/dexon_chain_makers.go b/core/dexon_chain_makers.go
new file mode 100644
index 000000000..128cc454b
--- /dev/null
+++ b/core/dexon_chain_makers.go
@@ -0,0 +1,312 @@
+// Copyright 2015 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
+
+package core
+
+import (
+ "fmt"
+ "math/big"
+ "math/rand"
+ "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/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/params"
+ "github.com/dexon-foundation/dexon/rlp"
+)
+
+func init() {
+ rand.Seed(time.Now().UTC().UnixNano())
+}
+
+// DexonBlockGen creates blocks for testing.
+// See GenerateChain for a detailed explanation.
+type DexonBlockGen struct {
+ i int
+ parent *types.Block
+ chain []*types.Block
+ header *types.Header
+ statedb *state.StateDB
+
+ dirtyNonce map[common.Address]uint64
+
+ position coreTypes.Position
+
+ gasPool *GasPool
+ txs []*types.Transaction
+ receipts []*types.Receipt
+
+ config *params.ChainConfig
+ engine consensus.Engine
+}
+
+// SetCoinbase sets the coinbase of the generated block.
+// It can be called at most once.
+func (b *DexonBlockGen) SetCoinbase(addr common.Address) {
+ if b.gasPool != nil {
+ if len(b.txs) > 0 {
+ panic("coinbase must be set before adding transactions")
+ }
+ panic("coinbase can only be set once")
+ }
+ b.header.Coinbase = addr
+ b.gasPool = new(GasPool).AddGas(b.header.GasLimit)
+}
+
+// SetExtra sets the extra data field of the generated block.
+func (b *DexonBlockGen) SetExtra(data []byte) {
+ b.header.Extra = data
+}
+
+// SetNonce sets the nonce field of the generated block.
+func (b *DexonBlockGen) SetNonce(nonce types.BlockNonce) {
+ b.header.Nonce = nonce
+}
+
+func (b *DexonBlockGen) SetPosition(position coreTypes.Position) {
+ b.position = position
+}
+
+// AddTx adds a transaction to the generated block. If no coinbase has
+// been set, the block's coinbase is set to the zero address.
+//
+// AddTx panics if the transaction cannot be executed. In addition to
+// the protocol-imposed limitations (gas limit, etc.), there are some
+// further limitations on the content of transactions that can be
+// added. If contract code relies on the BLOCKHASH instruction,
+// the block in chain will be returned.
+func (b *DexonBlockGen) AddTx(tx *types.Transaction) {
+ // TODO: check nonce and maintain nonce.
+ b.txs = append(b.txs, tx)
+}
+
+func (b *DexonBlockGen) PreparePayload() []byte {
+ return nil
+}
+
+func (b *DexonBlockGen) ProcessTransactions(c ChainContext) {
+ if b.gasPool == nil {
+ b.SetCoinbase(common.Address{})
+ }
+ for _, tx := range b.txs {
+ b.statedb.Prepare(tx.Hash(), common.Hash{}, len(b.txs))
+ // TODO: fix the chain context parameter
+ receipt, _, err := ApplyTransaction(b.config, c, &b.header.Coinbase, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed, vm.Config{})
+ if err != nil {
+ panic(err)
+ }
+ b.receipts = append(b.receipts, receipt)
+ }
+}
+
+// Number returns the block number of the block being generated.
+func (b *DexonBlockGen) Number() *big.Int {
+ return new(big.Int).Set(b.header.Number)
+}
+
+// AddUncheckedReceipt forcefully adds a receipts to the block without a
+// backing transaction.
+//
+// AddUncheckedReceipt will cause consensus failures when used during real
+// chain processing. This is best used in conjunction with raw block insertion.
+func (b *DexonBlockGen) AddUncheckedReceipt(receipt *types.Receipt) {
+ b.receipts = append(b.receipts, receipt)
+}
+
+// TxNonce returns the next valid transaction nonce for the
+// account at addr. It panics if the account does not exist.
+func (b *DexonBlockGen) TxNonce(addr common.Address) uint64 {
+ if !b.statedb.Exist(addr) {
+ panic("account does not exist")
+ }
+ if nonce, exists := b.dirtyNonce[addr]; exists {
+ return nonce
+ }
+ return b.statedb.GetNonce(addr)
+}
+
+// PrevBlock returns a previously generated block by number. It panics if
+// num is greater or equal to the number of the block being generated.
+// For index -1, PrevBlock returns the parent block given to GenerateChain.
+func (b *DexonBlockGen) PrevBlock(index int) *types.Block {
+ if index >= b.i {
+ panic(fmt.Errorf("block index %d out of range (%d,%d)", index, -1, b.i))
+ }
+ if index == -1 {
+ return b.parent
+ }
+ return b.chain[index]
+}
+
+func GenerateDexonChain(config *params.ChainConfig, parent *types.Block, engine consensus.Engine, db ethdb.Database, n int, gen func(int, *DexonBlockGen)) ([]*types.Block, []types.Receipts) {
+ if config == nil {
+ config = params.TestChainConfig
+ }
+ if engine == nil {
+ panic("engine is nil")
+ }
+ blocks, receipts := make(types.Blocks, n), make([]types.Receipts, n)
+ chain := &fakeDexonChain{
+ config: config,
+ engine: engine,
+ genesis: parent,
+ db: db,
+ headersByNumber: make(map[uint64]*types.Header),
+ roundHeight: make(map[uint64]uint64),
+ }
+ genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) {
+ b := &DexonBlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine}
+ b.header = makeDexonHeader(chain, parent, statedb, b.engine)
+
+ // Execute any user modifications to the block
+ if gen != nil {
+ gen(i, b)
+ }
+
+ b.header.DexconMeta = makeDexconMeta(b, parent)
+
+ // sign tsig to create dexcon, prepare witness
+ b.engine.Prepare(chain, b.header)
+
+ // Run EVM and finalize the block.
+ b.ProcessTransactions(chain)
+
+ // Finalize and seal the block
+ block, _ := b.engine.Finalize(chain, b.header, statedb, b.txs, nil, b.receipts)
+
+ // Write state changes to db
+ root, err := statedb.Commit(config.IsEIP158(b.header.Number))
+ if err != nil {
+ panic(fmt.Sprintf("state write error: %v", err))
+ }
+ if err := statedb.Database().TrieDB().Commit(root, false); err != nil {
+ panic(fmt.Sprintf("trie write error: %v", err))
+ }
+ return block, b.receipts
+ }
+ for i := 0; i < n; i++ {
+ statedb, err := state.New(parent.Root(), state.NewDatabase(db))
+ if err != nil {
+ panic(err)
+ }
+ block, receipt := genblock(i, parent, statedb)
+ blocks[i] = block
+ receipts[i] = receipt
+ parent = block
+ chain.InsertBlock(block)
+ }
+ return blocks, receipts
+}
+
+func makeDexonHeader(chain consensus.ChainReader, parent *types.Block, state *state.StateDB, engine consensus.Engine) *types.Header {
+ var time uint64
+ if parent.Time() == 0 {
+ time = 10
+ } else {
+ time = parent.Time() + 10 // block time is fixed at 10 seconds
+ }
+
+ return &types.Header{
+ Root: state.IntermediateRoot(chain.Config().IsEIP158(parent.Number())),
+ ParentHash: parent.Hash(),
+ Coinbase: parent.Coinbase(),
+ Difficulty: new(big.Int).Add(parent.Difficulty(), common.Big1),
+ GasLimit: chain.Config().Dexcon.BlockGasLimit,
+ Number: new(big.Int).Add(parent.Number(), common.Big1),
+ Time: time,
+ }
+}
+
+// Setup DexconMeata skeleton
+func makeDexconMeta(b *DexonBlockGen, parent *types.Block) []byte {
+ // Setup witness
+ h := parent.Number().Int64() - rand.Int63n(6)
+ if h < 0 {
+ h = 0
+ }
+ witnessedBlock := b.chain[h]
+ if witnessedBlock == nil {
+ witnessedBlock = parent
+ }
+ witnessedBlockHash := witnessedBlock.Hash()
+ data, err := rlp.EncodeToBytes(&witnessedBlockHash)
+ if err != nil {
+ panic(err)
+ }
+
+ block := coreTypes.Block{
+ Position: b.position,
+ Witness: coreTypes.Witness{
+ Height: witnessedBlock.NumberU64(),
+ Data: data,
+ },
+ }
+
+ dexconMeta, err := rlp.EncodeToBytes(&block)
+ if err != nil {
+ panic(err)
+ }
+ return dexconMeta
+}
+
+type fakeDexonChain struct {
+ config *params.ChainConfig
+ engine consensus.Engine
+ db ethdb.Database
+ genesis *types.Block
+ headersByNumber map[uint64]*types.Header
+ roundHeight map[uint64]uint64
+}
+
+func (f *fakeDexonChain) InsertBlock(block *types.Block) {
+ f.headersByNumber[block.NumberU64()] = block.Header()
+ if _, exists := f.roundHeight[block.Round()]; !exists {
+ f.roundHeight[block.Round()] = block.NumberU64()
+ }
+}
+
+// Config returns the chain configuration.
+func (f *fakeDexonChain) Config() *params.ChainConfig { return f.config }
+func (f *fakeDexonChain) Engine() consensus.Engine { return f.engine }
+func (f *fakeDexonChain) CurrentHeader() *types.Header { return nil }
+func (f *fakeDexonChain) GetHeaderByHash(hash common.Hash) *types.Header { return nil }
+func (f *fakeDexonChain) GetHeader(hash common.Hash, number uint64) *types.Header { return nil }
+func (f *fakeDexonChain) GetBlock(hash common.Hash, number uint64) *types.Block { return nil }
+
+func (f *fakeDexonChain) GetHeaderByNumber(number uint64) *types.Header {
+ if number == 0 {
+ return f.genesis.Header()
+ }
+ return f.headersByNumber[number]
+}
+
+func (f *fakeDexonChain) StateAt(hash common.Hash) (*state.StateDB, error) {
+ return state.New(hash, state.NewDatabase(f.db))
+}
+
+func (f *fakeDexonChain) GetRoundHeight(round uint64) (uint64, bool) {
+ if round == 0 {
+ return 0, true
+ }
+ height, ok := f.roundHeight[round]
+ return height, ok
+}
diff --git a/core/headerchain.go b/core/headerchain.go
index e988a53a2..17cedffdd 100644
--- a/core/headerchain.go
+++ b/core/headerchain.go
@@ -23,6 +23,7 @@ import (
"math"
"math/big"
mrand "math/rand"
+ "reflect"
"sync/atomic"
"time"
@@ -32,6 +33,7 @@ import (
"github.com/dexon-foundation/dexon/common"
"github.com/dexon-foundation/dexon/consensus"
+ "github.com/dexon-foundation/dexon/consensus/dexcon"
"github.com/dexon-foundation/dexon/core/rawdb"
"github.com/dexon-foundation/dexon/core/types"
"github.com/dexon-foundation/dexon/crypto"
@@ -394,7 +396,9 @@ func (hc *HeaderChain) WriteDexonHeader(header *types.HeaderWithGovState) (statu
type Wh2Callback func(*types.HeaderWithGovState) error
-func (hc *HeaderChain) ValidateDexonHeaderChain(chain []*types.HeaderWithGovState, verifierCache *dexCore.TSigVerifierCache) (int, error) {
+func (hc *HeaderChain) ValidateDexonHeaderChain(chain []*types.HeaderWithGovState,
+ gov dexcon.GovernanceStateFetcher,
+ verifierCache *dexCore.TSigVerifierCache, validator Validator) (int, error) {
// Do a sanity check that the provided chain is actually ordered and linked
for i := 1; i < len(chain); i++ {
if chain[i].Number.Uint64() != chain[i-1].Number.Uint64()+1 || chain[i].ParentHash != chain[i-1].Hash() {
@@ -415,40 +419,156 @@ func (hc *HeaderChain) ValidateDexonHeaderChain(chain []*types.HeaderWithGovStat
return 0, errors.New("aborted")
}
- if err := hc.verifyTSig(header.Header, verifierCache); err != nil {
+ if i == 0 {
+ log.Debug("validate header chain", "parent", header.ParentHash.String(), "number", header.Number.Uint64()-1)
+ if parent := hc.GetHeader(header.ParentHash, header.Number.Uint64()-1); parent == nil {
+ return 0, consensus.ErrUnknownAncestor
+ }
+ }
+
+ if err := hc.verifyDexonHeader(header.Header, gov, verifierCache); err != nil {
return i, err
}
+
+ // Verify witness
+ var coreBlock coreTypes.Block
+ if err := rlp.DecodeBytes(header.DexconMeta, &coreBlock); err != nil {
+ return i, err
+ }
+
+ if !coreBlock.IsEmpty() {
+ var witnessBlockHash common.Hash
+ if err := rlp.DecodeBytes(coreBlock.Witness.Data, &witnessBlockHash); err != nil {
+ log.Error("decode witness data fail", "err", err)
+ return i, err
+ }
+
+ index := int64(coreBlock.Witness.Height) - int64(chain[0].Number.Uint64())
+ if index < 0 {
+ if err := validator.ValidateWitnessData(
+ coreBlock.Witness.Height, witnessBlockHash); err != nil {
+ return i, err
+ }
+ } else {
+ if witnessBlockHash != chain[index].Hash() {
+ return i, consensus.ErrWitnessMismatch
+ }
+ }
+ }
}
return 0, nil
}
-func (hc *HeaderChain) verifyTSig(header *types.Header, verifierCache *dexCore.TSigVerifierCache) error {
+func (hc *HeaderChain) VerifyDexonHeader(header *types.Header,
+ gov dexcon.GovernanceStateFetcher,
+ verifierCache *dexCore.TSigVerifierCache, validator Validator) error {
+
+ if parent := hc.GetHeader(header.ParentHash, header.Number.Uint64()-1); parent == nil {
+ return consensus.ErrUnknownAncestor
+ }
+
+ if err := hc.verifyDexonHeader(header, gov, verifierCache); err != nil {
+ return err
+ }
+
+ // Verify witness
+ var coreBlock coreTypes.Block
+ if err := rlp.DecodeBytes(header.DexconMeta, &coreBlock); err != nil {
+ return err
+ }
+
+ if !coreBlock.IsEmpty() {
+ var witnessBlockHash common.Hash
+ if err := rlp.DecodeBytes(coreBlock.Witness.Data, &witnessBlockHash); err != nil {
+ log.Error("decode witness data fail", "err", err)
+ return err
+ }
+
+ if err := validator.ValidateWitnessData(
+ coreBlock.Witness.Height, witnessBlockHash); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (hc *HeaderChain) verifyDexonHeader(header *types.Header,
+ gov dexcon.GovernanceStateFetcher,
+ verifierCache *dexCore.TSigVerifierCache) error {
+
// If the header is a banned one, straight out abort
if BadHashes[header.Hash()] {
return ErrBlacklistedHash
}
- if header.Round == 0 {
- return nil
+ // Difficulty should always be 1.
+ if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
+ return fmt.Errorf("difficulty should be 1")
}
- var dexconMeta coreTypes.Block
- if err := rlp.DecodeBytes(header.DexconMeta, &dexconMeta); err != nil {
- return err
+ // Verify fields that should be same as dexcon meta.
+ var coreBlock coreTypes.Block
+ if err := rlp.DecodeBytes(header.DexconMeta, &coreBlock); err != nil {
+ return fmt.Errorf("decode dexcon meta fail, number=%d, err=%v",
+ header.Number.Uint64(), err)
+ }
+
+ if err := hc.verifyTSig(&coreBlock, verifierCache); err != nil {
+ log.Debug("verify header sig fail, number=%d, err=%v",
+ header.Number.Uint64(), err)
+ }
+
+ if header.Coinbase != common.BytesToAddress(coreBlock.ProposerID.Bytes()) {
+ return fmt.Errorf("coinbase mismatch")
+ }
+
+ if header.Time != uint64(coreBlock.Finalization.Timestamp.UnixNano()/1000000) {
+ return fmt.Errorf("timestamp mismatch")
+ }
+
+ if header.Number.Uint64() != coreBlock.Finalization.Height {
+ return fmt.Errorf("height mismatch")
+ }
+
+ if !reflect.DeepEqual(header.Randomness, coreBlock.Finalization.Randomness) {
+ return fmt.Errorf("randomness mismatch")
+ }
+
+ if header.Round != coreBlock.Position.Round {
+ return fmt.Errorf("round mismatch")
+ }
+
+ gs := gov.GetGovStateHelperAtRound(header.Round)
+ config := gs.Configuration()
+ if header.GasLimit != config.BlockGasLimit {
+ return fmt.Errorf("block gas limit mismatch")
+ }
+ return nil
+}
+
+func (hc *HeaderChain) verifyTSig(coreBlock *coreTypes.Block,
+ verifierCache *dexCore.TSigVerifierCache) error {
+
+ round := coreBlock.Position.Round
+ randomness := coreBlock.Finalization.Randomness
+
+ if round == 0 {
+ return nil
}
- v, ok, err := verifierCache.UpdateAndGet(header.Round)
+ // Verify threshold signature
+ v, ok, err := verifierCache.UpdateAndGet(round)
if err != nil {
panic(err)
}
if !ok {
- panic(fmt.Errorf("DKG of round %d is not finished", header.Round))
+ panic(fmt.Errorf("DKG of round %d is not finished", round))
}
- if !v.VerifySignature(dexconMeta.Hash, coreCrypto.Signature{
+ if !v.VerifySignature(coreBlock.Hash, coreCrypto.Signature{
Type: "bls",
- Signature: header.Randomness}) {
+ Signature: randomness}) {
return fmt.Errorf("signature invalid")
}
return nil
diff --git a/core/types/block.go b/core/types/block.go
index 4ff34c229..88e537e3a 100644
--- a/core/types/block.go
+++ b/core/types/block.go
@@ -264,6 +264,17 @@ func CopyHeader(h *Header) *Header {
cpy.Extra = make([]byte, len(h.Extra))
copy(cpy.Extra, h.Extra)
}
+ if cpy.Reward = new(big.Int); h.Reward != nil {
+ cpy.Reward.Set(h.Reward)
+ }
+ if len(h.Randomness) > 0 {
+ cpy.Randomness = make([]byte, len(h.Randomness))
+ copy(cpy.Randomness, h.Randomness)
+ }
+ if len(h.DexconMeta) > 0 {
+ cpy.DexconMeta = make([]byte, len(h.DexconMeta))
+ copy(cpy.DexconMeta, h.DexconMeta)
+ }
return &cpy
}
diff --git a/core/vm/governance.go b/core/vm/governance.go
index e77519c58..a0014005d 100644
--- a/core/vm/governance.go
+++ b/core/vm/governance.go
@@ -2193,3 +2193,126 @@ func (g *GovernanceContract) snapshotRound(round, height *big.Int) ([]byte, erro
g.state.PushRoundHeight(height)
return nil, nil
}
+
+func PackProposeCRS(round uint64, signedCRS []byte) ([]byte, error) {
+ method := GovernanceContractName2Method["proposeCRS"]
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), signedCRS)
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackNotifyRoundHeight(targetRound, consensusHeight uint64) ([]byte, error) {
+ method := GovernanceContractName2Method["snapshotRound"]
+ res, err := method.Inputs.Pack(
+ big.NewInt(int64(targetRound)), big.NewInt(int64(consensusHeight)))
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackAddDKGMasterPublicKey(round uint64, mpk *dkgTypes.MasterPublicKey) ([]byte, error) {
+ method := GovernanceContractName2Method["addDKGMasterPublicKey"]
+ encoded, err := rlp.EncodeToBytes(mpk)
+ if err != nil {
+ return nil, err
+ }
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackAddDKGMPKReady(round uint64, ready *dkgTypes.MPKReady) ([]byte, error) {
+ method := GovernanceContractName2Method["addDKGMPKReady"]
+ encoded, err := rlp.EncodeToBytes(ready)
+ if err != nil {
+ return nil, err
+ }
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackAddDKGComplaint(round uint64, complaint *dkgTypes.Complaint) ([]byte, error) {
+ method := GovernanceContractName2Method["addDKGComplaint"]
+ encoded, err := rlp.EncodeToBytes(complaint)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackAddDKGFinalize(round uint64, final *dkgTypes.Finalize) ([]byte, error) {
+ method := GovernanceContractName2Method["addDKGFinalize"]
+ encoded, err := rlp.EncodeToBytes(final)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ if err != nil {
+ return nil, err
+ }
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackReportForkVote(vote1, vote2 *coreTypes.Vote) ([]byte, error) {
+ method := GovernanceContractName2Method["report"]
+
+ vote1Bytes, err := rlp.EncodeToBytes(vote1)
+ if err != nil {
+ return nil, err
+ }
+
+ vote2Bytes, err := rlp.EncodeToBytes(vote2)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := method.Inputs.Pack(big.NewInt(ReportTypeForkVote), vote1Bytes, vote2Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ data := append(method.Id(), res...)
+ return data, nil
+}
+
+func PackReportForkBlock(block1, block2 *coreTypes.Block) ([]byte, error) {
+ method := GovernanceContractName2Method["report"]
+
+ block1Bytes, err := rlp.EncodeToBytes(block1)
+ if err != nil {
+ return nil, err
+ }
+
+ block2Bytes, err := rlp.EncodeToBytes(block2)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := method.Inputs.Pack(big.NewInt(ReportTypeForkBlock), block1Bytes, block2Bytes)
+ if err != nil {
+ return nil, err
+ }
+
+ data := append(method.Id(), res...)
+ return data, nil
+}
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
+}
diff --git a/dex/governance.go b/dex/governance.go
index 0d5a7c926..e9ea1f89c 100644
--- a/dex/governance.go
+++ b/dex/governance.go
@@ -37,7 +37,6 @@ import (
"github.com/dexon-foundation/dexon/crypto"
"github.com/dexon-foundation/dexon/log"
"github.com/dexon-foundation/dexon/params"
- "github.com/dexon-foundation/dexon/rlp"
)
type DexconGovernance struct {
@@ -119,15 +118,12 @@ func (d *DexconGovernance) LenCRS() uint64 {
// ProposeCRS send proposals of a new CRS
func (d *DexconGovernance) ProposeCRS(round uint64, signedCRS []byte) {
- method := vm.GovernanceContractName2Method["proposeCRS"]
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), signedCRS)
+ data, err := vm.PackProposeCRS(round, signedCRS)
if err != nil {
log.Error("failed to pack proposeCRS input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send proposeCRS tx", "err", err)
@@ -151,16 +147,12 @@ func (d *DexconGovernance) NodeSet(round uint64) []coreCrypto.PublicKey {
// NotifyRoundHeight register the mapping between round and height.
func (d *DexconGovernance) NotifyRoundHeight(targetRound, consensusHeight uint64) {
- method := vm.GovernanceContractName2Method["snapshotRound"]
-
- res, err := method.Inputs.Pack(
- big.NewInt(int64(targetRound)), big.NewInt(int64(consensusHeight)))
+ data, err := vm.PackNotifyRoundHeight(targetRound, consensusHeight)
if err != nil {
log.Error("failed to pack snapshotRound input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send snapshotRound tx", "err", err)
@@ -169,21 +161,12 @@ func (d *DexconGovernance) NotifyRoundHeight(targetRound, consensusHeight uint64
// AddDKGComplaint adds a DKGComplaint.
func (d *DexconGovernance) AddDKGComplaint(round uint64, complaint *dkgTypes.Complaint) {
- method := vm.GovernanceContractName2Method["addDKGComplaint"]
-
- encoded, err := rlp.EncodeToBytes(complaint)
- if err != nil {
- log.Error("failed to RLP encode complaint to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ data, err := vm.PackAddDKGComplaint(round, complaint)
if err != nil {
log.Error("failed to pack addDKGComplaint input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send addDKGComplaint tx", "err", err)
@@ -192,21 +175,12 @@ func (d *DexconGovernance) AddDKGComplaint(round uint64, complaint *dkgTypes.Com
// AddDKGMasterPublicKey adds a DKGMasterPublicKey.
func (d *DexconGovernance) AddDKGMasterPublicKey(round uint64, masterPublicKey *dkgTypes.MasterPublicKey) {
- method := vm.GovernanceContractName2Method["addDKGMasterPublicKey"]
-
- encoded, err := rlp.EncodeToBytes(masterPublicKey)
- if err != nil {
- log.Error("failed to RLP encode mpk to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ data, err := vm.PackAddDKGMasterPublicKey(round, masterPublicKey)
if err != nil {
log.Error("failed to pack addDKGMasterPublicKey input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send addDKGMasterPublicKey tx", "err", err)
@@ -215,21 +189,12 @@ func (d *DexconGovernance) AddDKGMasterPublicKey(round uint64, masterPublicKey *
// AddDKGMPKReady adds a DKG mpk ready message.
func (d *DexconGovernance) AddDKGMPKReady(round uint64, ready *dkgTypes.MPKReady) {
- method := vm.GovernanceContractName2Method["addDKGMPKReady"]
-
- encoded, err := rlp.EncodeToBytes(ready)
- if err != nil {
- log.Error("failed to RLP encode mpk ready to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ data, err := vm.PackAddDKGMPKReady(round, ready)
if err != nil {
log.Error("failed to pack addDKGMPKReady input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send addDKGMPKReady tx", "err", err)
@@ -238,21 +203,12 @@ func (d *DexconGovernance) AddDKGMPKReady(round uint64, ready *dkgTypes.MPKReady
// AddDKGFinalize adds a DKG finalize message.
func (d *DexconGovernance) AddDKGFinalize(round uint64, final *dkgTypes.Finalize) {
- method := vm.GovernanceContractName2Method["addDKGFinalize"]
-
- encoded, err := rlp.EncodeToBytes(final)
- if err != nil {
- log.Error("failed to RLP encode finalize to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(int64(round)), encoded)
+ data, err := vm.PackAddDKGFinalize(round, final)
if err != nil {
log.Error("failed to pack addDKGFinalize input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send addDKGFinalize tx", "err", err)
@@ -261,27 +217,12 @@ func (d *DexconGovernance) AddDKGFinalize(round uint64, final *dkgTypes.Finalize
// ReportForkVote reports a node for forking votes.
func (d *DexconGovernance) ReportForkVote(vote1, vote2 *coreTypes.Vote) {
- method := vm.GovernanceContractName2Method["report"]
-
- vote1Bytes, err := rlp.EncodeToBytes(vote1)
- if err != nil {
- log.Error("failed to RLP encode vote1 to bytes", "err", err)
- return
- }
-
- vote2Bytes, err := rlp.EncodeToBytes(vote2)
- if err != nil {
- log.Error("failed to RLP encode vote2 to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(vm.ReportTypeForkVote), vote1Bytes, vote2Bytes)
+ data, err := vm.PackReportForkVote(vote1, vote2)
if err != nil {
- log.Error("failed to pack report input", "err", err)
+ log.Error("failed to pack report fork vote input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send report fork vote tx", "err", err)
@@ -290,27 +231,12 @@ func (d *DexconGovernance) ReportForkVote(vote1, vote2 *coreTypes.Vote) {
// ReportForkBlock reports a node for forking blocks.
func (d *DexconGovernance) ReportForkBlock(block1, block2 *coreTypes.Block) {
- method := vm.GovernanceContractName2Method["report"]
-
- block1Bytes, err := rlp.EncodeToBytes(block1)
- if err != nil {
- log.Error("failed to RLP encode block1 to bytes", "err", err)
- return
- }
-
- block2Bytes, err := rlp.EncodeToBytes(block2)
- if err != nil {
- log.Error("failed to RLP encode block2 to bytes", "err", err)
- return
- }
-
- res, err := method.Inputs.Pack(big.NewInt(vm.ReportTypeForkBlock), block1Bytes, block2Bytes)
+ data, err := vm.PackReportForkBlock(block1, block2)
if err != nil {
- log.Error("failed to pack report input", "err", err)
+ log.Error("failed to pack report fork block input", "err", err)
return
}
- data := append(method.Id(), res...)
err = d.sendGovTx(context.Background(), data)
if err != nil {
log.Error("failed to send report fork block tx", "err", err)