From a42a4b84481d3aafe1f6925b33fcb58853f9db33 Mon Sep 17 00:00:00 2001 From: elvis88 <41582716+elvis88@users.noreply.github.com> Date: Tue, 9 Jul 2019 13:45:24 +0800 Subject: [PATCH] Dposfork (#366) * fork dpos --- blockchain/genesis.go | 10 +- consensus/consensus.go | 4 +- consensus/dpos/api.go | 205 ++++++++++++- consensus/dpos/config.go | 10 +- consensus/dpos/database.go | 80 +++-- consensus/dpos/dpos.go | 499 ++++++++++++++++++++++++++++---- consensus/dpos/ldb.go | 66 ++++- consensus/dpos/ldb_test.go | 28 +- consensus/dpos/processor.go | 64 +++- consensus/dpos/vote.go | 450 ++++++++++++++++++++++++++-- consensus/dpos/vote_test.go | 36 +-- consensus/miner/miner.go | 7 - consensus/miner/worker.go | 126 ++++---- nodes.txt | 1 + params/chainconfig.go | 9 +- processor/evm.go | 4 +- processor/transition.go | 6 +- processor/vm/instructions.go | 8 +- processor/vm/runtime/runtime.go | 4 +- processor/vm/vm.go | 2 +- types/action.go | 4 + 21 files changed, 1370 insertions(+), 253 deletions(-) create mode 100644 nodes.txt diff --git a/blockchain/genesis.go b/blockchain/genesis.go index 9c687283..b3fdaf7a 100644 --- a/blockchain/genesis.go +++ b/blockchain/genesis.go @@ -413,8 +413,14 @@ func (g *Genesis) ToBlock(db fdb.Database) (*types.Block, []*types.Receipt, erro return nil, nil, fmt.Errorf("genesis create candidate err %v", err) } } - if err := sys.UpdateElectedCandidates(epoch, epoch, number.Uint64(), ""); err != nil { - return nil, nil, fmt.Errorf("genesis create candidate err %v", err) + if fid := g.ForkID; fid >= params.ForkID2 { + if err := sys.UpdateElectedCandidates1(epoch, epoch, number.Uint64(), ""); err != nil { + return nil, nil, fmt.Errorf("genesis create candidate err %v", err) + } + } else { + if err := sys.UpdateElectedCandidates0(epoch, epoch, number.Uint64(), ""); err != nil { + return nil, nil, fmt.Errorf("genesis create candidate err %v", err) + } } // init fork controller diff --git a/consensus/consensus.go b/consensus/consensus.go index 8467a09d..4df06eeb 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -98,7 +98,7 @@ type IEngine interface { Engine() IEngine - ProcessAction(number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) + ProcessAction(fid uint64, number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) GetDelegatedByTime(state *state.StateDB, candidate string, timestamp uint64) (stake *big.Int, err error) @@ -112,7 +112,7 @@ type IEngine interface { GetActivedCandidateSize(state *state.StateDB, epoch uint64) (size uint64, err error) - GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, err error) + GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, isbad bool, err error) GetCandidateStake(state *state.StateDB, epoch uint64, candidate string) (stake *big.Int, err error) diff --git a/consensus/dpos/api.go b/consensus/dpos/api.go index 41a21934..cc95c69c 100644 --- a/consensus/dpos/api.go +++ b/consensus/dpos/api.go @@ -19,8 +19,10 @@ package dpos import ( "fmt" "math/big" + "sort" "time" + "github.com/ethereum/go-ethereum/log" "github.com/fractalplatform/fractal/consensus" "github.com/fractalplatform/fractal/rpc" ) @@ -65,8 +67,6 @@ func (api *API) Epoch(height uint64) (uint64, error) { // PrevEpoch get prev epoch number by epoch func (api *API) PrevEpoch(epoch uint64) (uint64, error) { - var e uint64 - if epoch == 0 { epoch, _ = api.epoch(api.chain.CurrentHeader().Number.Uint64()) } @@ -74,15 +74,12 @@ func (api *API) PrevEpoch(epoch uint64) (uint64, error) { if err != nil { return 0, err } - - e, _, err = api.dpos.GetEpoch(state, 1, epoch) - return e, err + pepoch, _, err := api.dpos.GetEpoch(state, 1, epoch) + return pepoch, err } // NextEpoch get next epoch number by epoch func (api *API) NextEpoch(epoch uint64) (uint64, error) { - var e uint64 - if epoch == 0 { epoch, _ = api.epoch(api.chain.CurrentHeader().Number.Uint64()) } @@ -90,9 +87,8 @@ func (api *API) NextEpoch(epoch uint64) (uint64, error) { if err != nil { return 0, err } - - e, _, err = api.dpos.GetEpoch(state, 2, epoch) - return e, err + nepoch, _, err := api.dpos.GetEpoch(state, 2, epoch) + return nepoch, err } // CandidatesSize get candidates size @@ -120,6 +116,7 @@ func (api *API) Candidates(epoch uint64, detail bool) (interface{}, error) { if err != nil { return nil, err } + sort.Sort(candidates) if detail { return candidates, nil } @@ -220,6 +217,191 @@ func (api *API) ValidCandidates(epoch uint64) (interface{}, error) { return sys.GetState(gstate.PreEpoch) } +func (api *API) BrowserAllEpoch() (interface{}, error) { + epochs := Epochs{} + epochs.Data = make([]*Epoch, 0) + epochNumber, _ := api.epoch(api.chain.CurrentHeader().Number.Uint64()) + sys, err := api.system() + if err != nil { + return nil, err + } + for { + data := &Epoch{} + timestamp := sys.config.epochTimeStamp(epochNumber) + gstate, err := sys.GetState(epochNumber) + if err != nil { + return nil, err + } + if sys.config.epoch(sys.config.ReferenceTime) == gstate.PreEpoch { + timestamp = sys.config.epochTimeStamp(gstate.PreEpoch) + } + + data.Start = timestamp / 1000000000 + data.Epoch = epochNumber + epochs.Data = append(epochs.Data, data) + if epochNumber == 1 { + break + } + // log.Info("BrowserAllEpoch1", "number", epochNumber) + epochNumber = gstate.PreEpoch + // log.Info("BrowserAllEpoch2", "number", epochNumber) + } + return epochs, nil +} + +func (api *API) BrowserEpochRecord(reqEpochNumber uint64) (interface{}, error) { + var req, data uint64 + if reqEpochNumber == 0 { + log.Warn("BrowserAccounting 0") + return nil, fmt.Errorf("request:0") + } + + vote, _ := api.epoch(api.chain.CurrentHeader().Number.Uint64()) + if reqEpochNumber > vote { + log.Warn("BrowserAccounting", " request:", reqEpochNumber, "> vote:", vote) + return nil, fmt.Errorf("request:%d > vote:%d", reqEpochNumber, vote) + } + req = reqEpochNumber + + sys, err := api.system() + if err != nil { + return nil, err + } + log.Info("BrowserAccounting", "req epoch:", req) + reqEpoch, err := sys.GetState(req) + if err != nil { + return nil, err + } + + data = reqEpoch.PreEpoch + log.Info("BrowserAccounting", "data epoch:", data) + timestamp := sys.config.epochTimeStamp(data) + dataEpoch, err := sys.GetState(data) + if err != nil { + return nil, err + } + + candidateInfos := ArrayCandidateInfoForBrowser{} + candidateInfos.Data = make([]*CandidateInfoForBrowser, len(dataEpoch.ActivatedCandidateSchedule)) + + var spare = 7 + var activate = 21 + for i, activatedCandidate := range dataEpoch.ActivatedCandidateSchedule { + candidateInfo := &CandidateInfoForBrowser{} + candidateInfo.Candidate = activatedCandidate + + curtmp, err := sys.GetCandidate(req, activatedCandidate) + if err != nil { + log.Warn("BrowserAccounting Cur Candidate:", req, " Data not found") + return nil, fmt.Errorf("Cur Candidate:%d Data not found", req) + } + candidateInfo.NowCounter = curtmp.Counter + candidateInfo.NowActualCounter = curtmp.ActualCounter + + tmp, err := sys.GetCandidate(data, activatedCandidate) + if err != nil { + return nil, err + } + candidateInfo.Quantity = tmp.Quantity.Mul(tmp.Quantity, api.dpos.config.unitStake()).String() + candidateInfo.TotalQuantity = tmp.TotalQuantity.String() + candidateInfo.Counter = tmp.Counter + candidateInfo.ActualCounter = tmp.ActualCounter + if i < activate { + candidateInfo.Status = 1 + } else { + candidateInfo.Status = 2 + } + // candidateInfo.Status + if balance, err := sys.GetBalanceByTime(activatedCandidate, timestamp); err != nil { + log.Warn("BrowserAccounting", "candidate", activatedCandidate, "ignore", err) + return nil, err + } else { + candidateInfo.Holder = balance.String() + } + candidateInfos.Data[i] = candidateInfo + } + + fmt.Println(dataEpoch.BadCandidateIndexSchedule, len(candidateInfos.Data)) + if len(dataEpoch.BadCandidateIndexSchedule) > 14 { + log.Warn("BrowserAccounting BadCandidateIndexSchedule > 14", "epoch", data) + return nil, fmt.Errorf("count %d > 14", len(dataEpoch.BadCandidateIndexSchedule)) + } + for i := 0; i < len(dataEpoch.BadCandidateIndexSchedule); i++ { + j := i + activate + if i < spare && len(candidateInfos.Data) > j { + // log.Info("***** i", "", i) + // log.Info("***** j", "", j) + // log.Info("***** len", "", len(dataEpoch.BadCandidateIndexSchedule)) + candidateInfos.Data[dataEpoch.BadCandidateIndexSchedule[i]].Status = 0 + // log.Info("*********") + candidateInfos.Data[j].Status = 1 + // log.Info("&&&&&&&&&") + } else { + candidateInfos.Data[dataEpoch.BadCandidateIndexSchedule[i]].Status = 0 + } + } + return candidateInfos, nil +} + +func (api *API) BrowserVote() (interface{}, error) { + vote, _ := api.epoch(api.chain.CurrentHeader().Number.Uint64()) + + sys, err := api.system() + if err != nil { + return nil, err + } + log.Info("BrowserVote", "vote:", vote) + reqEpoch, err := sys.GetState(vote) + if err != nil { + return nil, err + } + + //history miner rate + prereqEpoch, err := sys.GetState(reqEpoch.PreEpoch) + if err != nil { + return nil, err + } + + history := prereqEpoch.PreEpoch + log.Info("BrowserVote", "history:", history) + timestamp := sys.config.epochTimeStamp(vote) + + candidateInfos := ArrayCandidateInfoForBrowser{} + candidateInfos.Data = make([]*CandidateInfoForBrowser, len(reqEpoch.ActivatedCandidateSchedule)) + + for i, activatedCandidate := range reqEpoch.ActivatedCandidateSchedule { + candidateInfo := &CandidateInfoForBrowser{} + candidateInfo.Candidate = activatedCandidate + + tmp, err := sys.GetCandidate(vote, activatedCandidate) + if err != nil { + return nil, err + } + // candidateInfo.NowCounter = tmp.Counter + // candidateInfo.NowActualCounter = tmp.ActualCounter + + candidateInfo.Quantity = tmp.Quantity.Mul(tmp.Quantity, api.dpos.config.unitStake()).String() + candidateInfo.TotalQuantity = tmp.TotalQuantity.String() + + // candidateInfo.Type + if balance, err := sys.GetBalanceByTime(activatedCandidate, timestamp); err != nil { + log.Warn("Accounting", "candidate", activatedCandidate, "ignore", err) + return nil, err + } else { + candidateInfo.Holder = balance.String() + } + + histmp, err := sys.GetCandidate(history, activatedCandidate) + if err != nil { + return nil, err + } + candidateInfo.Counter = histmp.Counter + candidateInfo.ActualCounter = histmp.ActualCounter + candidateInfos.Data[i] = candidateInfo + } + return candidateInfos, nil +} + // SnapShotTime get snapshot timestamp func (api *API) SnapShotTime(epoch uint64) (interface{}, error) { if epoch == 0 { @@ -298,7 +480,7 @@ func (api *API) GetActivedCandidate(epoch uint64, index uint64) (interface{}, er if err != nil { return nil, err } - candidate, delegated, voted, scounter, acounter, rindex, err := api.dpos.GetActivedCandidate(state, epoch, index) + candidate, delegated, voted, scounter, acounter, rindex, isbad, err := api.dpos.GetActivedCandidate(state, epoch, index) if err != nil { return nil, err } @@ -310,5 +492,6 @@ func (api *API) GetActivedCandidate(epoch uint64, index uint64) (interface{}, er ret["shouldCount"] = scounter ret["actualCount"] = acounter ret["replaceIndex"] = rindex + ret["bad"] = isbad return ret, nil } diff --git a/consensus/dpos/config.go b/consensus/dpos/config.go index 0b7d1000..aa07de7b 100644 --- a/consensus/dpos/config.go +++ b/consensus/dpos/config.go @@ -21,6 +21,8 @@ import ( "math/big" "sync/atomic" "time" + + "github.com/fractalplatform/fractal/params" ) // DefaultConfig configures @@ -148,8 +150,12 @@ func (cfg *Config) nextslot(timestamp uint64) uint64 { return cfg.slot(timestamp) + cfg.blockInterval() } -func (cfg *Config) getoffset(timestamp uint64) uint64 { - offset := uint64(timestamp-cfg.blockInterval()-cfg.ReferenceTime) % cfg.epochInterval() % cfg.mepochInterval() +func (cfg *Config) getoffset(timestamp uint64, fid uint64) uint64 { + offsetInterval := cfg.blockInterval() + if fid >= params.ForkID2 { + offsetInterval = 0 + } + offset := uint64(timestamp-offsetInterval-cfg.ReferenceTime) % cfg.epochInterval() % cfg.mepochInterval() offset /= cfg.blockInterval() * cfg.BlockFrequency return offset } diff --git a/consensus/dpos/database.go b/consensus/dpos/database.go index 42b8ab7a..69ee6e8e 100644 --- a/consensus/dpos/database.go +++ b/consensus/dpos/database.go @@ -26,17 +26,17 @@ import ( "github.com/fractalplatform/fractal/types" ) -// LastEpoch latest -var LastEpoch = uint64(math.MaxUint64) - // IDB dpos database type IDB interface { SetCandidate(*CandidateInfo) error DelCandidate(uint64, string) error GetCandidate(uint64, string) (*CandidateInfo, error) - GetCandidates(uint64) ([]*CandidateInfo, error) + GetCandidates(uint64) (CandidateInfoArray, error) CandidatesSize(uint64) (uint64, error) + SetActivatedCandidate(uint64, *CandidateInfo) error + GetActivatedCandidate(uint64) (*CandidateInfo, error) + SetAvailableQuantity(uint64, string, *big.Int) error GetAvailableQuantity(uint64, string) (*big.Int, error) @@ -50,6 +50,9 @@ type IDB interface { SetLastestEpoch(uint64) error GetLastestEpoch() (uint64, error) + SetTakeOver(uint64) error + GetTakeOver() (uint64, error) + Undelegate(string, *big.Int) (*types.Action, error) IncAsset2Acct(string, string, *big.Int) (*types.Action, error) GetBalanceByTime(name string, timestamp uint64) (*big.Int, error) @@ -162,36 +165,71 @@ func (voter *VoterInfo) key() string { return fmt.Sprintf("0x%x_%s_%s", voter.Epoch, voter.Name, voter.Candidate) } +var ( + InvalidIndex = uint64(math.MaxUint64) +) + // GlobalState dpos state type GlobalState struct { - Epoch uint64 `json:"epoch"` // epoch - PreEpoch uint64 `json:"preEpoch"` // epoch - ActivatedCandidateSchedule []string `json:"activatedCandidateSchedule"` // candidates - ActivatedTotalQuantity *big.Int `json:"activatedTotalQuantity"` // the sum of activate candidate votes - OffCandidateSchedule []uint64 `json:"offCandidateSchedule"` // activated backup candidates - OffCandidateNumber []uint64 `json:"offCandidateNumber"` // activated backup candidates - TotalQuantity *big.Int `json:"totalQuantity"` // the sum of all candidate votes - TakeOver bool `json:"takeOver"` // systemio take over dpos - Dpos bool `json:"dpos"` // dpos status - Number uint64 `json:"number"` // timestamp + Epoch uint64 `json:"epoch"` // epoch + PreEpoch uint64 `json:"preEpoch"` // epoch + ActivatedCandidateSchedule []string `json:"activatedCandidateSchedule"` // candidates + ActivatedTotalQuantity *big.Int `json:"activatedTotalQuantity"` // the sum of activate candidate votes + BadCandidateIndexSchedule []uint64 `json:"badCandidateIndexSchedule"` // activated backup candidates + UsingCandidateIndexSchedule []uint64 `json:"usingCandidateIndexSchedule"` // activated backup candidates + TotalQuantity *big.Int `json:"totalQuantity"` // the sum of all candidate votes + TakeOver bool `json:"takeOver"` // systemio take over dpos + Dpos bool `json:"dpos"` // dpos status + Number uint64 `json:"number"` // timestamp +} + +// ArrayCandidateInfoForBrowser dpos state +type ArrayCandidateInfoForBrowser struct { + Data []*CandidateInfoForBrowser `json:"data"` +} + +// CandidateInfoForBrowser dpos state +type CandidateInfoForBrowser struct { + Candidate string `json:"candidate"` + Holder string `json:"holder"` + Quantity string `json:"quantity"` + TotalQuantity string `json:"totalQuantity"` + Counter uint64 `json:"shouldCounter"` + ActualCounter uint64 `json:"actualCounter"` + NowCounter uint64 `json:"nowShouldCounter"` + NowActualCounter uint64 `json:"nowActualCounter"` + Status uint64 `json:"status"` //0:die 1:activate 2:spare } // CandidateInfoArray array of candidate type CandidateInfoArray []*CandidateInfo +type Epochs struct { + Data []*Epoch `json:"data"` +} + +type Epoch struct { + Start uint64 `json:"start"` + Epoch uint64 `json:"epoch"` +} + func (prods CandidateInfoArray) Len() int { return len(prods) } func (prods CandidateInfoArray) Less(i, j int) bool { - val := prods[i].TotalQuantity.Cmp(prods[j].TotalQuantity) + return more(prods[i], prods[j]) +} +func (prods CandidateInfoArray) Swap(i, j int) { + prods[i], prods[j] = prods[j], prods[i] +} + +func more(frist *CandidateInfo, second *CandidateInfo) bool { + val := frist.TotalQuantity.Cmp(second.TotalQuantity) if val == 0 { - if prods[i].Number == prods[j].Number { - return strings.Compare(prods[i].Name, prods[j].Name) > 0 + if frist.Number == second.Number { + return strings.Compare(frist.Name, second.Name) > 0 } - return prods[i].Number < prods[j].Number + return frist.Number < second.Number } return val > 0 } -func (prods CandidateInfoArray) Swap(i, j int) { - prods[i], prods[j] = prods[j], prods[i] -} diff --git a/consensus/dpos/dpos.go b/consensus/dpos/dpos.go index e61180c6..bb16fd25 100644 --- a/consensus/dpos/dpos.go +++ b/consensus/dpos/dpos.go @@ -19,6 +19,7 @@ package dpos import ( "errors" "fmt" + "math" "math/big" "sort" "strings" @@ -30,6 +31,7 @@ import ( "github.com/fractalplatform/fractal/common" "github.com/fractalplatform/fractal/consensus" "github.com/fractalplatform/fractal/crypto" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/snapshot" "github.com/fractalplatform/fractal/state" "github.com/fractalplatform/fractal/types" @@ -116,13 +118,13 @@ func Genesis(cfg *Config, state *state.StateDB, timestamp uint64, number uint64) return err } if err := sys.SetState(&GlobalState{ - Epoch: epoch, - PreEpoch: epoch, - ActivatedTotalQuantity: big.NewInt(0), - TotalQuantity: big.NewInt(0), - OffCandidateNumber: []uint64{}, - OffCandidateSchedule: []uint64{}, - Number: number, + Epoch: epoch, + PreEpoch: epoch, + ActivatedTotalQuantity: big.NewInt(0), + TotalQuantity: big.NewInt(0), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + Number: number, }); err != nil { return err } @@ -191,17 +193,25 @@ func (dpos *Dpos) Author(header *types.Header) (common.Name, error) { // Prepare initializes the consensus fields of a block header according to the rules of a particular engine. The changes are executed inline. func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) error { + if fid := header.CurForkID(); fid >= params.ForkID2 { + return dpos.prepare1(chain, header, txs, receipts, state) + } + return dpos.prepare0(chain, header, txs, receipts, state) +} + +func (dpos *Dpos) prepare0(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) error { header.Extra = append(header.Extra, make([]byte, extraSeal)...) sys := NewSystem(state, dpos.config) parent := chain.GetHeaderByHash(header.ParentHash) pepoch := dpos.config.epoch(parent.Time.Uint64()) epoch := dpos.config.epoch(header.Time.Uint64()) if header.Number.Uint64() != 1 { - gstate, err := sys.GetState(LastEpoch) + gstate, err := sys.GetState(pepoch) if err != nil { return err } - if dpos.CalcProposedIrreversible(chain, parent, true) == 0 || header.Time.Uint64()-parent.Time.Uint64() > 2*dpos.config.mepochInterval() { + if header.Time.Uint64()-parent.Time.Uint64() > 2*dpos.config.mepochInterval() || + dpos.CalcProposedIrreversible(chain, parent, true) == 0 { if systemio := strings.Compare(header.Coinbase.String(), dpos.config.SystemName) == 0; systemio { gstate.TakeOver = true if err := sys.SetState(gstate); err != nil { @@ -220,16 +230,16 @@ func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, tx if header.Time.Uint64() < etimestamp { etimestamp = header.Time.Uint64() } - poffset := dpos.config.getoffset(parent.Time.Uint64()) + poffset := dpos.config.getoffset(parent.Time.Uint64(), params.ForkID0) for ; timestamp < etimestamp; timestamp += dpos.config.blockInterval() { - coffset := dpos.config.getoffset(timestamp) + coffset := dpos.config.getoffset(timestamp, params.ForkID0) if coffset != poffset { if coffset >= uint64(len(pstate.ActivatedCandidateSchedule)) { continue } name := pstate.ActivatedCandidateSchedule[coffset] - for rindex := len(pstate.OffCandidateSchedule); rindex > 0; rindex-- { - roffset := pstate.OffCandidateSchedule[uint64(rindex-1)] + for rindex := len(pstate.BadCandidateIndexSchedule); rindex > 0; rindex-- { + roffset := pstate.BadCandidateIndexSchedule[uint64(rindex-1)] if roffset == coffset { name = pstate.ActivatedCandidateSchedule[dpos.config.CandidateScheduleSize+uint64(rindex-1)] break @@ -258,7 +268,7 @@ func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, tx candidate.ActualCounter++ if gstate.TakeOver { candidate.Counter++ - } else if /*dpos.config.getoffset(parent.Time.Uint64())*/ dpos.config.getoffset(header.Time.Uint64()-dpos.config.blockInterval()) != dpos.config.getoffset(header.Time.Uint64()) || + } else if /*dpos.config.getoffset(parent.Time.Uint64())*/ dpos.config.getoffset(header.Time.Uint64()-dpos.config.blockInterval(), params.ForkID0) != dpos.config.getoffset(header.Time.Uint64(), params.ForkID0) || strings.Compare(parent.Coinbase.String(), header.Coinbase.String()) != 0 { etimestamp := sys.config.epochTimeStamp(gstate.Epoch+1) + 2*sys.config.blockInterval() c := dpos.config.shouldCounter(header.Time.Uint64(), etimestamp) @@ -273,9 +283,9 @@ func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, tx if pepoch != epoch { log.Debug("UpdateElectedCandidates", "prev", pepoch, "curr", epoch, "number", parent.Number.Uint64(), "time", parent.Time.Uint64()) - sys.UpdateElectedCandidates(pepoch, epoch, parent.Number.Uint64(), header.Coinbase.String()) + sys.UpdateElectedCandidates0(pepoch, epoch, parent.Number.Uint64(), header.Coinbase.String()) if timestamp := parent.Time.Uint64() + dpos.config.blockInterval(); parent.Number.Uint64() > 0 && timestamp < header.Time.Uint64() { - gstate, err := sys.GetState(LastEpoch) + gstate, err := sys.GetState(epoch) if err != nil { return err } @@ -288,15 +298,15 @@ func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, tx stimestamp = parent.Time.Uint64() } - poffset := dpos.config.getoffset(stimestamp) + poffset := dpos.config.getoffset(stimestamp, params.ForkID0) for stimestamp += dpos.config.blockInterval(); stimestamp < header.Time.Uint64(); stimestamp += dpos.config.blockInterval() { - coffset := dpos.config.getoffset(stimestamp) + coffset := dpos.config.getoffset(stimestamp, params.ForkID0) if coffset != poffset { if coffset >= uint64(len(pstate.ActivatedCandidateSchedule)) { continue } name := pstate.ActivatedCandidateSchedule[coffset] - for index, roffset := range pstate.OffCandidateSchedule { + for index, roffset := range pstate.BadCandidateIndexSchedule { if roffset == coffset { name = pstate.ActivatedCandidateSchedule[dpos.config.CandidateScheduleSize+uint64(index)] break @@ -336,12 +346,177 @@ func (dpos *Dpos) Prepare(chain consensus.IChainReader, header *types.Header, tx return nil } +func (dpos *Dpos) prepare1(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) error { + header.Extra = append(header.Extra, make([]byte, extraSeal)...) + + sys := NewSystem(state, dpos.config) + parent := chain.GetHeaderByHash(header.ParentHash) + pepoch := dpos.config.epoch(parent.Time.Uint64()) + epoch := dpos.config.epoch(header.Time.Uint64()) + + gstate, err := sys.GetState(pepoch) + if err != nil { + return err + } + if header.Number.Uint64() == 1 || gstate.TakeOver { + sys.UpdateElectedCandidates1(pepoch, epoch, header.Number.Uint64(), header.Coinbase.String()) + if candidate, err := sys.GetCandidate(epoch, header.Coinbase.String()); err != nil { + return err + } else if candidate != nil { + candidate.ActualCounter++ + candidate.Counter++ + if err := sys.SetCandidate(candidate); err != nil { + return err + } + } + return nil + } + + systemio := strings.Compare(header.Coinbase.String(), dpos.config.SystemName) == 0 + takeover := (header.Time.Uint64()-parent.Time.Uint64() > 2*dpos.config.mepochInterval() || dpos.CalcProposedIrreversible(chain, parent, true) == 0) && systemio + if takeover { + sys.UpdateElectedCandidates1(pepoch, epoch, header.Number.Uint64(), header.Coinbase.String()) + gstate, err := sys.GetState(epoch) + if err != nil { + return err + } + gstate.TakeOver = takeover + if err := sys.SetState(gstate); err != nil { + return err + } + if err := sys.SetTakeOver(gstate.Epoch); err != nil { + return err + } + + if candidate, err := sys.GetCandidate(epoch, header.Coinbase.String()); err != nil { + return err + } else if candidate != nil { + candidate.ActualCounter++ + candidate.Counter++ + if err := sys.SetCandidate(candidate); err != nil { + return err + } + } + return nil + } + + pstate, err := sys.GetState(gstate.PreEpoch) + if err != nil { + return err + } + candidates := map[uint64]*CandidateInfo{} + mepoch := (parent.Time.Uint64() - dpos.config.epochTimeStamp(pepoch)) / dpos.config.mepochInterval() / dpos.config.minMEpoch() + for timestamp := parent.Time.Uint64() + sys.config.blockInterval(); timestamp <= header.Time.Uint64(); timestamp += sys.config.blockInterval() { + tepoch := dpos.config.epoch(timestamp) + tmepoch := (timestamp - dpos.config.epochTimeStamp(tepoch)) / dpos.config.mepochInterval() / dpos.config.minMEpoch() + if mepoch != tmepoch { + for offset, cindex := range pstate.UsingCandidateIndexSchedule { + if uint64(cindex) == InvalidIndex { + continue + } + tcandidate, ok := candidates[uint64(offset)] + if !ok { + pcandidate, err := sys.GetCandidate(gstate.Epoch, pstate.ActivatedCandidateSchedule[cindex]) + if err != nil { + return err + } + tcandidate = pcandidate + } + ptcandidate, err := sys.GetActivatedCandidate(uint64(offset)) + if err != nil { + return err + } + if ptcandidate != nil && ptcandidate.Epoch == tepoch { + if strings.Compare(ptcandidate.Name, tcandidate.Name) != 0 { + panic(fmt.Sprintf("not reached %s != %s", tcandidate.Name, ptcandidate.Name)) + } + scnt := tcandidate.Counter - ptcandidate.Counter + acnt := tcandidate.ActualCounter - ptcandidate.ActualCounter + log.Debug("replace check", "num", header.Number.Uint64(), "epoch", gstate.Epoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "scnt", scnt, "acnt", acnt) + if scnt > acnt+scnt/2 { + if uint64(len(pstate.BadCandidateIndexSchedule))+dpos.config.CandidateScheduleSize < uint64(len(pstate.ActivatedCandidateSchedule)) { + rindex := uint64(len(pstate.BadCandidateIndexSchedule)) + dpos.config.CandidateScheduleSize + rname := pstate.ActivatedCandidateSchedule[rindex] + log.Info("replace checked", "num", header.Number.Uint64(), "epoch", gstate.Epoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "==>rcandidate", rname, "scnt", scnt, "acnt", acnt) + + pstate.BadCandidateIndexSchedule = append(pstate.BadCandidateIndexSchedule, uint64(offset)) + pstate.UsingCandidateIndexSchedule[uint64(offset)] = rindex + rcandidate, err := sys.GetCandidate(gstate.Epoch, rname) + if err != nil { + return err + } + tcandidate = rcandidate + } else { + log.Info("replace checked", "num", header.Number.Uint64(), "epoch", gstate.Epoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "==>rcandidate", "****", "scnt", scnt, "acnt", acnt) + pstate.UsingCandidateIndexSchedule[uint64(offset)] = InvalidIndex + } + } + } + log.Debug("replace start", "num", header.Number.Uint64(), "epoch", gstate.Epoch, "mepoch", mepoch, "index", offset, "candiate", tcandidate.Name, "counter", tcandidate.Counter, "actual", tcandidate.ActualCounter) + if err := sys.SetActivatedCandidate(uint64(offset), tcandidate); err != nil { + return err + } + } + log.Debug("replace after", "num", header.Number.Uint64(), "epoch", gstate.Epoch, "mepoch", mepoch, "usingCandidateIndexSchedule", fmt.Sprintf("%v", gstate.UsingCandidateIndexSchedule)) + if err := sys.SetState(pstate); err != nil { + return err + } + mepoch = tmepoch + } + + if gstate.Epoch != tepoch { + for _, candidate := range candidates { + if err := sys.SetCandidate(candidate); err != nil { + return err + } + } + + candidates = map[uint64]*CandidateInfo{} + sys.UpdateElectedCandidates1(gstate.Epoch, tepoch, header.Number.Uint64(), header.Coinbase.String()) + gstate, _ = sys.GetState(tepoch) + pstate, _ = sys.GetState(gstate.PreEpoch) + } + coffset := dpos.config.getoffset(timestamp, params.ForkID2) + name := sys.usingCandiate(pstate, coffset) + if name == "" { + continue + } + candidate, ok := candidates[coffset] + if !ok { + pcandidate, err := sys.GetCandidate(gstate.Epoch, name) + if err != nil { + return err + } + candidates[coffset] = pcandidate + candidate = pcandidate + } + candidate.Counter++ + } + candidates[sys.config.getoffset(header.Time.Uint64(), params.ForkID2)].ActualCounter++ + for _, candidate := range candidates { + if err := sys.SetCandidate(candidate); err != nil { + return err + } + } + if len(gstate.ActivatedCandidateSchedule) == 0 { + sys.UpdateElectedCandidates1(pepoch, epoch, header.Number.Uint64(), header.Coinbase.String()) + } + return nil +} + // Finalize assembles the final block. func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) (*types.Block, error) { if chain == nil { header.Root = state.IntermediateRoot() return types.NewBlock(header, txs, receipts), nil } + if fid := header.CurForkID(); fid >= params.ForkID2 { + return dpos.finalize1(chain, header, txs, receipts, state) + } + return dpos.finalize0(chain, header, txs, receipts, state) +} + +func (dpos *Dpos) finalize0(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) (*types.Block, error) { sys := NewSystem(state, dpos.config) counter := int64(0) extraReward := new(big.Int).Mul(dpos.config.extraBlockReward(), big.NewInt(counter)) @@ -373,7 +548,7 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t } } - gstate, err := sys.GetState(LastEpoch) + gstate, err := sys.GetState(dpos.config.epoch(header.Time.Uint64())) if err != nil { return nil, err } @@ -401,12 +576,12 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t info.ActualCounter++ pheader := chain.GetHeaderByHash(theader.ParentHash) - coffset := dpos.config.getoffset(theader.Time.Uint64()) - poffset := dpos.config.getoffset(pheader.Time.Uint64()) + coffset := dpos.config.getoffset(theader.Time.Uint64(), params.ForkID0) + poffset := dpos.config.getoffset(pheader.Time.Uint64(), params.ForkID0) exit := pheader.Time.Uint64() < timestamp || (pheader.Time.Uint64()-timestamp)/dpos.config.mepochInterval() < mepoch-dpos.config.minMEpoch() if ftimestamp := pheader.Time.Uint64() + dpos.config.blockInterval(); ftimestamp < theader.Time.Uint64() && poffset != coffset { tpoffset := poffset - toffset := dpos.config.getoffset(ftimestamp) + toffset := dpos.config.getoffset(ftimestamp, params.ForkID0) for { if toffset == coffset { break @@ -417,8 +592,8 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t continue } tname := pstate.ActivatedCandidateSchedule[toffset] - for rindex := len(pstate.OffCandidateSchedule); rindex > 0; rindex-- { - roffset := pstate.OffCandidateSchedule[uint64(rindex-1)] + for rindex := len(pstate.BadCandidateIndexSchedule); rindex > 0; rindex-- { + roffset := pstate.BadCandidateIndexSchedule[uint64(rindex-1)] if roffset == toffset { tname = pstate.ActivatedCandidateSchedule[dpos.config.CandidateScheduleSize+uint64(rindex-1)] break @@ -435,7 +610,7 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t tpoffset = toffset } ftimestamp += dpos.config.blockInterval() - toffset = dpos.config.getoffset(ftimestamp) + toffset = dpos.config.getoffset(ftimestamp, params.ForkID0) } info.Counter += dpos.config.shouldCounter(ftimestamp, theader.Time.Uint64()) } @@ -455,8 +630,8 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t if uint64(index) >= dpos.config.CandidateScheduleSize { break } - for rindex := len(pstate.OffCandidateSchedule); rindex > 0; rindex-- { - roffset := pstate.OffCandidateSchedule[uint64(rindex-1)] + for rindex := len(pstate.BadCandidateIndexSchedule); rindex > 0; rindex-- { + roffset := pstate.BadCandidateIndexSchedule[uint64(rindex-1)] if roffset == uint64(index) { tname = pstate.ActivatedCandidateSchedule[dpos.config.CandidateScheduleSize+uint64(rindex-1)] break @@ -474,9 +649,9 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t continue } log.Debug("replace check", "num", header.Number, "mepoch", mepoch, "candidate", tname, "scnt", scnt, "acnt", acnt) - if scnt-acnt >= scnt/2 && uint64(len(pstate.OffCandidateSchedule))+dpos.config.CandidateScheduleSize < uint64(len(pstate.ActivatedCandidateSchedule)) { - pstate.OffCandidateSchedule = append(pstate.OffCandidateSchedule, uint64(index)) - log.Info("replace index", "num", header.Number, "mepoch", mepoch, "candidate", tname, "scnt", scnt, "acnt", acnt, "rcandidate", pstate.ActivatedCandidateSchedule[uint64(len(pstate.OffCandidateSchedule)-1)+dpos.config.CandidateScheduleSize]) + if scnt-acnt >= scnt/2 && uint64(len(pstate.BadCandidateIndexSchedule))+dpos.config.CandidateScheduleSize < uint64(len(pstate.ActivatedCandidateSchedule)) { + pstate.BadCandidateIndexSchedule = append(pstate.BadCandidateIndexSchedule, uint64(index)) + log.Info("replace index", "num", header.Number, "mepoch", mepoch, "candidate", tname, "scnt", scnt, "acnt", acnt, "rcandidate", pstate.ActivatedCandidateSchedule[uint64(len(pstate.BadCandidateIndexSchedule)-1)+dpos.config.CandidateScheduleSize]) } } @@ -496,6 +671,46 @@ func (dpos *Dpos) Finalize(chain consensus.IChainReader, header *types.Header, t return blk, nil } +func (dpos *Dpos) finalize1(chain consensus.IChainReader, header *types.Header, txs []*types.Transaction, receipts []*types.Receipt, state *state.StateDB) (*types.Block, error) { + parent := chain.GetHeaderByHash(header.ParentHash) + sys := NewSystem(state, dpos.config) + + // reward + extraCounter := int64(0) + extraReward := new(big.Int).Mul(dpos.config.extraBlockReward(), big.NewInt(extraCounter)) + reward := new(big.Int).Add(dpos.config.blockReward(), extraReward) + sys.IncAsset2Acct(dpos.config.SystemName, header.Coinbase.String(), reward) + + blk := types.NewBlock(header, txs, receipts) + // first hard fork at a specific number + // If the block number is greater than or equal to the hard forking number, + // the fork function will take effect. This function is valid only in the test network. + if err := chain.ForkUpdate(blk, state); err != nil { + return nil, err + } + + //snapshot + snapshotInterval := chain.Config().SnapshotInterval * uint64(time.Millisecond) + parentTimeFormat := parent.Time.Uint64() / snapshotInterval * snapshotInterval + currentTimeFormat := header.Time.Uint64() / snapshotInterval * snapshotInterval + if parentTimeFormat != currentTimeFormat { + snapshotManager := snapshot.NewSnapshotManager(state) + if err := snapshotManager.SetSnapshot(currentTimeFormat, snapshot.BlockInfo{Number: header.Number.Uint64(), BlockHash: blk.ParentHash(), Timestamp: parentTimeFormat}); err != nil { + return nil, err + } + } + + // bftIrreversibles + if strings.Compare(header.Coinbase.String(), dpos.config.SystemName) == 0 { + dpos.bftIrreversibles.Purge() + } + dpos.bftIrreversibles.Add(header.Coinbase, header.ProposedIrreversible) + + // update state root at the end + blk.Head.Root = state.IntermediateRoot() + return blk, nil +} + // Seal generates a new block for the given input block with the local miner's seal place on top. func (dpos *Dpos) Seal(chain consensus.IChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) { header := block.Header() @@ -539,7 +754,7 @@ func (dpos *Dpos) VerifySeal(chain consensus.IChainReader, header *types.Header) return err } - if err := dpos.IsValidateCandidate(chain, parent, header.Time.Uint64(), proudcer, [][]byte{pubkey}, state, true); err != nil { + if err := dpos.IsValidateCandidate(chain, parent, header.Time.Uint64(), proudcer, [][]byte{pubkey}, state, true, header.CurForkID()); err != nil { return err } @@ -559,7 +774,7 @@ func (dpos *Dpos) CalcDifficulty(chain consensus.IChainReader, time uint64, pare } //IsValidateCandidate current candidate -func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *types.Header, timestamp uint64, candidate string, pubkeys [][]byte, state *state.StateDB, force bool) error { +func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *types.Header, timestamp uint64, candidate string, pubkeys [][]byte, state *state.StateDB, force bool, fid uint64) error { if timestamp%dpos.BlockInterval() != 0 { return errInvalidMintBlockTime } @@ -579,7 +794,8 @@ func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *type } sys := NewSystem(state, dpos.config) - gstate, err := sys.GetState(LastEpoch) + pepoch := dpos.config.epoch(parent.Time.Uint64()) + gstate, err := sys.GetState(pepoch) if err != nil { return err } @@ -604,16 +820,184 @@ func (dpos *Dpos) IsValidateCandidate(chain consensus.IChainReader, parent *type if err != nil { return err } - offset := dpos.config.getoffset(timestamp) - for index := len(pstate.OffCandidateSchedule); index > 0; index-- { - roffset := pstate.OffCandidateSchedule[uint64(index-1)] - if roffset == offset { - offset = dpos.config.CandidateScheduleSize + uint64(index-1) - break + + tname := "" + offset := dpos.config.getoffset(timestamp, fid) + if fid >= params.ForkID2 { + if sys.config.epoch(timestamp) != sys.config.epoch(parent.Time.Uint64()) { + pstate = gstate + } + if len(pstate.ActivatedCandidateSchedule) == 0 { + n := sys.config.BackupScheduleSize + sys.config.CandidateScheduleSize + candidateInfoArray, err := sys.GetCandidates(pstate.Epoch) + if err != nil { + return err + } + activatedCandidateSchedule := []string{} + activatedTotalQuantity := big.NewInt(0) + sort.Sort(candidateInfoArray) + if pstate.Dpos { + for _, candidateInfo := range candidateInfoArray { + if !candidateInfo.invalid() { + if candidateInfo.Quantity.Sign() == 0 || strings.Compare(candidateInfo.Name, sys.config.SystemName) == 0 { + continue + } + if uint64(len(activatedCandidateSchedule)) >= n { + break + } + activatedCandidateSchedule = append(activatedCandidateSchedule, candidateInfo.Name) + activatedTotalQuantity = new(big.Int).Add(activatedTotalQuantity, candidateInfo.TotalQuantity) + } + } + } else { + tstate := &GlobalState{ + Epoch: math.MaxUint64, + PreEpoch: math.MaxUint64, + ActivatedTotalQuantity: big.NewInt(0), + TotalQuantity: big.NewInt(0), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + Number: 0, + } + for _, candidateInfo := range candidateInfoArray { + if !candidateInfo.invalid() { + if candidateInfo.Quantity.Sign() != 0 && strings.Compare(candidateInfo.Name, sys.config.SystemName) != 0 { + tstate.Number++ + tstate.TotalQuantity = new(big.Int).Add(tstate.TotalQuantity, candidateInfo.TotalQuantity) + if uint64(len(tstate.ActivatedCandidateSchedule)) < n { + tstate.ActivatedCandidateSchedule = append(tstate.ActivatedCandidateSchedule, candidateInfo.Name) + tstate.ActivatedTotalQuantity = new(big.Int).Add(tstate.ActivatedTotalQuantity, candidateInfo.TotalQuantity) + } + continue + } + if uint64(len(activatedCandidateSchedule)) < n { + activatedCandidateSchedule = append(activatedCandidateSchedule, candidateInfo.Name) + activatedTotalQuantity = new(big.Int).Add(activatedTotalQuantity, candidateInfo.TotalQuantity) + } + } + } + + if tstate.TotalQuantity.Cmp(sys.config.ActivatedMinQuantity) >= 0 && + tstate.Number >= n && + tstate.Number >= sys.config.ActivatedMinCandidate { + pstate.Dpos = true + pstate.ActivatedTotalQuantity = tstate.ActivatedTotalQuantity + pstate.ActivatedCandidateSchedule = tstate.ActivatedCandidateSchedule + } else { + if init := len(activatedCandidateSchedule); init > 0 { + index := 0 + for uint64(len(activatedCandidateSchedule)) < sys.config.CandidateScheduleSize { + activatedCandidateSchedule = append(activatedCandidateSchedule, activatedCandidateSchedule[index%init]) + index++ + } + } + } + } + pstate.ActivatedCandidateSchedule = activatedCandidateSchedule + pstate.ActivatedTotalQuantity = activatedTotalQuantity + usingCandidateIndexSchedule := []uint64{} + for index := range pstate.ActivatedCandidateSchedule { + if uint64(index) >= sys.config.CandidateScheduleSize { + break + } + usingCandidateIndexSchedule = append(usingCandidateIndexSchedule, uint64(index)) + } + pstate.UsingCandidateIndexSchedule = usingCandidateIndexSchedule + } + if sys.config.epoch(timestamp) == pepoch { + candidates := map[uint64]*CandidateInfo{} + mcandidates := map[uint64]*CandidateInfo{} + mepoch := (parent.Time.Uint64() - sys.config.epochTimeStamp(pepoch)) / sys.config.mepochInterval() / sys.config.minMEpoch() + for ttimestamp := parent.Time.Uint64() + sys.config.blockInterval(); ttimestamp < timestamp; ttimestamp += sys.config.blockInterval() { + tmepoch := (ttimestamp - dpos.config.epochTimeStamp(pepoch)) / dpos.config.mepochInterval() / dpos.config.minMEpoch() + if mepoch != tmepoch { + for offset, cindex := range pstate.UsingCandidateIndexSchedule { + if uint64(cindex) == InvalidIndex { + continue + } + tcandidate, ok := candidates[uint64(offset)] + if !ok { + pcandidate, err := sys.GetCandidate(gstate.Epoch, pstate.ActivatedCandidateSchedule[cindex]) + if err != nil { + return err + } + tcandidate = pcandidate + } + ptcandidate, ok := mcandidates[uint64(offset)] + if !ok { + pcandidate, err := sys.GetActivatedCandidate(uint64(offset)) + if err != nil { + return err + } + ptcandidate = pcandidate + } + + if ptcandidate != nil && ptcandidate.Epoch == pepoch { + if strings.Compare(ptcandidate.Name, tcandidate.Name) != 0 { + panic(fmt.Sprintf("not reached %s != %s", tcandidate.Name, ptcandidate.Name)) + } + scnt := tcandidate.Counter - ptcandidate.Counter + acnt := tcandidate.ActualCounter - ptcandidate.ActualCounter + log.Debug("replace check", "num", parent.Number.Uint64()+1, "epoch", pepoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "scnt", scnt, "acnt", acnt) + if scnt > acnt+scnt/2 { + if uint64(len(pstate.BadCandidateIndexSchedule))+dpos.config.CandidateScheduleSize < uint64(len(pstate.ActivatedCandidateSchedule)) { + rindex := uint64(len(pstate.BadCandidateIndexSchedule)) + dpos.config.CandidateScheduleSize + rname := pstate.ActivatedCandidateSchedule[rindex] + log.Info("replace checked", "num", parent.Number.Uint64()+1, "epoch", pepoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "==>rcandidate", rname, "scnt", scnt, "acnt", acnt) + + pstate.BadCandidateIndexSchedule = append(pstate.BadCandidateIndexSchedule, uint64(offset)) + pstate.UsingCandidateIndexSchedule[uint64(offset)] = rindex + rcandidate, err := sys.GetCandidate(gstate.Epoch, rname) + if err != nil { + return err + } + tcandidate = rcandidate + } else { + log.Info("replace checked", "num", parent.Number.Uint64()+1, "epoch", pepoch, "mepoch", mepoch, "index", offset, "candidate", tcandidate.Name, "==>rcandidate", "****", "scnt", scnt, "acnt", acnt) + pstate.UsingCandidateIndexSchedule[uint64(offset)] = InvalidIndex + } + } + } + log.Debug("replace start", "num", parent.Number.Uint64()+1, "epoch", pepoch, "mepoch", mepoch, "index", offset, "candiate", tcandidate.Name, "counter", tcandidate.Counter, "actual", tcandidate.ActualCounter) + mcandidates[uint64(offset)] = tcandidate + } + log.Debug("replace after", "num", parent.Number.Uint64()+1, "epoch", pepoch, "mepoch", mepoch, "usingCandidateIndexSchedule", fmt.Sprintf("%v", gstate.UsingCandidateIndexSchedule)) + mepoch = tmepoch + } + + coffset := dpos.config.getoffset(timestamp, params.ForkID2) + name := sys.usingCandiate(pstate, coffset) + if name == "" { + continue + } + candidate, ok := candidates[coffset] + if !ok { + pcandidate, err := sys.GetCandidate(gstate.Epoch, name) + if err != nil { + return err + } + candidates[coffset] = pcandidate + candidate = pcandidate + } + candidate.Counter++ + } + } + tname = sys.usingCandiate(pstate, offset) + } else { + if offset < uint64(len(pstate.ActivatedCandidateSchedule)) { + tname = pstate.ActivatedCandidateSchedule[offset] + for rindex := len(pstate.BadCandidateIndexSchedule); rindex > 0; rindex-- { + roffset := pstate.BadCandidateIndexSchedule[uint64(rindex-1)] + if roffset == uint64(offset) { + tname = pstate.ActivatedCandidateSchedule[dpos.config.CandidateScheduleSize+uint64(rindex-1)] + break + } + } } } - if pstate == nil || offset >= uint64(len(pstate.ActivatedCandidateSchedule)) || strings.Compare(pstate.ActivatedCandidateSchedule[offset], candidate) != 0 { - return fmt.Errorf("%v %v, except %v index %v (%v) ", errInvalidBlockCandidate, candidate, pstate.ActivatedCandidateSchedule, offset, pstate.Epoch) + + if strings.Compare(tname, candidate) != 0 { + return fmt.Errorf("%v %v, except %v(%v) index %v (%v epoch) ", errInvalidBlockCandidate, candidate, pstate.ActivatedCandidateSchedule, pstate.UsingCandidateIndexSchedule, offset, pstate.Epoch) } return nil } @@ -745,29 +1129,29 @@ func (dpos *Dpos) GetActivedCandidateSize(state *state.StateDB, epoch uint64) (u } // GetActivedCandidate get actived candidate info -func (dpos *Dpos) GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (string, *big.Int, *big.Int, uint64, uint64, uint64, error) { +func (dpos *Dpos) GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (string, *big.Int, *big.Int, uint64, uint64, uint64, bool, error) { sys := NewSystem(state, dpos.config) gstate, err := sys.GetState(epoch) if err != nil { - return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, err + return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, false, err } pstate, err := sys.GetState(gstate.PreEpoch) if err != nil { - return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, err + return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, false, err } if index >= uint64(len(pstate.ActivatedCandidateSchedule)) { - return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, fmt.Errorf("out of index") + return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, false, fmt.Errorf("out of index") } candidate := pstate.ActivatedCandidateSchedule[index] prevCandidateInfo, err := sys.GetCandidate(gstate.PreEpoch, candidate) if err != nil { - return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, err + return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, false, err } candidateInfo, err := sys.GetCandidate(gstate.Epoch, candidate) if err != nil { - return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, err + return "", big.NewInt(0), big.NewInt(0), 0, 0, 0, false, err } if prevCandidateInfo == nil { @@ -787,11 +1171,18 @@ func (dpos *Dpos) GetActivedCandidate(state *state.StateDB, epoch uint64, index counter = actualCounter } rindex := uint64(0) - if s := uint64(len(pstate.OffCandidateSchedule)); index >= dpos.config.CandidateScheduleSize && index-dpos.config.CandidateScheduleSize < s { - rindex = pstate.OffCandidateSchedule[index-dpos.config.CandidateScheduleSize] + 1 + if s := uint64(len(pstate.BadCandidateIndexSchedule)); index >= dpos.config.CandidateScheduleSize && index-dpos.config.CandidateScheduleSize < s { + rindex = pstate.BadCandidateIndexSchedule[index-dpos.config.CandidateScheduleSize] + 1 } - return candidate, new(big.Int).Mul(prevCandidateInfo.Quantity, sys.config.unitStake()), new(big.Int).Mul(prevCandidateInfo.TotalQuantity, sys.config.unitStake()), counter, actualCounter, rindex, err + isbad := false + if index < dpos.config.CandidateScheduleSize { + isbad = pstate.UsingCandidateIndexSchedule[index] != index + } else if rindex != 0 { + isbad = pstate.UsingCandidateIndexSchedule[rindex-1] != index + } + + return candidate, new(big.Int).Mul(prevCandidateInfo.Quantity, sys.config.unitStake()), new(big.Int).Mul(prevCandidateInfo.TotalQuantity, sys.config.unitStake()), counter, actualCounter, rindex, isbad, err } // GetCandidateStake candidate delegate stake @@ -859,12 +1250,12 @@ func (dpos *Dpos) CalcProposedIrreversible(chain consensus.IChainReader, parent candidateMap := make(map[string]uint64) timestamp := curHeader.Time.Uint64() for curHeader.Number.Uint64() > 0 { - if strings.Compare(curHeader.Coinbase.String(), dpos.config.SystemName) == 0 { - return curHeader.Number.Uint64() - } if strict && timestamp-curHeader.Time.Uint64() >= 2*dpos.config.mepochInterval() { break } + if strings.Compare(curHeader.Coinbase.String(), dpos.config.SystemName) == 0 { + return curHeader.Number.Uint64() + } candidateMap[curHeader.Coinbase.String()]++ if uint64(len(candidateMap)) >= dpos.config.consensusSize() { return curHeader.Number.Uint64() diff --git a/consensus/dpos/ldb.go b/consensus/dpos/ldb.go index a847ea70..da30689e 100644 --- a/consensus/dpos/ldb.go +++ b/consensus/dpos/ldb.go @@ -21,7 +21,6 @@ import ( "encoding/hex" "fmt" "math/big" - "sort" "strings" "github.com/fractalplatform/fractal/types" @@ -46,12 +45,17 @@ var ( CandidateKeyPrefix = "p" // CandidateHead all candidate key CandidateHead = "s" + // ActivatedCandidateKeyPrefix candidateInfo + ActivatedCandidateKeyPrefix = "ap" // VoterKeyPrefix voterInfo VoterKeyPrefix = "v" // VoterHead head VoterHead = "v" + // TakeOver key + TakeOver = "takeover" + // StateKeyPrefix globalState StateKeyPrefix = "s" // LastestStateKey lastest @@ -174,7 +178,7 @@ func (db *LDB) GetCandidate(epoch uint64, name string) (*CandidateInfo, error) { } // GetCandidates get all candidate info & sort -func (db *LDB) GetCandidates(epoch uint64) ([]*CandidateInfo, error) { +func (db *LDB) GetCandidates(epoch uint64) (CandidateInfoArray, error) { // candidates head, err := db.GetCandidate(epoch, CandidateHead) if err != nil { @@ -194,7 +198,7 @@ func (db *LDB) GetCandidates(epoch uint64) ([]*CandidateInfo, error) { nextKey = candidateInfo.NextKey candidateInfos = append(candidateInfos, candidateInfo) } - sort.Sort(candidateInfos) + // sort.Sort(candidateInfos) return candidateInfos, nil } @@ -210,6 +214,31 @@ func (db *LDB) CandidatesSize(epoch uint64) (uint64, error) { return head.Counter, nil } +// SetActivatedCandidate update activated candidate info +func (db *LDB) SetActivatedCandidate(index uint64, candidate *CandidateInfo) error { + key := strings.Join([]string{ActivatedCandidateKeyPrefix, hex.EncodeToString(uint64tobytes(index))}, Separator) + if val, err := rlp.EncodeToBytes(candidate); err != nil { + return err + } else if err := db.Put(key, val); err != nil { + return err + } + return nil +} + +// GetActivatedCandidate get activated candidate info +func (db *LDB) GetActivatedCandidate(index uint64) (*CandidateInfo, error) { + key := strings.Join([]string{ActivatedCandidateKeyPrefix, hex.EncodeToString(uint64tobytes(index))}, Separator) + candidateInfo := &CandidateInfo{} + if val, err := db.Get(key); err != nil { + return nil, err + } else if val == nil { + return nil, nil + } else if err := rlp.DecodeBytes(val, candidateInfo); err != nil { + return nil, err + } + return candidateInfo, nil +} + // SetAvailableQuantity set quantity func (db *LDB) SetAvailableQuantity(epoch uint64, voter string, quantity *big.Int) error { head, err := db.GetVoter(epoch, voter, VoterHead) @@ -363,6 +392,29 @@ func (db *LDB) GetVotersByVoter(epoch uint64, voter string) ([]*VoterInfo, error return voterInfos, nil } +// SetTakeOver update activated candidate info +func (db *LDB) SetTakeOver(epoch uint64) error { + if val, err := rlp.EncodeToBytes(epoch); err != nil { + return err + } else if err := db.Put(TakeOver, val); err != nil { + return err + } + return nil +} + +// GetTakeOver get activated candidate info +func (db *LDB) GetTakeOver() (uint64, error) { + epoch := uint64(0) + if val, err := db.Get(TakeOver); err != nil { + return epoch, err + } else if val == nil { + return epoch, nil + } else if err := rlp.DecodeBytes(val, &epoch); err != nil { + return epoch, err + } + return epoch, nil +} + // SetState set global state info func (db *LDB) SetState(gstate *GlobalState) error { key := strings.Join([]string{StateKeyPrefix, hex.EncodeToString(uint64tobytes(gstate.Epoch))}, Separator) @@ -376,14 +428,6 @@ func (db *LDB) SetState(gstate *GlobalState) error { // GetState get state info func (db *LDB) GetState(epoch uint64) (*GlobalState, error) { - if epoch == LastEpoch { - var err error - epoch, err = db.GetLastestEpoch() - if err != nil { - return nil, err - } - } - key := strings.Join([]string{StateKeyPrefix, hex.EncodeToString(uint64tobytes(epoch))}, Separator) gstate := &GlobalState{} if val, err := db.Get(key); err != nil { diff --git a/consensus/dpos/ldb_test.go b/consensus/dpos/ldb_test.go index 17928578..eb852d29 100644 --- a/consensus/dpos/ldb_test.go +++ b/consensus/dpos/ldb_test.go @@ -345,13 +345,13 @@ func TestLDBGlobalState(t *testing.T) { for index := range candidates { gstate := &GlobalState{ - Epoch: uint64(index + 1), - PreEpoch: uint64(index), - ActivatedTotalQuantity: big.NewInt(0), - ActivatedCandidateSchedule: candidates[index:], - OffCandidateNumber: []uint64{}, - OffCandidateSchedule: []uint64{}, - TotalQuantity: big.NewInt(0), + Epoch: uint64(index + 1), + PreEpoch: uint64(index), + ActivatedTotalQuantity: big.NewInt(0), + ActivatedCandidateSchedule: candidates[index:], + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + TotalQuantity: big.NewInt(0), } if err := db.SetState(gstate); err != nil { panic(fmt.Errorf("SetState --- %v", err)) @@ -372,13 +372,13 @@ func TestLDBGlobalState(t *testing.T) { for index := range candidates { gstate := &GlobalState{ - Epoch: uint64(index + 1), - PreEpoch: uint64(index), - ActivatedTotalQuantity: big.NewInt(0), - ActivatedCandidateSchedule: candidates[index:], - TotalQuantity: big.NewInt(0), - OffCandidateNumber: []uint64{}, - OffCandidateSchedule: []uint64{}, + Epoch: uint64(index + 1), + PreEpoch: uint64(index), + ActivatedTotalQuantity: big.NewInt(0), + ActivatedCandidateSchedule: candidates[index:], + TotalQuantity: big.NewInt(0), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, } if err := db.SetState(gstate); err != nil { panic(fmt.Errorf("Redo SetState --- %v", err)) diff --git a/consensus/dpos/processor.go b/consensus/dpos/processor.go index 883156c9..a59a70d0 100644 --- a/consensus/dpos/processor.go +++ b/consensus/dpos/processor.go @@ -50,17 +50,22 @@ type KickedCandidate struct { Candidates []string } +// RemoveKickedCandidate remove kicked info +type RemoveKickedCandidate struct { + Candidates []string +} + // ProcessAction exec action -func (dpos *Dpos) ProcessAction(number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) { +func (dpos *Dpos) ProcessAction(fid uint64, number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) { snap := state.Snapshot() - internalLogs, err := dpos.processAction(number, chainCfg, state, action) + internalLogs, err := dpos.processAction(fid, number, chainCfg, state, action) if err != nil { state.RevertToSnapshot(snap) } return internalLogs, err } -func (dpos *Dpos) processAction(number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) { +func (dpos *Dpos) processAction(fid uint64, number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) { if err := action.Check(chainCfg); err != nil { return nil, err } @@ -81,28 +86,41 @@ func (dpos *Dpos) processAction(number uint64, chainCfg *params.ChainConfig, sta } switch action.Type() { case types.RegCandidate: + if fid >= params.ForkID2 { + if val := new(big.Int).Mul(dpos.config.CandidateMinQuantity, dpos.config.unitStake()); action.Value().Cmp(val) != 0 { + return nil, fmt.Errorf("value must be %v", val) + } + } arg := &RegisterCandidate{} if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { return nil, err } - if err := sys.RegCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number); err != nil { + if err := sys.RegCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number, fid); err != nil { return nil, err } case types.UpdateCandidate: + if fid >= params.ForkID2 { + if action.Value().Sign() == 1 { + return nil, fmt.Errorf("value must be zero") + } + } arg := &UpdateCandidate{} if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { return nil, err } - if err := sys.UpdateCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number); err != nil { + if err := sys.UpdateCandidate(epoch, action.Sender().String(), arg.URL, action.Value(), number, fid); err != nil { return nil, err } case types.UnregCandidate: - err := sys.UnregCandidate(epoch, action.Sender().String(), number) + if strings.Compare(action.Sender().String(), dpos.config.SystemName) == 0 { + return nil, fmt.Errorf("no permission") + } + err := sys.UnregCandidate(epoch, action.Sender().String(), number, fid) if err != nil { return nil, err } case types.RefundCandidate: - err := sys.RefundCandidate(epoch, action.Sender().String(), number) + err := sys.RefundCandidate(epoch, action.Sender().String(), number, fid) if err != nil { return nil, err } @@ -111,11 +129,12 @@ func (dpos *Dpos) processAction(number uint64, chainCfg *params.ChainConfig, sta if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { return nil, err } - if err := sys.VoteCandidate(epoch, action.Sender().String(), arg.Candidate, arg.Stake, number); err != nil { + if err := sys.VoteCandidate(epoch, action.Sender().String(), arg.Candidate, arg.Stake, number, fid); err != nil { return nil, err } case types.KickedCandidate: - if strings.Compare(action.Sender().String(), dpos.config.SystemName) != 0 { + gstate, _ := sys.GetState(epoch) + if gstate.TakeOver == false || strings.Compare(action.Sender().String(), dpos.config.SystemName) != 0 { return nil, fmt.Errorf("no permission for kicking candidates") } arg := &KickedCandidate{} @@ -123,15 +142,36 @@ func (dpos *Dpos) processAction(number uint64, chainCfg *params.ChainConfig, sta return nil, err } for _, cadicate := range arg.Candidates { - if err := sys.KickedCandidate(epoch, cadicate, number); err != nil { + if strings.Compare(cadicate, dpos.config.SystemName) == 0 { + continue + } + if err := sys.KickedCandidate(epoch, cadicate, number, fid); err != nil { return nil, err } } - case types.ExitTakeOver: + case types.RemoveKickedCandidate: if strings.Compare(action.Sender().String(), dpos.config.SystemName) != 0 { + return nil, fmt.Errorf("no permission for removing candidates") + } + arg := &RemoveKickedCandidate{} + if err := rlp.DecodeBytes(action.Data(), &arg); err != nil { + return nil, err + } + for _, cadicate := range arg.Candidates { + if strings.Compare(cadicate, dpos.config.SystemName) == 0 { + continue + } + if err := sys.RemoveKickedCandidate(epoch, cadicate, number, fid); err != nil { + return nil, err + } + } + + case types.ExitTakeOver: + gstate, _ := sys.GetState(epoch) + if gstate.TakeOver == false || strings.Compare(action.Sender().String(), dpos.config.SystemName) != 0 { return nil, fmt.Errorf("no permission for exit take over") } - if err := sys.ExitTakeOver(epoch); err != nil { + if err := sys.ExitTakeOver(epoch, number, fid); err != nil { return nil, err } default: diff --git a/consensus/dpos/vote.go b/consensus/dpos/vote.go index 9e6f64a7..bb357c48 100644 --- a/consensus/dpos/vote.go +++ b/consensus/dpos/vote.go @@ -18,9 +18,15 @@ package dpos import ( "fmt" + "math" "math/big" + "sort" "strings" + "time" + "github.com/ethereum/go-ethereum/log" + "github.com/fractalplatform/fractal/common" + "github.com/fractalplatform/fractal/params" "github.com/fractalplatform/fractal/state" "github.com/fractalplatform/fractal/types" ) @@ -47,7 +53,7 @@ func NewSystem(state *state.StateDB, config *Config) *System { } // RegCandidate register a candidate -func (sys *System) RegCandidate(epoch uint64, candidate string, url string, stake *big.Int, number uint64) error { +func (sys *System) RegCandidate(epoch uint64, candidate string, url string, stake *big.Int, number uint64, fid uint64) error { // url validity if uint64(len(url)) > sys.config.MaxURLLen { return fmt.Errorf("invalid url (too long, max %v)", sys.config.MaxURLLen) @@ -97,23 +103,29 @@ func (sys *System) RegCandidate(epoch uint64, candidate string, url string, stak } prod.Quantity = new(big.Int).Add(prod.Quantity, q) prod.TotalQuantity = new(big.Int).Add(prod.TotalQuantity, q) - if err := sys.SetCandidate(prod); err != nil { - return err - } gstate, err := sys.GetState(epoch) if err != nil { return err } gstate.TotalQuantity = new(big.Int).Add(gstate.TotalQuantity, q) + if fid >= params.ForkID2 { + if err := sys.updateState(gstate, prod); err != nil { + return err + } + } if err := sys.SetState(gstate); err != nil { return err } + + if err := sys.SetCandidate(prod); err != nil { + return err + } return nil } // UpdateCandidate update a candidate -func (sys *System) UpdateCandidate(epoch uint64, candidate string, url string, nstake *big.Int, number uint64) error { +func (sys *System) UpdateCandidate(epoch uint64, candidate string, url string, nstake *big.Int, number uint64, fid uint64) error { // url validity if uint64(len(url)) > sys.config.MaxURLLen { return fmt.Errorf("invalid url (too long, max %v)", sys.config.MaxURLLen) @@ -177,23 +189,28 @@ func (sys *System) UpdateCandidate(epoch uint64, candidate string, url string, n prod.Quantity = new(big.Int).Add(prod.Quantity, q) prod.TotalQuantity = new(big.Int).Add(prod.TotalQuantity, q) prod.Number = number - if err := sys.SetCandidate(prod); err != nil { - return err - } gstate, err := sys.GetState(epoch) if err != nil { return err } gstate.TotalQuantity = new(big.Int).Add(gstate.TotalQuantity, q) + if fid >= params.ForkID2 { + if err := sys.updateState(gstate, prod); err != nil { + return err + } + } if err := sys.SetState(gstate); err != nil { return err } + if err := sys.SetCandidate(prod); err != nil { + return err + } return nil } // UnregCandidate unregister a candidate -func (sys *System) UnregCandidate(epoch uint64, candidate string, number uint64) error { +func (sys *System) UnregCandidate(epoch uint64, candidate string, number uint64, fid uint64) error { // name validity prod, err := sys.GetCandidate(epoch, candidate) if err != nil { @@ -209,9 +226,6 @@ func (sys *System) UnregCandidate(epoch uint64, candidate string, number uint64) // db prod.Type = Freeze prod.Number = number - if err := sys.SetCandidate(prod); err != nil { - return err - } // stake := new(big.Int).Mul(prod.Quantity, sys.config.unitStake()) // action, err := sys.Undelegate(candidate, stake) @@ -246,14 +260,22 @@ func (sys *System) UnregCandidate(epoch uint64, candidate string, number uint64) return err } gstate.TotalQuantity = new(big.Int).Sub(gstate.TotalQuantity, prod.TotalQuantity) + if fid >= params.ForkID2 { + if err := sys.updateState(gstate, prod); err != nil { + return err + } + } if err := sys.SetState(gstate); err != nil { return err } + if err := sys.SetCandidate(prod); err != nil { + return err + } return nil } // RefundCandidate refund a candidate -func (sys *System) RefundCandidate(epoch uint64, candidate string, number uint64) error { +func (sys *System) RefundCandidate(epoch uint64, candidate string, number uint64, fid uint64) error { // name validity prod, err := sys.GetCandidate(epoch, candidate) if err != nil { @@ -331,7 +353,7 @@ func (sys *System) RefundCandidate(epoch uint64, candidate string, number uint64 } // VoteCandidate vote a candidate -func (sys *System) VoteCandidate(epoch uint64, voter string, candidate string, stake *big.Int, number uint64) error { +func (sys *System) VoteCandidate(epoch uint64, voter string, candidate string, stake *big.Int, number uint64, fid uint64) error { // candidate validity prod, err := sys.GetCandidate(epoch, candidate) if err != nil { @@ -402,19 +424,24 @@ func (sys *System) VoteCandidate(epoch uint64, voter string, candidate string, s } prod.TotalQuantity = new(big.Int).Add(prod.TotalQuantity, q) - if err := sys.SetCandidate(prod); err != nil { - return err - } gstate.TotalQuantity = new(big.Int).Add(gstate.TotalQuantity, q) + if fid >= params.ForkID2 { + if err := sys.updateState(gstate, prod); err != nil { + return err + } + } if err := sys.SetState(gstate); err != nil { return err } + if err := sys.SetCandidate(prod); err != nil { + return err + } return nil } // KickedCandidate kicked -func (sys *System) KickedCandidate(epoch uint64, candidate string, number uint64) error { +func (sys *System) KickedCandidate(epoch uint64, candidate string, number uint64, fid uint64) error { // name validity prod, err := sys.GetCandidate(epoch, candidate) if prod == nil || err != nil { @@ -456,6 +483,12 @@ func (sys *System) KickedCandidate(epoch uint64, candidate string, number uint64 return err } gstate.TotalQuantity = new(big.Int).Sub(gstate.TotalQuantity, prod.TotalQuantity) + if fid >= params.ForkID2 { + prod.Type = Black + if err := sys.updateState(gstate, prod); err != nil { + return err + } + } if err := sys.SetState(gstate); err != nil { return err } @@ -466,18 +499,44 @@ func (sys *System) KickedCandidate(epoch uint64, candidate string, number uint64 return sys.SetCandidate(prod) } +// RemoveKickedCandidate remove +func (sys *System) RemoveKickedCandidate(epoch uint64, candidate string, number uint64, fid uint64) error { + // name validity + prod, err := sys.GetCandidate(epoch, candidate) + if prod == nil || err != nil { + return err + } + if prod.Type != Black { + return nil + } + + if err := sys.DelCandidate(epoch, prod.Name); err != nil { + return err + } + return nil +} + // ExitTakeOver system exit take over -func (sys *System) ExitTakeOver(epoch uint64) error { +func (sys *System) ExitTakeOver(epoch uint64, number uint64, fid uint64) error { gstate, err := sys.GetState(epoch) if err != nil { return err } + if fid >= params.ForkID2 { + epoch, err := sys.GetTakeOver() + if err != nil { + return err + } + if gstate.Epoch == epoch { + return fmt.Errorf("take over must in diff epoch") + } + } gstate.TakeOver = false return sys.SetState(gstate) } -// UpdateElectedCandidates update -func (sys *System) UpdateElectedCandidates(pepoch uint64, epoch uint64, number uint64, miner string) error { +// UpdateElectedCandidates0 update +func (sys *System) UpdateElectedCandidates0(pepoch uint64, epoch uint64, number uint64, miner string) error { if pepoch > epoch { panic(fmt.Errorf("UpdateElectedCandidates unreached")) } @@ -495,6 +554,7 @@ func (sys *System) UpdateElectedCandidates(pepoch uint64, epoch uint64, number u if err != nil { return err } + sort.Sort(candidateInfoArray) n := sys.config.BackupScheduleSize + sys.config.CandidateScheduleSize activatedCandidateSchedule := []string{} activatedTotalQuantity := big.NewInt(0) @@ -575,14 +635,14 @@ func (sys *System) UpdateElectedCandidates(pepoch uint64, epoch uint64, number u if pepoch != epoch { gstate := &GlobalState{ - Epoch: epoch, - PreEpoch: pstate.Epoch, - ActivatedTotalQuantity: big.NewInt(0), - TotalQuantity: new(big.Int).SetBytes(ntotalQuantity.Bytes()), - OffCandidateNumber: []uint64{}, - OffCandidateSchedule: []uint64{}, - TakeOver: pstate.TakeOver, - Dpos: pstate.Dpos, + Epoch: epoch, + PreEpoch: pstate.Epoch, + ActivatedTotalQuantity: big.NewInt(0), + TotalQuantity: new(big.Int).SetBytes(ntotalQuantity.Bytes()), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + TakeOver: pstate.TakeOver, + Dpos: pstate.Dpos, } if err := sys.SetLastestEpoch(epoch); err != nil { return err @@ -592,6 +652,169 @@ func (sys *System) UpdateElectedCandidates(pepoch uint64, epoch uint64, number u return nil } +// UpdateElectedCandidates1 update +func (sys *System) UpdateElectedCandidates1(pepoch uint64, epoch uint64, number uint64, miner string) error { + if pepoch > epoch { + panic(fmt.Errorf("UpdateElectedCandidates unreached")) + } + pstate, err := sys.GetState(pepoch) + if err != nil { + return err + } + if pepoch == epoch && + len(pstate.ActivatedCandidateSchedule) != 0 { + return nil + } + + t := time.Now() + defer func() { + log.Debug("UpdateElectedCandidates1", "pepoch", pepoch, "epoch", epoch, "number", number, "elapsed", common.PrettyDuration(time.Now().Sub(t))) + }() + n := sys.config.BackupScheduleSize + sys.config.CandidateScheduleSize + initActivatedCandidateSchedule := func(gstate *GlobalState, candidateInfoArray CandidateInfoArray) error { + activatedCandidateSchedule := []string{} + activatedTotalQuantity := big.NewInt(0) + sort.Sort(candidateInfoArray) + if gstate.Dpos { + for _, candidateInfo := range candidateInfoArray { + if !candidateInfo.invalid() { + if candidateInfo.Quantity.Sign() == 0 || strings.Compare(candidateInfo.Name, sys.config.SystemName) == 0 { + continue + } + if uint64(len(activatedCandidateSchedule)) >= n { + break + } + activatedCandidateSchedule = append(activatedCandidateSchedule, candidateInfo.Name) + activatedTotalQuantity = new(big.Int).Add(activatedTotalQuantity, candidateInfo.TotalQuantity) + } + } + } else { + tstate := &GlobalState{ + Epoch: math.MaxUint64, + PreEpoch: math.MaxUint64, + ActivatedTotalQuantity: big.NewInt(0), + TotalQuantity: big.NewInt(0), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + Number: 0, + } + for _, candidateInfo := range candidateInfoArray { + if !candidateInfo.invalid() { + if candidateInfo.Quantity.Sign() != 0 && strings.Compare(candidateInfo.Name, sys.config.SystemName) != 0 { + tstate.Number++ + tstate.TotalQuantity = new(big.Int).Add(tstate.TotalQuantity, candidateInfo.TotalQuantity) + if uint64(len(tstate.ActivatedCandidateSchedule)) < n { + tstate.ActivatedCandidateSchedule = append(tstate.ActivatedCandidateSchedule, candidateInfo.Name) + tstate.ActivatedTotalQuantity = new(big.Int).Add(tstate.ActivatedTotalQuantity, candidateInfo.TotalQuantity) + } + continue + } + if uint64(len(activatedCandidateSchedule)) < n { + activatedCandidateSchedule = append(activatedCandidateSchedule, candidateInfo.Name) + activatedTotalQuantity = new(big.Int).Add(activatedTotalQuantity, candidateInfo.TotalQuantity) + } + } + } + + if tstate.TotalQuantity.Cmp(sys.config.ActivatedMinQuantity) >= 0 && + tstate.Number >= n && + tstate.Number >= sys.config.ActivatedMinCandidate { + gstate.Dpos = true + gstate.ActivatedTotalQuantity = tstate.ActivatedTotalQuantity + gstate.ActivatedCandidateSchedule = tstate.ActivatedCandidateSchedule + } else { + if err := sys.SetState(tstate); err != nil { + return err + } + if init := len(activatedCandidateSchedule); init > 0 { + index := 0 + for uint64(len(activatedCandidateSchedule)) < sys.config.CandidateScheduleSize { + activatedCandidateSchedule = append(activatedCandidateSchedule, activatedCandidateSchedule[index%init]) + index++ + } + } + } + } + gstate.ActivatedCandidateSchedule = activatedCandidateSchedule + gstate.ActivatedTotalQuantity = activatedTotalQuantity + if err := sys.SetState(gstate); err != nil { + return err + } + return nil + } + + candidateInfoArray, err := sys.GetCandidates(pstate.Epoch) + if err != nil { + return err + } + if len(pstate.ActivatedCandidateSchedule) == 0 { + ppstate, err := sys.GetState(pstate.PreEpoch) + if err != nil { + return err + } + usingCandidateIndexSchedule := []uint64{} + for index := range ppstate.ActivatedCandidateSchedule { + if uint64(index) >= sys.config.CandidateScheduleSize { + break + } + usingCandidateIndexSchedule = append(usingCandidateIndexSchedule, uint64(index)) + } + ppstate.UsingCandidateIndexSchedule = usingCandidateIndexSchedule + if err := sys.SetState(ppstate); err != nil { + return err + } + + if err := initActivatedCandidateSchedule(pstate, candidateInfoArray); err != nil { + return err + } + } + + if pepoch != epoch { + usingCandidateIndexSchedule := []uint64{} + for index := range pstate.ActivatedCandidateSchedule { + if uint64(index) >= sys.config.CandidateScheduleSize { + break + } + usingCandidateIndexSchedule = append(usingCandidateIndexSchedule, uint64(index)) + } + pstate.UsingCandidateIndexSchedule = usingCandidateIndexSchedule + if err := sys.SetState(pstate); err != nil { + return err + } + + tcandidateInfoArray := CandidateInfoArray{} + gstate := &GlobalState{ + Epoch: epoch, + PreEpoch: pepoch, + ActivatedTotalQuantity: big.NewInt(0), + TotalQuantity: big.NewInt(0), + UsingCandidateIndexSchedule: []uint64{}, + BadCandidateIndexSchedule: []uint64{}, + TakeOver: pstate.TakeOver, + Dpos: pstate.Dpos, + Number: number, + } + for _, candidateInfo := range candidateInfoArray { + tcandidateInfo := candidateInfo.copy() + tcandidateInfo.Epoch = epoch + tcandidateInfo.TotalQuantity = tcandidateInfo.Quantity + if !tcandidateInfo.invalid() { + gstate.TotalQuantity = new(big.Int).Add(gstate.TotalQuantity, tcandidateInfo.TotalQuantity) + } + if err := sys.SetCandidate(tcandidateInfo); err != nil { + return err + } + tcandidateInfoArray = append(tcandidateInfoArray, tcandidateInfo) + } + if err := initActivatedCandidateSchedule(gstate, tcandidateInfoArray); err != nil { + return err + } + if err := sys.SetLastestEpoch(gstate.Epoch); err != nil { + return err + } + } + return nil +} func (sys *System) getAvailableQuantity(epoch uint64, voter string) (*big.Int, error) { q, err := sys.GetAvailableQuantity(epoch, voter) if err != nil { @@ -616,3 +839,172 @@ func (sys *System) getAvailableQuantity(epoch uint64, voter string) (*big.Int, e } return q, nil } + +func (sys *System) usingCandiate(gstate *GlobalState, offset uint64) string { + size := uint64(len(gstate.UsingCandidateIndexSchedule)) + if size == 0 && len(gstate.BadCandidateIndexSchedule) == 0 { + for index := range gstate.ActivatedCandidateSchedule { + if uint64(index) >= sys.config.CandidateScheduleSize { + break + } + gstate.UsingCandidateIndexSchedule = append(gstate.UsingCandidateIndexSchedule, uint64(index)) + size++ + } + } + if offset >= size { + return "" + } + index := gstate.UsingCandidateIndexSchedule[offset] + if index == InvalidIndex { + return "" + } + return gstate.ActivatedCandidateSchedule[index] +} + +func (sys *System) updateState(gstate *GlobalState, prod *CandidateInfo) error { + if prod.Quantity.Sign() == 0 || + strings.Compare(prod.Name, sys.config.SystemName) == 0 { + return nil + } + // timestamp := sys.config.epochTimeStamp(gstate.Epoch) + // if bquantity, err := sys.GetBalanceByTime(prod.Name, timestamp); err != nil { + // log.Debug("insert", "candidate", prod.Name, "ignore", err) + // return nil + // } else if s := new(big.Int).Mul(sys.config.unitStake(), sys.config.CandidateAvailableMinQuantity); bquantity.Cmp(s) == -1 { + // log.Debug("insert", "candidate", prod.Name, "ignore", "insufficient available quantity") + // return nil + // } + + insert := func(gstate *GlobalState, prod *CandidateInfo) error { + n := sys.config.CandidateScheduleSize + sys.config.BackupScheduleSize + var low *CandidateInfo + if cnt := len(gstate.ActivatedCandidateSchedule); uint64(cnt) == n { + lowprod, err := sys.GetCandidate(prod.Epoch, gstate.ActivatedCandidateSchedule[cnt-1]) + if err != nil { + return err + } + if cmp := lowprod.TotalQuantity.Cmp(prod.TotalQuantity); cmp == 1 { + return nil + } + low = lowprod + } + + if prod.invalid() { + has := false + findex := 0 + names := map[string]bool{} + for index, name := range gstate.ActivatedCandidateSchedule { + names[name] = true + if strings.Compare(name, prod.Name) == 0 { + findex = index + has = true + } + } + if !has { + return nil + } + gstate.ActivatedTotalQuantity = new(big.Int).Sub(gstate.ActivatedTotalQuantity, prod.TotalQuantity) + gstate.ActivatedCandidateSchedule = append(gstate.ActivatedCandidateSchedule[:findex], gstate.ActivatedCandidateSchedule[findex+1:]...) + + candidateInfoArray, err := sys.GetCandidates(prod.Epoch) + if err != nil { + return err + } + var sprod *CandidateInfo + for _, tprod := range candidateInfoArray { + if !tprod.invalid() { + if _, ok := names[tprod.Name]; ok { + continue + } + if tprod.Quantity.Sign() == 0 || + strings.Compare(tprod.Name, sys.config.SystemName) == 0 { + continue + } + if sprod == nil || more(tprod, sprod) { + sprod = tprod + log.Debug("updateState", "candiate invalid", prod.Name, "replace", sprod.Name) + } + } + } + if sprod == nil { + log.Debug("updateState", "candiate invalid", prod.Name) + return nil + } + log.Debug("updateState", "candiate invalid", prod.Name, "replaced", sprod.Name) + prod = sprod + } + + activatedCandidateSchedule := []string{} + has := false + for _, name := range gstate.ActivatedCandidateSchedule { + tprod, err := sys.GetCandidate(prod.Epoch, name) + if err != nil { + return err + } + if strings.Compare(prod.Name, name) == 0 { + gstate.ActivatedTotalQuantity = new(big.Int).Sub(gstate.ActivatedTotalQuantity, tprod.TotalQuantity) + continue + } + if !has && more(prod, tprod) { + has = true + gstate.ActivatedTotalQuantity = new(big.Int).Add(gstate.ActivatedTotalQuantity, prod.TotalQuantity) + activatedCandidateSchedule = append(activatedCandidateSchedule, prod.Name) + } + activatedCandidateSchedule = append(activatedCandidateSchedule, name) + } + if cnt := len(activatedCandidateSchedule); uint64(cnt) > n { + activatedCandidateSchedule = activatedCandidateSchedule[:n] + gstate.ActivatedTotalQuantity = new(big.Int).Sub(gstate.ActivatedTotalQuantity, low.TotalQuantity) + } else if !has && uint64(cnt) < n { + gstate.ActivatedTotalQuantity = new(big.Int).Add(gstate.ActivatedTotalQuantity, prod.TotalQuantity) + activatedCandidateSchedule = append(activatedCandidateSchedule, prod.Name) + } + gstate.ActivatedCandidateSchedule = activatedCandidateSchedule + return nil + } + + if !gstate.Dpos { + epoch := uint64(math.MaxUint64) + tstate, err := sys.GetState(epoch) + if err != nil { + return err + } + + if prod.invalid() { + tstate.TotalQuantity = new(big.Int).Sub(tstate.TotalQuantity, prod.TotalQuantity) + tstate.Number-- + } else { + tprod, err := sys.GetCandidate(prod.Epoch, prod.Name) + if err != nil { + return err + } + if tprod == nil { + tstate.TotalQuantity = new(big.Int).Add(tstate.TotalQuantity, prod.TotalQuantity) + tstate.Number++ + } else { + tstate.TotalQuantity = new(big.Int).Add(tstate.TotalQuantity, new(big.Int).Sub(prod.TotalQuantity, tprod.TotalQuantity)) + } + } + + if err := insert(tstate, prod); err != nil { + return err + } + + if tstate.TotalQuantity.Cmp(sys.config.ActivatedMinQuantity) >= 0 && + tstate.Number >= sys.config.BackupScheduleSize+sys.config.CandidateScheduleSize && + tstate.Number >= sys.config.ActivatedMinCandidate { + gstate.Dpos = true + gstate.ActivatedTotalQuantity = tstate.ActivatedTotalQuantity + gstate.ActivatedCandidateSchedule = tstate.ActivatedCandidateSchedule + } + + if err := sys.SetState(tstate); err != nil { + return err + } + return nil + } + if err := insert(gstate, prod); err != nil { + return err + } + return nil +} diff --git a/consensus/dpos/vote_test.go b/consensus/dpos/vote_test.go index df47ef84..ebd6338b 100644 --- a/consensus/dpos/vote_test.go +++ b/consensus/dpos/vote_test.go @@ -56,16 +56,16 @@ func TestCandiate(t *testing.T) { if err := sys.IDB.SetAvailableQuantity(uint64(index), candidate, new(big.Int).Mul(big10, minStakeCandidate)); err != nil { panic(fmt.Errorf("SetAvailableQuantity --- %v", err)) } - if err := sys.RegCandidate(uint64(index), candidate, strings.Repeat(fmt.Sprintf("www.%v.com", candidate), int(DefaultConfig.MaxURLLen)), new(big.Int).Mul(big1, minStakeCandidate), uint64(index)); !strings.Contains(err.Error(), "invalid url") { + if err := sys.RegCandidate(uint64(index), candidate, strings.Repeat(fmt.Sprintf("www.%v.com", candidate), int(DefaultConfig.MaxURLLen)), new(big.Int).Mul(big1, minStakeCandidate), uint64(index), 0); !strings.Contains(err.Error(), "invalid url") { panic(fmt.Sprintf("RegCandidate invalid url %v mismatch", err)) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), big1, uint64(index)); !strings.Contains(err.Error(), "non divisibility") { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), big1, uint64(index), 0); !strings.Contains(err.Error(), "non divisibility") { panic(fmt.Sprintf("RegCandidate invalid stake %v mismatch", err)) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big0, minStakeCandidate), uint64(index)); !strings.Contains(err.Error(), "insufficient") { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big0, minStakeCandidate), uint64(index), 0); !strings.Contains(err.Error(), "insufficient") { panic(fmt.Sprintf("RegCandidate invalid stake %v mismatch", err)) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index)); err != nil { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index), 0); err != nil { panic(fmt.Sprintf("RegCandidate %v", err)) } @@ -82,11 +82,11 @@ func TestCandiate(t *testing.T) { panic(fmt.Sprintf("GetState mismatch")) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index)); !strings.Contains(err.Error(), "invalid candidate") { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index), 0); !strings.Contains(err.Error(), "invalid candidate") { panic(fmt.Sprintf("RegCandidate invalid name %v mismatch", err)) } - if err := sys.UpdateCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big2, minStakeCandidate), uint64(index)); err != nil { + if err := sys.UpdateCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big2, minStakeCandidate), uint64(index), 0); err != nil { panic(fmt.Sprintf("UpdateCandidate %v", err)) } @@ -102,7 +102,7 @@ func TestCandiate(t *testing.T) { panic(fmt.Sprintf("GetState mismatch")) } - if err := sys.UnregCandidate(uint64(index), candidate, uint64(index)); err != nil { + if err := sys.UnregCandidate(uint64(index), candidate, uint64(index), 0); err != nil { panic(fmt.Sprintf("UnregCandidate %v", err)) } @@ -141,10 +141,10 @@ func TestVote(t *testing.T) { if err := sys.IDB.SetAvailableQuantity(uint64(index), candidate, new(big.Int).Mul(big10, minStakeCandidate)); err != nil { panic(fmt.Errorf("SetAvailableQuantity --- %v", err)) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index)); err != nil { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index), 0); err != nil { panic(fmt.Sprintf("RegCandidate %v", err)) } - if err := sys.RefundCandidate(uint64(index), candidate, uint64(index)); err == nil { + if err := sys.RefundCandidate(uint64(index), candidate, uint64(index), 0); err == nil { panic(fmt.Sprintf("RefundCandidate %v", err)) } } @@ -153,19 +153,19 @@ func TestVote(t *testing.T) { if err := sys.IDB.SetAvailableQuantity(uint64(index), voter, new(big.Int).Mul(big10, minStakeVote)); err != nil { panic(fmt.Errorf("SetAvailableQuantity --- %v", err)) } - if err := sys.VoteCandidate(uint64(index), voter, "test", new(big.Int).Mul(big1, minStakeVote), uint64(index)); !strings.Contains(err.Error(), "invalid candidate") { + if err := sys.VoteCandidate(uint64(index), voter, "test", new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); !strings.Contains(err.Error(), "invalid candidate") { panic(fmt.Sprintf("VoteCandidate invalid candidate %v mismatch", err)) } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], big1, uint64(index)); !strings.Contains(err.Error(), "non divisibility") { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], big1, uint64(index), 0); !strings.Contains(err.Error(), "non divisibility") { panic(fmt.Sprintf("VoteCandidate invalid stake %v mismatch", err)) } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big0, minStakeVote), uint64(index)); !strings.Contains(err.Error(), "insufficient") { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big0, minStakeVote), uint64(index), 0); !strings.Contains(err.Error(), "insufficient") { panic(fmt.Sprintf("VoteCandidate invalid stake %v mismatch", err)) } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index)); err != nil { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); err != nil { panic(fmt.Sprintf("VoteCandidate --- %v", err)) } @@ -173,7 +173,7 @@ func TestVote(t *testing.T) { panic(fmt.Sprintf("GetCandidate mismatch")) } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index)); err != nil { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); err != nil { panic(fmt.Sprintf("VoteCandidate --- %v", err)) } @@ -209,7 +209,7 @@ func TestCandidateVote(t *testing.T) { if err := sys.IDB.SetAvailableQuantity(uint64(index), candidate, new(big.Int).Mul(big10, minStakeCandidate)); err != nil { panic(fmt.Errorf("SetAvailableQuantity --- %v", err)) } - if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index)); err != nil { + if err := sys.RegCandidate(uint64(index), candidate, fmt.Sprintf("www.%v.com", candidate), new(big.Int).Mul(big1, minStakeCandidate), uint64(index), 0); err != nil { panic(fmt.Sprintf("RegCandidate %v", err)) } // if err := sys.UnregCandidate(uint64(index), candidate, uint64(index)); err != nil { @@ -221,7 +221,7 @@ func TestCandidateVote(t *testing.T) { if err := sys.IDB.SetAvailableQuantity(uint64(index), voter, new(big.Int).Mul(big10, minStakeVote)); err != nil { panic(fmt.Errorf("SetAvailableQuantity --- %v", err)) } - if err := sys.VoteCandidate(uint64(index), voter, "test", new(big.Int).Mul(big1, minStakeVote), uint64(index)); !strings.Contains(err.Error(), "invalid candidate") { + if err := sys.VoteCandidate(uint64(index), voter, "test", new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); !strings.Contains(err.Error(), "invalid candidate") { panic(fmt.Sprintf("VoteCandidate invalid candidate %v mismatch", err)) } @@ -233,7 +233,7 @@ func TestCandidateVote(t *testing.T) { // panic(fmt.Sprintf("VoteCandidate invalid stake %v mismatch", err)) // } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index)); err != nil { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); err != nil { panic(fmt.Sprintf("VoteCandidate --- %v", err)) } @@ -241,7 +241,7 @@ func TestCandidateVote(t *testing.T) { panic(fmt.Sprintf("GetCandidate mismatch")) } - if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index)); err != nil { + if err := sys.VoteCandidate(uint64(index), voter, candidates[index], new(big.Int).Mul(big1, minStakeVote), uint64(index), 0); err != nil { panic(fmt.Sprintf("VoteCandidate --- %v", err)) } diff --git a/consensus/miner/miner.go b/consensus/miner/miner.go index ac73faa4..65d67726 100644 --- a/consensus/miner/miner.go +++ b/consensus/miner/miner.go @@ -26,8 +26,6 @@ import ( "github.com/fractalplatform/fractal/consensus" "github.com/fractalplatform/fractal/crypto" "github.com/fractalplatform/fractal/params" - "github.com/fractalplatform/fractal/state" - "github.com/fractalplatform/fractal/types" ) // Miner creates blocks and searches for proof values. @@ -116,11 +114,6 @@ func (miner *Miner) Mining() bool { return atomic.LoadInt32(&miner.mining) > 0 } -// Pending returns the currently pending block and associated state. -func (miner *Miner) Pending() (*types.Block, *state.StateDB) { - return miner.worker.pending() -} - // SetCoinbase coinbase name & private key func (miner *Miner) SetCoinbase(name string, privKeys []string) error { privs := make([]*ecdsa.PrivateKey, 0, len(privKeys)) diff --git a/consensus/miner/worker.go b/consensus/miner/worker.go index 54294cb7..17eae285 100644 --- a/consensus/miner/worker.go +++ b/consensus/miner/worker.go @@ -17,7 +17,6 @@ package miner import ( - "bytes" "crypto/ecdsa" "errors" "fmt" @@ -61,12 +60,11 @@ type Worker struct { pubKeys [][]byte extra []byte - currentWork *Work - wg sync.WaitGroup mining int32 quitWork chan struct{} quitWorkRW sync.RWMutex + wgWork sync.WaitGroup quit chan struct{} force bool } @@ -77,7 +75,6 @@ func newWorker(consensus consensus.IConsensus) *Worker { quit: make(chan struct{}), } go worker.update() - worker.commitNewWork(time.Now().UnixNano(), nil) return worker } @@ -137,11 +134,11 @@ func (worker *Worker) mintLoop() { return nil, fmt.Errorf("not found match private key for sign") }) interval := int64(dpos.BlockInterval()) - timer := time.NewTimer(time.Duration(interval - (time.Now().UnixNano() % interval))) - defer timer.Stop() + c := make(chan time.Time) + worker.utimer(time.Duration(interval-(time.Now().UnixNano()%interval)), c) for { select { - case now := <-timer.C: + case now := <-c: worker.quitWorkRW.Lock() if worker.quitWork != nil { close(worker.quitWork) @@ -149,10 +146,16 @@ func (worker *Worker) mintLoop() { log.Debug("next time coming, will be closing current work") } worker.quitWorkRW.Unlock() - time.Sleep(time.Duration(worker.delayDuration * uint64(time.Millisecond))) + worker.wgWork.Wait() + quit := make(chan struct{}) - worker.mintBlock(int64(dpos.Slot(uint64(now.UnixNano()))), quit) - timer.Reset(time.Duration(interval - (time.Now().UnixNano() % interval))) + worker.wgWork.Add(1) + timestamp := int64(dpos.Slot(uint64(now.UnixNano()))) + go worker.mintBlock(timestamp, quit) + if d := time.Unix(timestamp/int64(time.Second), timestamp%int64(time.Second)).Sub(time.Now()); d > 0 { + worker.usleep(d) + } + worker.utimer(time.Duration(interval-(time.Now().UnixNano()%interval)), c) case <-worker.quit: worker.quit = make(chan struct{}) return @@ -168,50 +171,53 @@ func (worker *Worker) mintBlock(timestamp int64, quit chan struct{}) { worker.quitWorkRW.Lock() worker.quitWork = nil worker.quitWorkRW.Unlock() + worker.wgWork.Done() }() - cdpos := worker.Engine().(*dpos.Dpos) - header := worker.CurrentHeader() - state, err := worker.StateAt(header.Root) - if err != nil { - log.Error("failed to mint block", "timestamp", timestamp, "err", err) - return - } - if err := cdpos.IsValidateCandidate(worker, header, uint64(timestamp), worker.coinbase, worker.pubKeys, state, worker.force); err != nil { - switch err { - case dpos.ErrSystemTakeOver: - fallthrough - case dpos.ErrTooMuchRreversible: - fallthrough - case dpos.ErrIllegalCandidateName: - fallthrough - case dpos.ErrIllegalCandidatePubKey: - log.Error("failed to mint the block", "timestamp", timestamp, "err", err, "candidate", worker.coinbase) - default: - log.Debug("failed to mint the block", "timestamp", timestamp, "err", err) - } - return - } - bstart := time.Now() -outer: - + log.Debug("mint block", "timestamp", timestamp) for { select { case <-quit: return default: } - block, err := worker.commitNewWork(timestamp, quit) + + cdpos := worker.Engine().(*dpos.Dpos) + header := worker.CurrentHeader() + state, err := worker.StateAt(header.Root) + if err != nil { + log.Error("failed to mint block", "timestamp", timestamp, "err", err) + return + } + theader := &types.Header{} + worker.FillForkID(theader, state) + if err := cdpos.IsValidateCandidate(worker, header, uint64(timestamp), worker.coinbase, worker.pubKeys, state, worker.force, theader.CurForkID()); err != nil { + switch err { + case dpos.ErrSystemTakeOver: + fallthrough + case dpos.ErrTooMuchRreversible: + fallthrough + case dpos.ErrIllegalCandidateName: + fallthrough + case dpos.ErrIllegalCandidatePubKey: + log.Error("failed to mint the block", "timestamp", timestamp, "err", err, "candidate", worker.coinbase) + default: + log.Debug("failed to mint the block", "timestamp", timestamp, "err", err) + } + return + } + block, err := worker.commitNewWork(timestamp, header, quit) if err == nil { log.Info("Mined new block", "candidate", block.Coinbase(), "number", block.Number(), "hash", block.Hash().String(), "time", block.Time().Int64(), "txs", len(block.Txs), "gas", block.GasUsed(), "diff", block.Difficulty(), "elapsed", common.PrettyDuration(time.Since(bstart))) - break outer + break } if strings.Contains(err.Error(), "mint") { log.Error("failed to mint block", "timestamp", timestamp, "err", err) - break outer + break } else if strings.Contains(err.Error(), "wait") { - time.Sleep(time.Duration(cdpos.BlockInterval() / 10)) + worker.usleep(time.Duration(cdpos.BlockInterval() / 10)) + //time.Sleep(time.Duration(cdpos.BlockInterval() / 10)) } log.Warn("failed to mint block", "timestamp", timestamp, "err", err) @@ -249,14 +255,7 @@ func (worker *Worker) setExtra(extra []byte) { worker.extra = extra } -func (worker *Worker) pending() (*types.Block, *state.StateDB) { - worker.mu.Lock() - defer worker.mu.Unlock() - return worker.currentWork.currentBlock, worker.currentWork.currentState -} - -func (worker *Worker) commitNewWork(timestamp int64, quit chan struct{}) (*types.Block, error) { - parent := worker.CurrentHeader() +func (worker *Worker) commitNewWork(timestamp int64, parent *types.Header, quit chan struct{}) (*types.Block, error) { dpos := worker.Engine().(*dpos.Dpos) if time.Now().UnixNano() >= timestamp+int64(dpos.BlockInterval()) { return nil, errors.New("mint the ingore block") @@ -272,11 +271,6 @@ func (worker *Worker) commitNewWork(timestamp int64, quit chan struct{}) (*types } number := parent.Number - pblk := worker.GetBlock(parent.Hash(), parent.Number.Uint64()) - if pblk == nil { - log.Error("parent is nil", "number", parent.Number.Uint64()) - return nil, errors.New("parent is nil") - } header := &types.Header{ ParentHash: parent.Hash(), Number: new(big.Int).Add(number, big.NewInt(1)), @@ -307,9 +301,6 @@ func (worker *Worker) commitNewWork(timestamp int64, quit chan struct{}) (*types currentCnt: 0, quit: quit, } - worker.mu.Lock() - worker.currentWork = work - worker.mu.Unlock() if err := worker.Prepare(worker.IConsensus, work.currentHeader, work.currentTxs, work.currentReceipts, work.currentState); err != nil { return nil, fmt.Errorf("prepare header for mining, err: %v", err) @@ -352,12 +343,10 @@ func (worker *Worker) commitNewWork(timestamp int64, quit chan struct{}) (*types log.BlockHash = block.Hash() } - if !bytes.Equal(block.ParentHash().Bytes(), worker.CurrentHeader().Hash().Bytes()) { - return nil, fmt.Errorf("old parent hash") - } if _, err := worker.WriteBlockWithState(block, work.currentReceipts, work.currentState); err != nil { return nil, fmt.Errorf("writing block to chain, err: %v", err) } + time.Sleep(time.Duration(worker.delayDuration * uint64(time.Millisecond))) event.SendEvent(&event.Event{Typecode: event.ChainHeadEv, Data: block}) event.SendEvent(&event.Event{Typecode: event.NewMinedEv, Data: blockchain.NewMinedBlockEvent{ @@ -365,9 +354,8 @@ func (worker *Worker) commitNewWork(timestamp int64, quit chan struct{}) (*types }}) return block, nil } - block := types.NewBlock(work.currentHeader, work.currentTxs, work.currentReceipts) - work.currentBlock = block - return block, nil + work.currentBlock = types.NewBlock(work.currentHeader, work.currentTxs, work.currentReceipts) + return work.currentBlock, nil } func (worker *Worker) commitTransactions(work *Work, txs *types.TransactionsByPriceAndNonce, interval uint64) error { @@ -481,3 +469,19 @@ type Work struct { currentState *state.StateDB quit chan struct{} } + +func (worker *Worker) usleep(duration time.Duration) { + end := time.Now().Add(duration) + for { + time.Sleep(time.Microsecond) + if time.Now().Sub(end) >= 0 { + break + } + } +} +func (worker *Worker) utimer(duration time.Duration, c chan time.Time) { + go func(c chan time.Time) { + worker.usleep(duration) + c <- time.Now() + }(c) +} diff --git a/nodes.txt b/nodes.txt new file mode 100644 index 00000000..7ebce70d --- /dev/null +++ b/nodes.txt @@ -0,0 +1 @@ +fnode://3a4d6ae6fbfef6bdfaa6c15b4b1c9752740c0d75db76193a26be05a77fe9c7fd34151e4537b3acef0738cc567d4da5ef2928090696061a437c58ee4694149637@192.168.2.13:40281 diff --git a/params/chainconfig.go b/params/chainconfig.go index 14d1c9ac..f2228ed8 100644 --- a/params/chainconfig.go +++ b/params/chainconfig.go @@ -144,8 +144,13 @@ func (cfg *ChainConfig) Copy() *ChainConfig { } const ( - ForkID1 = uint64(1) //ForkID1 account first name > 12, asset name contain account name + //ForkID0 init + ForkID0 = uint64(0) + //ForkID1 account first name > 12, asset name contain account name + ForkID1 = uint64(1) + //ForkID2 dpos + ForkID2 = uint64(2) // NextForkID is the id of next fork - NextForkID uint64 = 1 + NextForkID uint64 = 2 ) diff --git a/processor/evm.go b/processor/evm.go index 9f1c3b50..fb0fc49b 100644 --- a/processor/evm.go +++ b/processor/evm.go @@ -72,7 +72,7 @@ type ChainContext interface { type EngineContext interface { Author(header *types.Header) (common.Name, error) - ProcessAction(number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) + ProcessAction(fid uint64, number uint64, chainCfg *params.ChainConfig, state *state.StateDB, action *types.Action) ([]*types.InternalAction, error) GetDelegatedByTime(state *state.StateDB, candidate string, timestamp uint64) (stake *big.Int, err error) @@ -80,7 +80,7 @@ type EngineContext interface { GetActivedCandidateSize(state *state.StateDB, epoch uint64) (size uint64, err error) - GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, err error) + GetActivedCandidate(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, isbad bool, err error) GetVoterStake(state *state.StateDB, epoch uint64, voter string, candidate string) (stake *big.Int, err error) } diff --git a/processor/transition.go b/processor/transition.go index d98ef110..21a71481 100644 --- a/processor/transition.go +++ b/processor/transition.go @@ -147,8 +147,10 @@ func (st *StateTransition) TransitionDb() (ret []byte, usedGas uint64, failed bo fallthrough case actionType == types.KickedCandidate: fallthrough + case actionType == types.RemoveKickedCandidate: + fallthrough case actionType == types.ExitTakeOver: - internalLogs, err := st.engine.ProcessAction(st.evm.Context.BlockNumber.Uint64(), + internalLogs, err := st.engine.ProcessAction(st.evm.Context.ForkID, st.evm.Context.BlockNumber.Uint64(), st.evm.ChainConfig(), st.evm.StateDB, st.action) vmerr = err evm.InternalTxs = append(evm.InternalTxs, internalLogs...) @@ -249,6 +251,8 @@ func (st *StateTransition) distributeGas(intrinsicGas uint64) { fallthrough case types.KickedCandidate: fallthrough + case types.RemoveKickedCandidate: + fallthrough case types.ExitTakeOver: st.distributeToSystemAccount(common.Name(st.chainConfig.DposName)) return diff --git a/processor/vm/instructions.go b/processor/vm/instructions.go index 9be17421..0ad56fb5 100644 --- a/processor/vm/instructions.go +++ b/processor/vm/instructions.go @@ -1144,7 +1144,7 @@ func opGetCandidate(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st id := epochID.Uint64() i := index.Uint64() // - name, stake, totalVote, counter, actualCounter, replace, err := evm.Context.GetActivedCandidate(evm.StateDB, id, i) + name, stake, totalVote, counter, actualCounter, replace, isbad, err := evm.Context.GetActivedCandidate(evm.StateDB, id, i) // if err == nil { id, err := evm.AccountDB.GetAccountIDByName(common.Name(name)) @@ -1155,6 +1155,11 @@ func opGetCandidate(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st stack.push(evm.interpreter.intPool.get().SetUint64(counter)) stack.push(evm.interpreter.intPool.get().SetUint64(actualCounter)) stack.push(evm.interpreter.intPool.get().SetUint64(replace)) + if isbad { + stack.push(evm.interpreter.intPool.get().SetUint64(1)) + } else { + stack.push(evm.interpreter.intPool.get().SetUint64(0)) + } return nil, nil } } @@ -1165,6 +1170,7 @@ func opGetCandidate(pc *uint64, evm *EVM, contract *Contract, memory *Memory, st stack.push(evm.interpreter.intPool.getZero()) stack.push(evm.interpreter.intPool.getZero()) stack.push(evm.interpreter.intPool.getZero()) + stack.push(evm.interpreter.intPool.getZero()) return nil, nil } diff --git a/processor/vm/runtime/runtime.go b/processor/vm/runtime/runtime.go index aaf09541..31f38046 100644 --- a/processor/vm/runtime/runtime.go +++ b/processor/vm/runtime/runtime.go @@ -113,8 +113,8 @@ func NewEnv(cfg *Config) *vm.EVM { return 3, nil }, //GetActivedCandidate - GetActivedCandidate: func(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, err error) { - return "testname", big.NewInt(0), big.NewInt(3), 3, 3, 3, nil + GetActivedCandidate: func(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, totalVote *big.Int, counter uint64, actualCounter uint64, replace uint64, isbad bool, err error) { + return "testname", big.NewInt(0), big.NewInt(3), 3, 3, 3, false, nil }, //GetVoterStake diff --git a/processor/vm/vm.go b/processor/vm/vm.go index dc779e78..432005b7 100644 --- a/processor/vm/vm.go +++ b/processor/vm/vm.go @@ -40,7 +40,7 @@ type ( //GetActivedCandidateSize GetActivedCandidateSizeFunc func(state *state.StateDB, epoch uint64) (size uint64, err error) //GetActivedCandidate - GetActivedCandidateFunc func(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, votes *big.Int, counter uint64, actualCounter uint64, replace uint64, err error) + GetActivedCandidateFunc func(state *state.StateDB, epoch uint64, index uint64) (name string, stake *big.Int, votes *big.Int, counter uint64, actualCounter uint64, replace uint64, isbad bool, err error) //GetVoterStake GetVoterStakeFunc func(state *state.StateDB, epoch uint64, voter string, candidate string) (stake *big.Int, err error) // GetHeaderByNumberFunc diff --git a/types/action.go b/types/action.go index a951a455..53c37da4 100644 --- a/types/action.go +++ b/types/action.go @@ -87,6 +87,8 @@ const ( KickedCandidate ActionType = 0x400 + iota // ExitTakeOver exit ExitTakeOver + // RemoveKickedCandidate kicked + RemoveKickedCandidate ) const ( @@ -213,6 +215,8 @@ func (a *Action) Check(conf *params.ChainConfig) error { fallthrough case KickedCandidate: fallthrough + case RemoveKickedCandidate: + fallthrough case ExitTakeOver: if a.data.To.String() != conf.DposName { return fmt.Errorf("Receipt should is %v", conf.DposName)