// 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
// <http://www.gnu.org/licenses/>.
package dex
import (
"bytes"
"fmt"
"github.com/dexon-foundation/dexon/log"
"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/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
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 gasUsed uint64
for addr, txs := range txsMap {
// every address's transactions will appear in fixed chain
if !d.checkChain(addr, chainSize, chainID) {
continue
}
var nonce uint64
for i, tx := range txs {
// validate start nonce
// check undelivered block first
// or else check compaction chain state
if i == 0 {
nonce = tx.Nonce()
msg, err := tx.AsMessage(types.MakeSigner(d.blockchain.Config(), currentBlock.Header().Number))
if err != nil {
return nil, err
}
undeliveredNonce, exist, err := d.blockchain.GetNonceInConfirmedBlock(position.ChainID, msg.From())
if err != nil {
return nil, err
} else if exist {
if msg.Nonce() != undeliveredNonce+1 {
break
}
} else {
stateDB, err := d.blockchain.State()
if err != nil {
return nil, err
}
if msg.Nonce() != stateDB.GetNonce(msg.From())+1 {
break
}
}
} else if tx.Nonce() != nonce+1 { // check if nonce is sequential
break
}
core.ApplyTransaction(d.blockchain.Config(), d.blockchain, nil, gp, stateDB, currentBlock.Header(), tx, &gasUsed, d.vmConfig)
if gasUsed > gasLimit {
break
}
allTxs = append(allTxs, tx)
nonce = tx.Nonce()
}
}
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))
// verify transactions
addressNonce := map[common.Address]*struct {
Min uint64
Max uint64
}{}
for _, transaction := range transactions {
if d.txPool.ValidateTx(transaction, false) != nil {
return false
}
msg, err := transaction.AsMessage(types.MakeSigner(d.blockchain.Config(), currentBlock.Header().Number))
if err != nil {
return false
}
if !d.checkChain(msg.From(), chainSize, chainID) {
return false
}
nonce, exist := addressNonce[msg.From()]
if !exist {
addressNonce[msg.From()] = &struct {
Min uint64
Max uint64
}{Min: msg.Nonce(), Max: msg.Nonce()}
} else if msg.Nonce() != nonce.Max+1 {
// address nonce is not sequential
return false
}
nonce.Max++
}
for address, nonce := range addressNonce {
undeliveredNonce, exist, err := d.blockchain.GetNonceInConfirmedBlock(block.Position.ChainID, address)
if err != nil {
return false
} else if exist {
if nonce.Min != undeliveredNonce+1 {
return false
}
} else {
stateDB, err := d.blockchain.State()
if err != nil {
return false
}
if nonce.Min != stateDB.GetNonce(address)+1 {
return false
}
}
}
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.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: %v", 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: %v", 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)
}