From 9b6a659b4de7c9b1c2f331b880a1397159181600 Mon Sep 17 00:00:00 2001 From: Bojie Wu Date: Tue, 9 Oct 2018 13:28:45 +0800 Subject: dex: implement dexon application interface --- consensus/dexcon/dexcon.go | 7 +- core/tx_pool.go | 4 + dex/app.go | 223 ++++++++++++++++++++++++++++++++++++++++++--- dex/backend.go | 70 +++++++++++++- dex/config.go | 9 ++ 5 files changed, 295 insertions(+), 18 deletions(-) diff --git a/consensus/dexcon/dexcon.go b/consensus/dexcon/dexcon.go index 9004106aa..919e920dc 100644 --- a/consensus/dexcon/dexcon.go +++ b/consensus/dexcon/dexcon.go @@ -27,6 +27,8 @@ import ( "github.com/dexon-foundation/dexon/rpc" ) +var blockReward = big.NewInt(5e+18) + // Config is the configuration for DEXON consensus. type Config struct { } @@ -99,7 +101,10 @@ func (d *Dexcon) Prepare(chain consensus.ChainReader, header *types.Header) erro // Finalize implements consensus.Engine, ensuring no uncles are set, nor block // rewards given, and returns the final block. func (d *Dexcon) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { - return nil, nil + state.AddBalance(header.Coinbase, blockReward) + header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) + + return types.NewBlock(header, txs, uncles, receipts), nil } // Seal implements consensus.Engine, attempting to create a sealed block using diff --git a/core/tx_pool.go b/core/tx_pool.go index eccf82e93..d79512d9c 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -1157,6 +1157,10 @@ func (pool *TxPool) demoteUnexecutables() { } } +func (pool *TxPool) ValidateTx(tx *types.Transaction, local bool) error { + return pool.validateTx(tx, local) +} + // addressByHeartbeat is an account address tagged with its last activity timestamp. type addressByHeartbeat struct { address common.Address diff --git a/dex/app.go b/dex/app.go index a8b04577d..80384ddc9 100644 --- a/dex/app.go +++ b/dex/app.go @@ -18,37 +18,234 @@ package dex import ( - "github.com/dexon-foundation/dexon-consensus-core/core/types" + "bytes" + "math/big" + "sync" + "time" + coreTypes "github.com/dexon-foundation/dexon-consensus-core/core/types" + + "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/core" + "github.com/dexon-foundation/dexon/core/rawdb" + "github.com/dexon-foundation/dexon/core/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/rlp" ) // DexconApp implementes the DEXON consensus core application interface. type DexconApp struct { - txPool *core.TxPool + txPool *core.TxPool + blockchain *core.BlockChain + gov *DexconGovernance + chainDB ethdb.Database + config *Config + vmConfig vm.Config + + notifyChan map[uint64]*notify + mutex *sync.Mutex +} + +type notify struct { + results []chan bool } -func NewDexconApp(txPool *core.TxPool) *DexconApp { +func NewDexconApp(txPool *core.TxPool, blockchain *core.BlockChain, gov *DexconGovernance, chainDB ethdb.Database, config *Config, vmConfig vm.Config) *DexconApp { return &DexconApp{ - txPool: txPool, + txPool: txPool, + blockchain: blockchain, + gov: gov, + chainDB: chainDB, + config: config, + vmConfig: vmConfig, + notifyChan: make(map[uint64]*notify), + mutex: &sync.Mutex{}, } } -// PreparePayload is called when consensus core is preparing a block. -func (d *DexconApp) PreparePayload(position types.Position) []byte { - return nil +func (d *DexconApp) addNotify(height uint64) <-chan bool { + d.mutex.Lock() + defer d.mutex.Unlock() + result := make(chan bool) + if n, exist := d.notifyChan[height]; exist { + n.results = append(n.results, result) + } else { + d.notifyChan[height] = ¬ify{} + d.notifyChan[height].results = append(d.notifyChan[height].results, result) + } + return result } -// PrepareWitness will return the witness data no lower than consensusHeight. -func (d *DexconApp) PrepareWitness(consensusHeight uint64) types.Witness { - return types.Witness{} +func (d *DexconApp) notify(height uint64) { + d.mutex.Lock() + defer d.mutex.Unlock() + for h, n := range d.notifyChan { + if height >= h { + for _, ch := range n.results { + ch <- true + } + delete(d.notifyChan, h) + } + } } -// VerifyPayload verifies if the payloads are valid. -func (d *DexconApp) VerifyBlock(block *types.Block) bool { +// PreparePayload is called when consensus core is preparing payload for block. +func (d *DexconApp) PreparePayload(position coreTypes.Position) (payload []byte) { + txsMap, err := d.txPool.Pending() + if err != nil { + return + } + + currentBlock := d.blockchain.CurrentBlock() + gasLimit := core.CalcGasLimit(currentBlock, d.config.GasFloor, d.config.GasCeil) + gp := new(core.GasPool).AddGas(gasLimit) + + stateDB, err := state.New(currentBlock.Root(), state.NewDatabase(d.chainDB)) + if err != nil { + return + } + + chainID := new(big.Int).SetUint64(uint64(position.ChainID)) + chainSize := new(big.Int).SetUint64(uint64(d.gov.Configuration(position.Round).NumChains)) + var allTxs types.Transactions + var gasUsed uint64 + for addr, txs := range txsMap { + addrModChainSize := new(big.Int) + if addrModChainSize.Mod(addr.Big(), chainSize).Cmp(chainID) != 0 { + continue + } + + for _, tx := range txs { + core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &gasUsed, d.vmConfig) + if gasUsed > gasLimit { + break + } + allTxs = append(allTxs, tx) + } + } + payload, err = rlp.EncodeToBytes(&allTxs) + if err != nil { + // do something + return + } + + return +} + +type WitnessData struct { + Root common.Hash + TxHash common.Hash + ReceiptHash common.Hash +} + +func (d *DexconApp) PrepareWitness(consensusHeight uint64) (witness coreTypes.Witness) { + var currentBlock *types.Block + currentBlock = d.blockchain.CurrentBlock() + if currentBlock.NumberU64() < consensusHeight { + // wait notification + if <-d.addNotify(consensusHeight) { + currentBlock = d.blockchain.CurrentBlock() + } else { + // do something if notify fail + } + } + + witnessData, err := rlp.EncodeToBytes(&WitnessData{ + Root: currentBlock.Root(), + TxHash: currentBlock.TxHash(), + ReceiptHash: currentBlock.ReceiptHash(), + }) + if err != nil { + return + } + + return coreTypes.Witness{ + Timestamp: time.Unix(currentBlock.Time().Int64(), 0), + Height: currentBlock.NumberU64(), + Data: witnessData, + } +} + +// VerifyBlock verifies if the payloads are valid. +func (d *DexconApp) VerifyBlock(block *coreTypes.Block) bool { + // decode payload to transactions + var transactions types.Transactions + err := rlp.Decode(bytes.NewReader(block.Payload), &transactions) + if err != nil { + return false + } + + // verify transactions + for _, transaction := range transactions { + tx, _, _, _ := rawdb.ReadTransaction(d.chainDB, transaction.Hash()) + if tx == nil || d.txPool.ValidateTx(transaction, false) != nil { + return false + } + } + + currentBlock := d.blockchain.CurrentBlock() + gasLimit := core.CalcGasLimit(currentBlock, d.config.GasFloor, d.config.GasCeil) + gp := new(core.GasPool).AddGas(gasLimit) + + stateDB, err := state.New(currentBlock.Root(), state.NewDatabase(d.chainDB)) + if err != nil { + return false + } + + var gasUsed uint64 + for _, tx := range transactions { + core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &gasUsed, d.vmConfig) + } + + if gasUsed > gasLimit+d.config.GasLimitTolerance { + return false + } + + witnessData := WitnessData{} + err = rlp.Decode(bytes.NewReader(block.Witness.Data), &witnessData) + if err != nil { + return false + } + + witnessBlock := d.blockchain.GetBlockByNumber(block.Witness.Height) + if witnessBlock == nil { + return false + } else if witnessBlock.Root() != witnessData.Root { + // invalid state root of witness data + return false + } else if witnessBlock.ReceiptHash() != witnessData.ReceiptHash { + // invalid receipt root of witness data + return false + } else if witnessBlock.TxHash() != witnessData.ReceiptHash { + // invalid tx root of witness data + return false + } + return true } // BlockDelivered is called when a block is add to the compaction chain. -func (d *DexconApp) BlockDelivered(block types.Block) { +func (d *DexconApp) BlockDelivered(block coreTypes.Block) { + var transactions types.Transactions + err := rlp.Decode(bytes.NewReader(block.Payload), &transactions) + if err != nil { + return + } + + _, err = d.blockchain.InsertChain( + []*types.Block{types.NewBlock(&types.Header{ + ParentHash: common.Hash(block.ParentHash), + Number: new(big.Int).SetUint64(block.ConsensusHeight), + Time: new(big.Int).SetInt64(block.ConsensusTimestamp.Unix()), + TxHash: types.DeriveSha(transactions), + Coinbase: common.BytesToAddress(block.ProposerID.Hash[:]), + }, transactions, nil, nil)}) + if err != nil { + // do something + return + } + + d.notify(block.ConsensusHeight) } diff --git a/dex/backend.go b/dex/backend.go index 707b1abbb..b74636de4 100644 --- a/dex/backend.go +++ b/dex/backend.go @@ -18,17 +18,23 @@ package dex import ( + "fmt" + dexCore "github.com/dexon-foundation/dexon-consensus-core/core" "github.com/dexon-foundation/dexon-consensus-core/core/blockdb" "github.com/dexon-foundation/dexon-consensus-core/core/crypto/ecdsa" "github.com/dexon-foundation/dexon/accounts" "github.com/dexon-foundation/dexon/consensus" + "github.com/dexon-foundation/dexon/consensus/dexcon" "github.com/dexon-foundation/dexon/core" "github.com/dexon-foundation/dexon/core/bloombits" + "github.com/dexon-foundation/dexon/core/rawdb" + "github.com/dexon-foundation/dexon/core/vm" "github.com/dexon-foundation/dexon/ethdb" "github.com/dexon-foundation/dexon/event" "github.com/dexon-foundation/dexon/internal/ethapi" + "github.com/dexon-foundation/dexon/log" "github.com/dexon-foundation/dexon/node" "github.com/dexon-foundation/dexon/p2p" "github.com/dexon-foundation/dexon/params" @@ -72,7 +78,6 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) { if err != nil { panic(err) } - app := NewDexconApp(nil) gov := NewDexconGovernance() network := NewDexconNetwork() @@ -81,8 +86,26 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) { if err != nil { panic(err) } - consensus := dexCore.NewConsensus(app, gov, db, network, privKey) + chainDb, err := CreateDB(ctx, config, "chaindata") + if err != nil { + return nil, err + } + chainConfig, genesisHash, genesisErr := core.SetupGenesisBlock(chainDb, + config.Genesis) + if _, ok := genesisErr.(*params.ConfigCompatError); genesisErr != nil && !ok { + return nil, genesisErr + } + log.Info("Initialised chain configuration", "config", chainConfig) + + if !config.SkipBcVersionCheck { + bcVersion := rawdb.ReadDatabaseVersion(chainDb) + if bcVersion != nil && *bcVersion != core.BlockChainVersion { + return nil, fmt.Errorf("Blockchain DB version mismatch (%d / %d).\n", + bcVersion, core.BlockChainVersion) + } + rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) + } dex := &Dexon{ config: config, eventMux: ctx.EventMux, @@ -90,12 +113,39 @@ func New(ctx *node.ServiceContext, config *Config) (*Dexon, error) { shutdownChan: make(chan bool), networkID: config.NetworkId, bloomRequests: make(chan chan *bloombits.Retrieval), - app: app, governance: gov, network: network, blockdb: db, - consensus: consensus, + engine: dexcon.New(¶ms.DexconConfig{}), + } + + var ( + vmConfig = vm.Config{ + EnablePreimageRecording: config.EnablePreimageRecording, + EWASMInterpreter: config.EWASMInterpreter, + EVMInterpreter: config.EVMInterpreter, + } + cacheConfig = &core.CacheConfig{Disabled: config.NoPruning, TrieCleanLimit: config.TrieCleanCache, TrieDirtyLimit: config.TrieDirtyCache, TrieTimeLimit: config.TrieTimeout} + ) + dex.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, dex.chainConfig, dex.engine, vmConfig, nil) + + // Rewind the chain in case of an incompatible config upgrade. + if compat, ok := genesisErr.(*params.ConfigCompatError); ok { + log.Warn("Rewinding chain to upgrade configuration", "err", compat) + dex.blockchain.SetHead(compat.RewindTo) + rawdb.WriteChainConfig(chainDb, genesisHash, chainConfig) + } + dex.bloomIndexer.Start(dex.blockchain) + + if config.TxPool.Journal != "" { + config.TxPool.Journal = ctx.ResolvePath(config.TxPool.Journal) } + dex.txPool = core.NewTxPool(config.TxPool, dex.chainConfig, dex.blockchain) + + dex.app = NewDexconApp(dex.txPool, dex.blockchain, gov, chainDb, config, vmConfig) + + dex.consensus = dexCore.NewConsensus(dex.app, gov, db, network, privKey) + return dex, nil } @@ -114,3 +164,15 @@ func (s *Dexon) Start(server *p2p.Server) error { func (s *Dexon) Stop() error { return nil } + +// CreateDB creates the chain database. +func CreateDB(ctx *node.ServiceContext, config *Config, name string) (ethdb.Database, error) { + db, err := ctx.OpenDatabase(name, config.DatabaseCache, config.DatabaseHandles) + if err != nil { + return nil, err + } + if db, ok := db.(*ethdb.LDBDatabase); ok { + db.Meter("eth/db/chaindata/") + } + return db, nil +} diff --git a/dex/config.go b/dex/config.go index fa9988bfb..5a43496ab 100644 --- a/dex/config.go +++ b/dex/config.go @@ -45,6 +45,10 @@ var DefaultConfig = Config{ Blocks: 20, Percentile: 60, }, + + GasFloor: 8000000, + GasCeil: 8000000, + GasLimitTolerance: 1000000, } func init() { @@ -86,6 +90,11 @@ type Config struct { TrieDirtyCache int TrieTimeout time.Duration + // For calculate gas limit + GasFloor uint64 + GasCeil uint64 + GasLimitTolerance uint64 + // Dexcon options Dexcon dexcon.Config -- cgit