// Copyright 2018 The dexon-consensus-core Authors // This file is part of the dexon-consensus-core library. // // The dexon-consensus-core 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 dexon-consensus-core 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 dexon-consensus-core library. If not, see // . package dex import ( "bytes" "fmt" "math/big" "sync" "time" coreCommon "github.com/dexon-foundation/dexon-consensus-core/common" coreTypes "github.com/dexon-foundation/dexon-consensus-core/core/types" "github.com/dexon-foundation/dexon/common" "github.com/dexon-foundation/dexon/common/math" "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/log" "github.com/dexon-foundation/dexon/rlp" ) // DexconApp implementes the DEXON consensus core application interface. type DexconApp struct { 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 } type witnessData struct { Root common.Hash TxHash common.Hash ReceiptHash common.Hash } func NewDexconApp(txPool *core.TxPool, blockchain *core.BlockChain, gov *DexconGovernance, chainDB ethdb.Database, config *Config, vmConfig vm.Config) *DexconApp { return &DexconApp{ txPool: txPool, blockchain: blockchain, gov: gov, chainDB: chainDB, config: config, vmConfig: vmConfig, notifyChan: make(map[uint64]*notify), mutex: &sync.Mutex{}, } } 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 } 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) } } } func (d *DexconApp) checkChain(address common.Address, chainSize, chainID *big.Int) bool { addrModChainSize := new(big.Int) return addrModChainSize.Mod(address.Big(), chainSize).Cmp(chainID) == 0 } // PreparePayload is called when consensus core is preparing payload for block. func (d *DexconApp) PreparePayload(position coreTypes.Position) (payload []byte, err error) { 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 totalGasUsed uint64 for addr, txs := range txsMap { // every address's transactions will appear in fixed chain if !d.checkChain(addr, chainSize, chainID) { continue } undeliveredTxs, err := d.blockchain.GetConfirmedTxsByAddress(position.ChainID, addr) if err != nil { return nil, err } for _, tx := range undeliveredTxs { // confirmed txs must apply successfully var gasUsed uint64 gp := new(core.GasPool).AddGas(math.MaxUint64) _, _, err = core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &gasUsed, d.vmConfig) if err != nil { log.Error("apply confirmed transaction", "error", err) return nil, err } } for _, tx := range txs { // apply transaction to calculate total gas used, validate nonce and check available balance _, _, err = core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &totalGasUsed, d.vmConfig) if err != nil || totalGasUsed > gasLimit { log.Debug("apply transaction fail", "error", err, "totalGasUsed", totalGasUsed, "gasLimit", gasLimit) break } allTxs = append(allTxs, tx) } } payload, err = rlp.EncodeToBytes(&allTxs) if err != nil { return } return } // PrepareWitness will return the witness data no lower than consensusHeight. func (d *DexconApp) PrepareWitness(consensusHeight uint64) (witness coreTypes.Witness, err error) { var currentBlock *types.Block currentBlock = d.blockchain.CurrentBlock() if currentBlock.NumberU64() < consensusHeight { // wait notification if <-d.addNotify(consensusHeight) { currentBlock = d.blockchain.CurrentBlock() } else { err = fmt.Errorf("fail to wait notification") return } } 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, }, nil } // 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 } currentBlock := d.blockchain.CurrentBlock() chainID := new(big.Int).SetUint64(uint64(block.Position.ChainID)) chainSize := new(big.Int).SetUint64(uint64(d.gov.Configuration(block.Position.Round).NumChains)) stateDB, err := state.New(currentBlock.Root(), state.NewDatabase(d.chainDB)) if err != nil { return false } // verify transactions addressMap := map[common.Address]interface{}{} gasLimit := core.CalcGasLimit(currentBlock, d.config.GasFloor, d.config.GasCeil) gp := new(core.GasPool).AddGas(gasLimit) var totalGasUsed uint64 for _, transaction := range transactions { msg, err := transaction.AsMessage(types.MakeSigner(d.blockchain.Config(), currentBlock.Header().Number)) if err != nil { return false } if !d.checkChain(msg.From(), chainSize, chainID) { log.Error("check chain fail", "from", msg.From().String(), "chainSize", chainSize, "chainID", chainID) return false } _, exist := addressMap[msg.From()] if !exist { txs, err := d.blockchain.GetConfirmedTxsByAddress(block.Position.ChainID, msg.From()) if err != nil { log.Error("get confirmed txs by address", "error", err) return false } gp := new(core.GasPool).AddGas(math.MaxUint64) for _, tx := range txs { var gasUsed uint64 // confirmed txs must apply successfully _, _, err := core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &gasUsed, d.vmConfig) if err != nil { log.Error("apply confirmed transaction", "error", err) return false } } addressMap[msg.From()] = nil } _, _, err = core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), transaction, &totalGasUsed, d.vmConfig) if err != nil { log.Error("apply block transaction", "error", err) return false } } if totalGasUsed > 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.TxHash { // invalid tx root of witness data return false } for _, transaction := range witnessBlock.Transactions() { tx, _, _, _ := rawdb.ReadTransaction(d.chainDB, transaction.Hash()) if tx == nil { return false } } return true } // BlockDelivered is called when a block is add to the compaction chain. func (d *DexconApp) BlockDelivered(blockHash coreCommon.Hash, result coreTypes.FinalizationResult) { block := d.blockchain.GetConfirmedBlockByHash(blockHash) if block == nil { // do something log.Error("can not get confirmed block") return } var transactions types.Transactions err := rlp.Decode(bytes.NewReader(block.Payload), &transactions) if err != nil { log.Error("payload rlp decode", "error", err) return } _, err = d.blockchain.InsertChain( []*types.Block{types.NewBlock(&types.Header{ ParentHash: common.Hash(block.ParentHash), Number: new(big.Int).SetUint64(result.Height), Time: new(big.Int).SetInt64(result.Timestamp.Unix()), TxHash: types.DeriveSha(transactions), Coinbase: common.BytesToAddress(block.ProposerID.Hash[:]), }, transactions, nil, nil)}) if err != nil { log.Error("insert chain", "error", err) return } d.blockchain.RemoveConfirmedBlock(blockHash) d.notify(result.Height) } // BlockConfirmed is called when a block is confirmed and added to lattice. func (d *DexconApp) BlockConfirmed(block coreTypes.Block) { d.blockchain.AddConfirmedBlock(&block) }