From 23fc54de4f886978578207acc15f469abecc46b7 Mon Sep 17 00:00:00 2001 From: Sonic Date: Mon, 5 Nov 2018 11:26:27 +0800 Subject: core: GenerateChainWithRoundChange for testing --- core/chain_makers.go | 482 ++++++++++++++++++++++++++++++++++++++++++++++ core/chain_makers_test.go | 110 +++++++++++ core/genesis.go | 6 + 3 files changed, 598 insertions(+) (limited to 'core') diff --git a/core/chain_makers.go b/core/chain_makers.go index 40d34fd93..b464555d3 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -17,8 +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" @@ -26,10 +37,16 @@ import ( "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() { + rand.Seed(time.Now().UTC().UnixNano()) +} + // BlockGen creates blocks for testing. // See GenerateChain for a detailed explanation. type BlockGen struct { @@ -248,6 +265,137 @@ 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) @@ -281,3 +429,337 @@ 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/chain_makers_test.go b/core/chain_makers_test.go index 0be6625d9..c240ed824 100644 --- a/core/chain_makers_test.go +++ b/core/chain_makers_test.go @@ -17,6 +17,7 @@ package core import ( + "crypto/ecdsa" "fmt" "math/big" @@ -98,3 +99,112 @@ func ExampleGenerateChain() { // balance of addr2: 10000 // balance of addr3: 19687500000000001000 } + +func ExampleGenerateChainWithRoundChange() { + var ( + // genesis node set + nodekey1, _ = crypto.HexToECDSA("3cf5bdee098cc34536a7b0e80d85e07a380efca76fc12136299b9e5ba24193c8") + nodekey2, _ = crypto.HexToECDSA("96c9f1435d53577db18d45411326311529a0e8affb19218e27f65769a482c0fb") + nodekey3, _ = crypto.HexToECDSA("b25e955e30dd87cbaec83287beea6ec9c4c72498bc66905590756bf48da5d1fc") + nodekey4, _ = crypto.HexToECDSA("35577f65312f4a5e0b5391f5385043a6bc7b51fa4851a579e845b5fea33efded") + nodeaddr1 = crypto.PubkeyToAddress(nodekey1.PublicKey) + nodeaddr2 = crypto.PubkeyToAddress(nodekey2.PublicKey) + nodeaddr3 = crypto.PubkeyToAddress(nodekey3.PublicKey) + nodeaddr4 = crypto.PubkeyToAddress(nodekey4.PublicKey) + + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + key2, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + key3, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + addr2 = crypto.PubkeyToAddress(key2.PublicKey) + addr3 = crypto.PubkeyToAddress(key3.PublicKey) + + db = ethdb.NewMemDatabase() + ) + + ether := big.NewInt(1e18) + gspec := Genesis{ + Config: params.TestnetChainConfig, + Alloc: GenesisAlloc{ + nodeaddr1: { + Balance: new(big.Int).Mul(big.NewInt(1000), ether), + Staked: new(big.Int).Mul(big.NewInt(500), ether), + PublicKey: crypto.FromECDSAPub(&nodekey1.PublicKey), + }, + nodeaddr2: { + Balance: new(big.Int).Mul(big.NewInt(1000), ether), + Staked: new(big.Int).Mul(big.NewInt(500), ether), + PublicKey: crypto.FromECDSAPub(&nodekey2.PublicKey), + }, + nodeaddr3: { + Balance: new(big.Int).Mul(big.NewInt(1000), ether), + Staked: new(big.Int).Mul(big.NewInt(500), ether), + PublicKey: crypto.FromECDSAPub(&nodekey3.PublicKey), + }, + nodeaddr4: { + Balance: new(big.Int).Mul(big.NewInt(1000), ether), + Staked: new(big.Int).Mul(big.NewInt(500), ether), + PublicKey: crypto.FromECDSAPub(&nodekey4.PublicKey), + }, + addr1: { + Balance: big.NewInt(1000000), + }, + }, + } + genesis := gspec.MustCommit(db) + crs := crypto.Keccak256Hash([]byte(gspec.Config.Dexcon.GenesisCRSText)) + signer := types.NewEIP155Signer(gspec.Config.ChainID) + nodeSet := NewNodeSet(uint64(0), crs, signer, + []*ecdsa.PrivateKey{nodekey1, nodekey2, nodekey3, nodekey4}) + + // This call generates a chain of 1000 blocks. The function runs for + // each block and adds different features to gen based on the + // block index. + chain, _ := GenerateChainWithRoundChange(gspec.Config, genesis, ethash.NewFaker(), db, 1000, func(i int, gen *BlockGen) { + switch i { + case 0: + // In block 1, addr1 sends addr2 some ether. + tx, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(10000), params.TxGas, nil, nil), signer, key1) + gen.AddTx(tx) + case 1: + // In block 2, addr1 sends some more ether to addr2. + // addr2 passes it on to addr3. + tx1, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr1), addr2, big.NewInt(1000), params.TxGas, nil, nil), signer, key1) + tx2, _ := types.SignTx(types.NewTransaction(gen.TxNonce(addr2), addr3, big.NewInt(1000), params.TxGas, nil, nil), signer, key2) + gen.AddTx(tx1) + gen.AddTx(tx2) + case 2: + // Block 3 is empty but was mined by addr3. + gen.SetCoinbase(addr3) + gen.SetExtra([]byte("yeehaw")) + case 3: + // Block 4 includes blocks 2 and 3 as uncle headers (with modified extra data). + b2 := gen.PrevBlock(1).Header() + b2.Extra = []byte("foo") + gen.AddUncle(b2) + b3 := gen.PrevBlock(2).Header() + b3.Extra = []byte("foo") + gen.AddUncle(b3) + } + }, nodeSet, 30) + + // Import the chain. This runs all block validation rules. + blockchain, _ := NewBlockChain(db, nil, gspec.Config, ethash.NewFaker(), vm.Config{}) + defer blockchain.Stop() + + if i, err := blockchain.InsertChain(chain); err != nil { + fmt.Printf("insert error (block %d): %v\n", chain[i].NumberU64(), err) + return + } + + state, _ := blockchain.State() + fmt.Printf("last block: #%d\n", blockchain.CurrentBlock().Number()) + fmt.Println("balance of addr1:", state.GetBalance(addr1)) + fmt.Println("balance of addr2:", state.GetBalance(addr2)) + fmt.Println("balance of addr3:", state.GetBalance(addr3)) + // Output: + // last block: #5 + // balance of addr1: 989000 + // balance of addr2: 10000 + // balance of addr3: 19687500000000001000 +} diff --git a/core/genesis.go b/core/genesis.go index 2412ede30..8b957edd2 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -257,6 +257,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { totalStaked := big.NewInt(0) for addr, account := range g.Alloc { + if account.Staked == nil { + account.Staked = big.NewInt(0) + } statedb.AddBalance(addr, new(big.Int).Sub(account.Balance, account.Staked)) statedb.SetCode(addr, account.Code) statedb.SetNonce(addr, account.Nonce) @@ -278,6 +281,9 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { for _, addr := range keys { account := g.Alloc[addr] + if account.Staked == nil { + account.Staked = big.NewInt(0) + } if account.Staked.Cmp(big.NewInt(0)) > 0 { govStateHelper.Stake(addr, account.PublicKey, account.Staked) } -- cgit v1.2.3