diff --git a/consensus/consensus.go b/consensus/consensus.go index bfc3540e4..0e7f38996 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -47,6 +47,9 @@ type ChainHeaderReader interface { // GetTd retrieves the total difficulty from the database by hash and number. GetTd(hash common.Hash, number uint64) *big.Int + + // GetCanonicalHash returns the canonical hash for a given block number + GetCanonicalHash(number uint64) common.Hash } // ChainReader defines a small collection of methods needed to access the local diff --git a/consensus/oasys/contract.go b/consensus/oasys/contract.go index 098165db2..f02b3ad49 100644 --- a/consensus/oasys/contract.go +++ b/consensus/oasys/contract.go @@ -278,7 +278,8 @@ func (c *Oasys) IsSystemTransaction(tx *types.Transaction, header *types.Header) return false, nil } else if _, ok := methods[called.RawName]; ok { log.Info("System method transacted", - "validator", header.Coinbase.Hex(), "tx", tx.Hash().Hex(), + "number", header.Number, "hash", header.Hash().Hex(), + "tx", tx.Hash().Hex(), "validator", header.Coinbase.Hex(), "contract", contract.address.Hex(), "method", called.RawName) return true, nil } @@ -332,7 +333,7 @@ func (c *Oasys) initializeSystemContracts( // Transact the `StakeManager.slash` method. func (c *Oasys) slash( validator common.Address, - schedule map[uint64]common.Address, + schedules []*common.Address, state *state.StateDB, header *types.Header, cx core.ChainContext, @@ -343,8 +344,8 @@ func (c *Oasys) slash( mining bool, ) error { blocks := int64(0) - for _, address := range schedule { - if address == validator { + for _, address := range schedules { + if *address == validator { blocks++ } } diff --git a/consensus/oasys/contract_test.go b/consensus/oasys/contract_test.go index 803a36ee3..5b20500e3 100644 --- a/consensus/oasys/contract_test.go +++ b/consensus/oasys/contract_test.go @@ -107,7 +107,7 @@ func TestSlash(t *testing.T) { } validator := accounts[0].Address - schedule := map[uint64]common.Address{} + schedule := []*common.Address{} header := &types.Header{ Number: big.NewInt(50), Coinbase: accounts[0].Address, diff --git a/consensus/oasys/oasys.go b/consensus/oasys/oasys.go index 08bc4963f..e5e1ebef5 100644 --- a/consensus/oasys/oasys.go +++ b/consensus/oasys/oasys.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "math/big" - "math/rand" "sort" "sync" "time" @@ -50,7 +49,8 @@ var ( diffInTurn = big.NewInt(2) // Block difficulty for in-turn signatures diffNoTurn = big.NewInt(1) // Block difficulty for out-of-turn signatures - ether = big.NewInt(1_000_000_000_000_000_000) + ether = big.NewInt(1_000_000_000_000_000_000) + totalSupply = new(big.Int).Mul(big.NewInt(10_000_000_000), ether) // From WhitePaper ) // Various error messages to mark blocks invalid. These should be private to @@ -113,10 +113,21 @@ var ( errCoinBaseMisMatch = errors.New("coinbase do not match with signature") ) +var ( + // The key is the hash value of the final block from the previous epoch. + schedulerCache *lru.Cache +) + // SignerFn hashes and signs the data to be signed by a backing account. type SignerFn func(signer accounts.Account, mimeType string, message []byte) ([]byte, error) type TxSignerFn func(accounts.Account, *types.Transaction, *big.Int) (*types.Transaction, error) +func init() { + // The capacity should be greater than or equal to the + // maximum batch verification size divided by the epoch period. + schedulerCache, _ = lru.New(32) +} + // ecrecover extracts the Ethereum account address from a signed header. func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) { // If the signature's already cached, return that @@ -209,6 +220,7 @@ func (c *Oasys) VerifyHeaders(chain consensus.ChainHeaderReader, headers []*type go func() { for i, header := range headers { + uncommittedHashes.Add(header.Hash(), header.ParentHash) err := c.verifyHeader(chain, header, headers[:i]) select { @@ -264,10 +276,8 @@ func (c *Oasys) verifyHeader(chain consensus.ChainHeaderReader, header *types.He return errInvalidUncleHash } // Ensure that the block's difficulty is meaningful (may not be correct at this point) - if number > 0 { - if header.Difficulty == nil || (header.Difficulty.Cmp(diffInTurn) != 0 && header.Difficulty.Cmp(diffNoTurn) != 0) { - return errInvalidDifficulty - } + if number > 0 && header.Difficulty == nil { + return errInvalidDifficulty } // Verify that the gas limit is <= 2^63-1 if header.GasLimit > params.MaxGasLimit { @@ -278,14 +288,14 @@ func (c *Oasys) verifyHeader(chain consensus.ChainHeaderReader, header *types.He return err } // All basic checks passed, verify cascading fields - return c.verifyCascadingFields(chain, header, parents) + return c.verifyCascadingFields(chain, header, parents, env) } // verifyCascadingFields verifies all the header fields that are not standalone, // rather depend on a batch of previous headers. The caller may optionally pass // in a batch of parents (ascending order) to avoid looking those up from the // database. This is useful for concurrently verifying a batch of new headers. -func (c *Oasys) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { +func (c *Oasys) verifyCascadingFields(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, env *environmentValue) error { // The genesis block is the always valid dead-end number := header.Number.Uint64() if number == 0 { @@ -302,27 +312,32 @@ func (c *Oasys) verifyCascadingFields(chain consensus.ChainHeaderReader, header if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash { return consensus.ErrUnknownAncestor } - env, err := c.environment(chain, header, parents) - if err != nil { - return err - } - var backoff uint64 + + var ( + validators []common.Address + stakes []*big.Int + ) if number > 0 && env.IsEpoch(number) { result, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) if err != nil { log.Error("Failed to get validators", "in", "verifyCascadingFields", "hash", header.ParentHash, "number", number, "err", err) return err } - backoff = c.backOffTime(chain, result, env, number, header.Coinbase) + validators, stakes = result.Operators, result.Stakes } else { // Retrieve the snapshot needed to verify this header and cache it snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) if err != nil { return err } - backoff = snap.backOffTime(chain, env, number, header.Coinbase) + validators, stakes = snap.validatorsToTuple() + } + scheduler, err := c.scheduler(chain, header, env, validators, stakes) + if err != nil { + log.Error("Failed to get scheduler", "in", "verifyCascadingFields", "number", number, "err", err) + return err } - if header.Time < parent.Time+env.BlockPeriod.Uint64()+backoff { + if header.Time < parent.Time+env.BlockPeriod.Uint64()+scheduler.backOffTime(number, header.Coinbase) { return consensus.ErrFutureBlock } @@ -344,7 +359,7 @@ func (c *Oasys) verifyCascadingFields(chain consensus.ChainHeaderReader, header } // All basic checks passed, verify the seal and return - return c.verifySeal(chain, header, parents) + return c.verifySeal(chain, header, parents, scheduler) } // snapshot retrieves the authorization snapshot at a given point in time. @@ -447,7 +462,7 @@ func (c *Oasys) VerifyUncles(chain consensus.ChainReader, block *types.Block) er // consensus protocol requirements. The method accepts an optional list of parent // headers that aren't yet part of the local blockchain to generate the snapshots // from. -func (c *Oasys) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) error { +func (c *Oasys) verifySeal(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header, scheduler *scheduler) error { // Verifying the genesis block is not supported number := header.Number.Uint64() if number == 0 { @@ -462,42 +477,14 @@ func (c *Oasys) verifySeal(chain consensus.ChainHeaderReader, header *types.Head if validator != header.Coinbase { return errCoinBaseMisMatch } - - env, err := c.environment(chain, header, parents) - if err != nil { - return err - } - var ( - exists bool - schedule map[uint64]common.Address - ) - if number > 0 && env.IsEpoch(number) { - result, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) - if err != nil { - log.Error("Failed to get validators", "in", "verifySeal", "hash", header.ParentHash, "number", number, "err", err) - return err - } - exists = result.Exists(validator) - schedule = c.getValidatorSchedule(chain, result, env, number) - } else { - snap, err := c.snapshot(chain, number-1, header.ParentHash, parents) - if err != nil { - return err - } - exists = snap.exists(validator) - schedule = snap.getValidatorSchedule(chain, env, number) - } - if !exists { + if !scheduler.exists(validator) { return errUnauthorizedValidator } // Ensure that the difficulty corresponds to the turn-ness of the validator if !c.fakeDiff { - inturn := schedule[number] == validator - if inturn && header.Difficulty.Cmp(diffInTurn) != 0 { - return errWrongDifficulty - } - if !inturn && header.Difficulty.Cmp(diffNoTurn) != 0 { + difficulty := scheduler.difficulty(number, validator, c.chainConfig.IsForkedOasysExtendDifficulty(header.Number)) + if header.Difficulty.Cmp(difficulty) != 0 { return errWrongDifficulty } } @@ -526,8 +513,8 @@ func (c *Oasys) Prepare(chain consensus.ChainHeaderReader, header *types.Header) header.Extra = header.Extra[:extraVanity] var ( - backoff uint64 - schedule map[uint64]common.Address + validators []common.Address + stakes []*big.Int ) if number > 0 && env.IsEpoch(number) { result, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) @@ -535,34 +522,33 @@ func (c *Oasys) Prepare(chain consensus.ChainHeaderReader, header *types.Header) log.Error("Failed to get validators", "in", "Prepare", "hash", header.ParentHash, "number", number, "err", err) return err } - - header.Extra = append(header.Extra, c.getExtraHeaderValueInEpoch(header.Number, result.Operators)...) - backoff = c.backOffTime(chain, result, env, number, c.signer) - schedule = c.getValidatorSchedule(chain, result, env, number) + validators, stakes = result.Operators, result.Stakes + header.Extra = append(header.Extra, c.getExtraHeaderValueInEpoch(header.Number, validators)...) } else { snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { return err } - backoff = snap.backOffTime(chain, env, number, c.signer) - schedule = snap.getValidatorSchedule(chain, env, number) + validators, stakes = snap.validatorsToTuple() + } + scheduler, err := c.scheduler(chain, header, env, validators, stakes) + if err != nil { + log.Error("Failed to get scheduler", "in", "Prepare", "number", number, "err", err) + return err } // Add extra seal header.Extra = append(header.Extra, make([]byte, extraSeal)...) - if schedule[number] == c.signer { - header.Difficulty = new(big.Int).Set(diffInTurn) - } else { - header.Difficulty = new(big.Int).Set(diffNoTurn) - } + // Add the difficulty + header.Difficulty = scheduler.difficulty(number, c.signer, c.chainConfig.IsForkedOasysExtendDifficulty(header.Number)) // Ensure the timestamp has the correct delay parent := chain.GetHeader(header.ParentHash, number-1) if parent == nil { return consensus.ErrUnknownAncestor } - header.Time = parent.Time + env.BlockPeriod.Uint64() + backoff + header.Time = parent.Time + env.BlockPeriod.Uint64() + scheduler.backOffTime(number, c.signer) if header.Time < uint64(time.Now().Unix()) { header.Time = uint64(time.Now().Unix()) } @@ -578,9 +564,6 @@ func (c *Oasys) Finalize(chain consensus.ChainHeaderReader, header *types.Header return err } - header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) - header.UncleHash = types.CalcUncleHash(nil) - hash := header.Hash() number := header.Number.Uint64() @@ -600,23 +583,27 @@ func (c *Oasys) Finalize(chain consensus.ChainHeaderReader, header *types.Header } var ( - isEpoch = env.IsEpoch(number) - schedule map[uint64]common.Address - nextValidators *nextValidators + validators []common.Address + stakes []*big.Int ) - if isEpoch { - nextValidators, err = getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) + if env.IsEpoch(number) { + result, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) if err != nil { log.Error("Failed to get validators", "in", "Finalize", "hash", header.ParentHash, "number", number, "err", err) return err } - schedule = c.getValidatorSchedule(chain, nextValidators, env, number) + validators, stakes = result.Operators, result.Stakes } else { snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { return err } - schedule = snap.getValidatorSchedule(chain, env, number) + validators, stakes = snap.validatorsToTuple() + } + scheduler, err := c.scheduler(chain, header, env, validators, stakes) + if err != nil { + log.Error("Failed to get scheduler", "in", "Finalize", "number", number, "err", err) + return err } if err := c.addBalanceToStakeManager(state, header.ParentHash, number, env); err != nil { @@ -624,23 +611,22 @@ func (c *Oasys) Finalize(chain consensus.ChainHeaderReader, header *types.Header return err } - if isEpoch { + if env.IsEpoch(number) { // If the block is a epoch block, verify the validator list or hash actual := header.Extra[extraVanity : len(header.Extra)-extraSeal] - if err := c.verifyExtraHeaderValueInEpoch(header.Number, actual, nextValidators.Operators); err != nil { + if err := c.verifyExtraHeaderValueInEpoch(header.Number, actual, validators); err != nil { return err } } - if number >= c.config.Epoch && header.Difficulty.Cmp(diffInTurn) != 0 { + if number >= c.config.Epoch { validator, err := ecrecover(header, c.signatures) if err != nil { return err } - expectedValidator := schedule[number] - if validator != expectedValidator { - if err := c.slash(expectedValidator, schedule, state, header, cx, txs, receipts, systemTxs, usedGas, false); err != nil { - log.Error("Failed to slash validator", "in", "Finalize", "hash", hash, "number", number, "address", expectedValidator, "err", err) + if expected := *scheduler.expect(number); expected != validator { + if err := c.slash(expected, scheduler.schedules(), state, header, cx, txs, receipts, systemTxs, usedGas, false); err != nil { + log.Error("Failed to slash validator", "in", "Finalize", "hash", hash, "number", number, "address", expected, "err", err) } } } @@ -683,20 +669,28 @@ func (c *Oasys) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *t return nil, nil, err } - var schedule map[uint64]common.Address + var ( + validators []common.Address + stakes []*big.Int + ) if env.IsEpoch(number) { - nextValidators, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) + result, err := getNextValidators(c.chainConfig, c.ethAPI, header.ParentHash, env.Epoch(number), number) if err != nil { log.Error("Failed to get validators", "in", "FinalizeAndAssemble", "hash", header.ParentHash, "number", number, "err", err) return nil, nil, err } - schedule = c.getValidatorSchedule(chain, nextValidators, env, number) + validators, stakes = result.Operators, result.Stakes } else { snap, err := c.snapshot(chain, number-1, header.ParentHash, nil) if err != nil { return nil, nil, err } - schedule = snap.getValidatorSchedule(chain, env, number) + validators, stakes = snap.validatorsToTuple() + } + scheduler, err := c.scheduler(chain, header, env, validators, stakes) + if err != nil { + log.Error("Failed to get scheduler", "in", "FinalizeAndAssemble", "number", number, "err", err) + return nil, nil, err } if err := c.addBalanceToStakeManager(state, header.ParentHash, number, env); err != nil { @@ -704,11 +698,10 @@ func (c *Oasys) FinalizeAndAssemble(chain consensus.ChainHeaderReader, header *t return nil, nil, err } - if number >= c.config.Epoch && header.Difficulty.Cmp(diffInTurn) != 0 { - expectedValidator := schedule[number] - if header.Coinbase != expectedValidator { - if err := c.slash(expectedValidator, schedule, state, header, cx, &txs, &receipts, nil, &header.GasUsed, true); err != nil { - log.Error("Failed to slash validator", "in", "FinalizeAndAssemble", "hash", hash, "number", number, "address", expectedValidator, "err", err) + if number >= c.config.Epoch { + if expected := *scheduler.expect(number); expected != header.Coinbase { + if err := c.slash(expected, scheduler.schedules(), state, header, cx, &txs, &receipts, nil, &header.GasUsed, true); err != nil { + log.Error("Failed to slash validator", "in", "FinalizeAndAssemble", "hash", hash, "number", number, "address", expected, "err", err) } } } @@ -815,26 +808,31 @@ func (c *Oasys) CalcDifficulty(chain consensus.ChainHeaderReader, time uint64, p return nil } - var schedule map[uint64]common.Address + var ( + validators []common.Address + stakes []*big.Int + ) if env.IsEpoch(number) { result, err := getNextValidators(c.chainConfig, c.ethAPI, parent.Hash(), env.Epoch(number), number) if err != nil { log.Error("Failed to get validators", "in", "Seal", "hash", parent.Hash(), "number", number, "err", err) return nil } - schedule = c.getValidatorSchedule(chain, result, env, number) + validators, stakes = result.Operators, result.Stakes } else { snap, err := c.snapshot(chain, number, parent.Hash(), nil) if err != nil { return nil } - schedule = snap.getValidatorSchedule(chain, env, number) + validators, stakes = snap.validatorsToTuple() } - - if schedule[number] == c.signer { - return new(big.Int).Set(diffInTurn) + scheduler, err := c.scheduler(chain, parent, env, validators, stakes) + if err != nil { + log.Error("Failed to get scheduler", "in", "CalcDifficulty", "number", number, "err", err) + return nil } - return new(big.Int).Set(diffNoTurn) + + return scheduler.difficulty(number, c.signer, c.chainConfig.IsForkedOasysExtendDifficulty(parent.Number)) } // SealHash returns the hash of a block prior to it being sealed. @@ -975,18 +973,6 @@ func (c *Oasys) addBalanceToStakeManager(state *state.StateDB, hash common.Hash, return nil } -func (c *Oasys) getValidatorSchedule(chain consensus.ChainHeaderReader, result *nextValidators, env *environmentValue, number uint64) map[uint64]common.Address { - return getValidatorSchedule(chain, result.Operators, result.Stakes, env, number) -} - -func (c *Oasys) backOffTime(chain consensus.ChainHeaderReader, result *nextValidators, - env *environmentValue, number uint64, validator common.Address) uint64 { - if !result.Exists(validator) { - return 0 - } - return backOffTime(chain, result.Operators, result.Stakes, env, number, validator) -} - func (c *Oasys) environment(chain consensus.ChainHeaderReader, header *types.Header, parents []*types.Header) (*environmentValue, error) { number := header.Number.Uint64() if number < c.config.Epoch { @@ -998,166 +984,65 @@ func (c *Oasys) environment(chain consensus.ChainHeaderReader, header *types.Hea return nil, err } + var env *environmentValue if number%snap.Environment.EpochPeriod.Uint64() == 0 { - nextEnv, err := getNextEnvironmentValue(c.ethAPI, header.ParentHash) - if err != nil { + if env, err = getNextEnvironmentValue(c.ethAPI, header.ParentHash); err != nil { log.Error("Failed to get environment value", "in", "environment", "hash", header.ParentHash, "number", number, "err", err) return nil, err } - return nextEnv, nil - } - - return snap.Environment, nil -} - -// Oasys transaction verification -func (c *Oasys) verifyTx(header *types.Header, txs []*types.Transaction) error { - for _, tx := range txs { - if from, err := types.Sender(c.txSigner, tx); err != nil { - return err - } else if err = core.VerifyTx(tx, from); err != nil { - return err - } + } else { + env = snap.Environment } - return nil -} - -type validatorAndValue struct { - validator common.Address - value *big.Int -} - -type validatorsAndValuesAscending []*validatorAndValue -func (s validatorsAndValuesAscending) Len() int { return len(s) } -func (s validatorsAndValuesAscending) Less(i, j int) bool { - if s[i].value.Cmp(s[j].value) == 0 { - return bytes.Compare(s[i].validator[:], s[j].validator[:]) < 0 + if env.BlockPeriod.Cmp(common.Big0) == 0 { + return nil, errors.New("invalid block period") } - return s[i].value.Cmp(s[j].value) < 0 -} -func (s validatorsAndValuesAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func sortValidatorsAndValues(validators []common.Address, values []*big.Int) ([]common.Address, []*big.Int) { - choices := make([]*validatorAndValue, len(validators)) - for i, validator := range validators { - choices[i] = &validatorAndValue{validator, values[i]} + if env.EpochPeriod.Cmp(common.Big0) == 0 { + return nil, errors.New("invalid epoch period") } - sort.Sort(validatorsAndValuesAscending(choices)) - - rvalidators := make([]common.Address, len(choices)) - rvalues := make([]*big.Int, len(choices)) - for i, c := range choices { - rvalidators[i] = c.validator - rvalues[i] = new(big.Int).Set(c.value) + if env.ValidatorThreshold.Cmp(common.Big0) == 0 { + return nil, errors.New("invalid validator threshold") } - return rvalidators, rvalues -} -type weightedRandomChooser struct { - random *rand.Rand - validators []common.Address - totals []int - max int + return env, nil } -func (c *weightedRandomChooser) choice() common.Address { - if (c.max) == 0 { - i := rand.Intn(len(c.validators)) - return c.validators[i] - } - - x := c.randInt() - i := 0 - j := len(c.totals) +func (c *Oasys) scheduler(chain consensus.ChainHeaderReader, header *types.Header, + env *environmentValue, validators []common.Address, stakes []*big.Int) (*scheduler, error) { + number := header.Number.Uint64() - for i < j { - h := (i + j) >> 1 - if c.totals[h] < x { - i = h + 1 - } else { - j = h - } + // Previous epoch does not exists. + if number < c.config.Epoch { + return newScheduler(env, 0, newWeightedChooser(validators, stakes, 0)), nil } - return c.validators[i] -} - -func (c *weightedRandomChooser) randInt() int { - if (c.max) == 0 { - return 0 + // After the second epoch, the hash of the last block + // of the previous epoch is used as the random seed. + seedHash, err := getPrevEpochLastBlockHash(c.config, chain, env, header) + if err != nil { + return nil, err + } else if seedHash == emptyHash { + return nil, errors.New("invalid seed hash") } - return c.random.Intn(c.max) + 1 -} - -func (c *weightedRandomChooser) skip() { - c.randInt() -} -func newWeightedRandomChooser( - chain consensus.ChainHeaderReader, - validators []common.Address, - stakes []*big.Int, - env *environmentValue, - number uint64, -) *weightedRandomChooser { - start := env.GetFirstBlock(number) - seed := int64(start) - if start > 0 { - if header := chain.GetHeaderByNumber(start - 1); header != nil { - seed = header.Hash().Big().Int64() - } + if cache, ok := schedulerCache.Get(seedHash); ok { + return cache.(*scheduler), nil } - validators, stakes = sortValidatorsAndValues(validators, stakes) - chooser := &weightedRandomChooser{ - random: rand.New(rand.NewSource(seed)), - validators: make([]common.Address, len(validators)), - totals: make([]int, len(stakes)), - max: 0, - } - for i, validator := range validators { - chooser.validators[i] = validator - chooser.max += int(new(big.Int).Div(stakes[i], ether).Int64()) - chooser.totals[i] = chooser.max - } - return chooser + created := newScheduler(env, env.GetFirstBlock(number), + newWeightedChooser(validators, stakes, seedHash.Big().Int64())) + schedulerCache.Add(seedHash, created) + return created, nil } -func getValidatorSchedule(chain consensus.ChainHeaderReader, validators []common.Address, stakes []*big.Int, env *environmentValue, number uint64) map[uint64]common.Address { - start := env.GetFirstBlock(number) - chooser := newWeightedRandomChooser(chain, validators, stakes, env, number) - epochPeriod := env.EpochPeriod.Uint64() - ret := make(map[uint64]common.Address) - for i := uint64(0); i < epochPeriod; i++ { - ret[start+i] = chooser.choice() - } - return ret -} - -func backOffTime(chain consensus.ChainHeaderReader, validators []common.Address, stakes []*big.Int, env *environmentValue, number uint64, validator common.Address) uint64 { - start := env.GetFirstBlock(number) - chooser := newWeightedRandomChooser(chain, validators, stakes, env, number) - for i := number - start; i > 0; i-- { - chooser.skip() - } - - turn := 0 - prevs := make(map[common.Address]bool) - for { - picked := chooser.choice() - if picked == validator { - break - } - if prevs[picked] { - continue +// Oasys transaction verification +func (c *Oasys) verifyTx(header *types.Header, txs []*types.Transaction) error { + for _, tx := range txs { + if from, err := types.Sender(c.txSigner, tx); err != nil { + return err + } else if err = core.VerifyTx(tx, from); err != nil { + return err } - prevs[picked] = true - turn++ } - - if turn == 0 { - return 0 - } - return uint64(turn) + backoffWiggleTime + return nil } diff --git a/consensus/oasys/oasys_test.go b/consensus/oasys/oasys_test.go deleted file mode 100644 index dcd72ef78..000000000 --- a/consensus/oasys/oasys_test.go +++ /dev/null @@ -1,264 +0,0 @@ -package oasys - -import ( - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" -) - -var ( - epochPeriod = big.NewInt(40) - - validators = []common.Address{ - common.HexToAddress("0xd0887E868eCd4b16B75c60595DD0a7bA21Dbc0E9"), - common.HexToAddress("0xEFb5BCC9b58fDaBEc50d3bFb5838f8FBf65B5Bba"), - common.HexToAddress("0xd96FF3bfe34AF3680806d25cbfc1e138924f0Af2"), - common.HexToAddress("0xa0BE9Fa71e130df9B033F24AF4b8cD944976081a"), - } - stakes = []*big.Int{ - new(big.Int).Mul(big.NewInt(10_000_000), ether), - new(big.Int).Mul(big.NewInt(10_000_000), ether), - new(big.Int).Mul(big.NewInt(10_000_000), ether), - new(big.Int).Mul(big.NewInt(10_000_000), ether), - } - - names = map[common.Address]string{ - validators[0]: "validator-0", - validators[1]: "validator-1", - validators[2]: "validator-2", - validators[3]: "validator-3", - } -) - -func TestAddBalanceToStakeManager(t *testing.T) {} - -func TestBackOffTime(t *testing.T) { - testCases := []struct { - block uint64 - want []uint64 - }{ - // epoch 1 - {40, []uint64{2, 4, 3, 0}}, - {41, []uint64{0, 4, 2, 3}}, - {42, []uint64{3, 4, 0, 2}}, - {43, []uint64{2, 4, 3, 0}}, - {44, []uint64{0, 4, 2, 3}}, - {45, []uint64{0, 4, 2, 3}}, - {46, []uint64{2, 4, 0, 3}}, - {47, []uint64{0, 3, 4, 2}}, - {48, []uint64{0, 3, 4, 2}}, - {49, []uint64{2, 3, 4, 0}}, - {50, []uint64{0, 2, 3, 4}}, - {51, []uint64{3, 0, 2, 4}}, - {52, []uint64{3, 0, 2, 4}}, - {53, []uint64{3, 0, 2, 4}}, - {54, []uint64{2, 4, 0, 3}}, - {55, []uint64{0, 4, 3, 2}}, - {56, []uint64{2, 4, 3, 0}}, - {57, []uint64{0, 4, 3, 2}}, - {58, []uint64{0, 4, 3, 2}}, - {59, []uint64{2, 4, 3, 0}}, - {60, []uint64{2, 4, 3, 0}}, - {61, []uint64{2, 4, 3, 0}}, - {62, []uint64{0, 4, 3, 2}}, - {63, []uint64{3, 4, 2, 0}}, - {64, []uint64{2, 4, 0, 3}}, - {65, []uint64{0, 3, 4, 2}}, - {66, []uint64{0, 3, 4, 2}}, - {67, []uint64{4, 2, 3, 0}}, - {68, []uint64{3, 0, 2, 4}}, - {69, []uint64{3, 0, 2, 4}}, - {70, []uint64{2, 4, 0, 3}}, - {71, []uint64{0, 4, 2, 3}}, - {72, []uint64{4, 3, 0, 2}}, - {73, []uint64{4, 3, 2, 0}}, - {74, []uint64{4, 3, 0, 2}}, - {75, []uint64{4, 3, 2, 0}}, - {76, []uint64{4, 2, 0, 3}}, - {77, []uint64{4, 0, 3, 2}}, - {78, []uint64{4, 0, 3, 2}}, - {79, []uint64{4, 3, 2, 0}}, - // epoch 2 - {80, []uint64{0, 4, 2, 3}}, - {81, []uint64{0, 4, 2, 3}}, - {82, []uint64{2, 4, 0, 3}}, - {83, []uint64{2, 4, 0, 3}}, - {84, []uint64{0, 4, 2, 3}}, - {85, []uint64{3, 4, 0, 2}}, - {86, []uint64{3, 4, 2, 0}}, - {87, []uint64{3, 4, 0, 2}}, - {88, []uint64{2, 4, 3, 0}}, - {89, []uint64{0, 3, 2, 4}}, - {90, []uint64{3, 2, 0, 4}}, - {91, []uint64{2, 0, 4, 3}}, - {92, []uint64{0, 2, 4, 3}}, - {93, []uint64{2, 0, 4, 3}}, - {94, []uint64{0, 4, 3, 2}}, - {95, []uint64{0, 4, 3, 2}}, - {96, []uint64{4, 3, 2, 0}}, - {97, []uint64{4, 3, 0, 2}}, - {98, []uint64{4, 3, 0, 2}}, - {99, []uint64{4, 3, 0, 2}}, - {100, []uint64{4, 2, 3, 0}}, - {101, []uint64{3, 0, 2, 4}}, - {102, []uint64{3, 0, 2, 4}}, - {103, []uint64{2, 3, 0, 4}}, - {104, []uint64{0, 3, 2, 4}}, - {105, []uint64{4, 2, 0, 3}}, - {106, []uint64{4, 0, 2, 3}}, - {107, []uint64{4, 0, 2, 3}}, - {108, []uint64{4, 2, 0, 3}}, - {109, []uint64{4, 2, 0, 3}}, - {110, []uint64{3, 0, 4, 2}}, - {111, []uint64{3, 0, 4, 2}}, - {112, []uint64{2, 4, 3, 0}}, - {113, []uint64{0, 3, 2, 4}}, - {114, []uint64{4, 2, 0, 3}}, - {115, []uint64{4, 2, 0, 3}}, - {116, []uint64{3, 0, 4, 2}}, - {117, []uint64{2, 3, 4, 0}}, - {118, []uint64{0, 3, 4, 2}}, - {119, []uint64{2, 3, 4, 0}}, - } - - wallets, accounts, err := makeWallets(1) - if err != nil { - t.Fatalf("failed to create test wallets: %v", err) - } - - env, err := makeEnv(*wallets[0], *accounts[0]) - if err != nil { - t.Fatalf("failed to create test env: %v", err) - } - - envValue := &environmentValue{ - StartBlock: common.Big0, - StartEpoch: common.Big1, - EpochPeriod: epochPeriod, - } - - for _, tc := range testCases { - for i, want := range tc.want { - validator := validators[i] - backoff := backOffTime(env.chain, validators, stakes, envValue, tc.block, validator) - if backoff != want { - t.Errorf("backoff mismatch, block %v, validator %v, got %v, want %v", tc.block, names[validator], backoff, want) - } - } - } -} - -func TestGetValidatorSchedule(t *testing.T) { - testCases := []struct { - block uint64 - want string - }{ - // epoch 1 - {40, "validator-3"}, - {41, "validator-0"}, - {42, "validator-2"}, - {43, "validator-3"}, - {44, "validator-0"}, - {45, "validator-0"}, - {46, "validator-2"}, - {47, "validator-0"}, - {48, "validator-0"}, - {49, "validator-3"}, - {50, "validator-0"}, - {51, "validator-1"}, - {52, "validator-1"}, - {53, "validator-1"}, - {54, "validator-2"}, - {55, "validator-0"}, - {56, "validator-3"}, - {57, "validator-0"}, - {58, "validator-0"}, - {59, "validator-3"}, - {60, "validator-3"}, - {61, "validator-3"}, - {62, "validator-0"}, - {63, "validator-3"}, - {64, "validator-2"}, - {65, "validator-0"}, - {66, "validator-0"}, - {67, "validator-3"}, - {68, "validator-1"}, - {69, "validator-1"}, - {70, "validator-2"}, - {71, "validator-0"}, - {72, "validator-2"}, - {73, "validator-3"}, - {74, "validator-2"}, - {75, "validator-3"}, - {76, "validator-2"}, - {77, "validator-1"}, - {78, "validator-1"}, - {79, "validator-3"}, - // epoch 2 - {80, "validator-0"}, - {81, "validator-0"}, - {82, "validator-2"}, - {83, "validator-2"}, - {84, "validator-0"}, - {85, "validator-2"}, - {86, "validator-3"}, - {87, "validator-2"}, - {88, "validator-3"}, - {89, "validator-0"}, - {90, "validator-2"}, - {91, "validator-1"}, - {92, "validator-0"}, - {93, "validator-1"}, - {94, "validator-0"}, - {95, "validator-0"}, - {96, "validator-3"}, - {97, "validator-2"}, - {98, "validator-2"}, - {99, "validator-2"}, - {100, "validator-3"}, - {101, "validator-1"}, - {102, "validator-1"}, - {103, "validator-2"}, - {104, "validator-0"}, - {105, "validator-2"}, - {106, "validator-1"}, - {107, "validator-1"}, - {108, "validator-2"}, - {109, "validator-2"}, - {110, "validator-1"}, - {111, "validator-1"}, - {112, "validator-3"}, - {113, "validator-0"}, - {114, "validator-2"}, - {115, "validator-2"}, - {116, "validator-1"}, - {117, "validator-3"}, - {118, "validator-0"}, - {119, "validator-3"}, - } - - wallets, accounts, err := makeWallets(1) - if err != nil { - t.Fatalf("failed to create test wallets: %v", err) - } - - env, err := makeEnv(*wallets[0], *accounts[0]) - if err != nil { - t.Fatalf("failed to create test env: %v", err) - } - - envValue := &environmentValue{ - StartBlock: common.Big0, - StartEpoch: common.Big1, - EpochPeriod: epochPeriod, - } - - for _, tc := range testCases { - schedule := getValidatorSchedule(env.chain, validators, stakes, envValue, tc.block) - got := names[schedule[tc.block]] - if got != tc.want { - t.Errorf("validator mismatch, block %v, got %v, want %v", tc.block, got, tc.want) - } - } -} diff --git a/consensus/oasys/scheduler.go b/consensus/oasys/scheduler.go new file mode 100644 index 000000000..830a5550b --- /dev/null +++ b/consensus/oasys/scheduler.go @@ -0,0 +1,303 @@ +package oasys + +import ( + "bytes" + "errors" + "fmt" + "math/big" + "math/rand" + "sort" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + lru "github.com/hashicorp/golang-lru" +) + +var ( + emptyHash common.Hash + + // During batch validation, since the parent header does + // not exist in the database, it is temporarily stored here. + uncommittedHashes *lru.Cache + + // Cache the hash value of the final block of the + // previous epoch for a block with a certain ParentHash. + lastBlockHashes *lru.Cache +) + +func init() { + // Set the capacity equal to the "blockCacheMaxItems" in the "eth/downloader" package. + // WARNING: The capacity must not be smaller than the maximum size of the batch verification. + uncommittedHashes, _ = lru.New(8192) + + // Set the capacity equal to the maximum number of validators. + // (That is equal to the maximum number of fork chains.) + lastBlockHashes, _ = lru.New(1000) +} + +type scheduler struct { + mu sync.Mutex + env *environmentValue + + // WARNING: Consensus engine should not use this value directly. + chooser *weightedChooser + + // Mapping the real address to the pointer address of "chooser.validators". + ptrmap map[common.Address]*common.Address + + // Validator schedule indexed by block position in the epoch. + // WARNING: Consensus engine should not use this value directly. + choices []*common.Address + + // Validator order indexed by block position in the epoch. + // WARNING: Consensus engine should not use this value directly. + turns *lru.Cache + + // Example: period=1000, epoch=2, validators=[A,B,C] + // Index 0: block=1000 choices[0]=A turns[0]=[A,B,C] + // Index 1: block=1001 choices[1]=B turns[1]=[B,C,A] + // Index 2: block=1002 choices[2]=C turns[2]=[C,A,B] + // Index 3: block=1003 choices[3]=D turns[3]=[A,B,C] + // Note: The turn is random, so it will not always be [A,B,C]. +} + +func newScheduler(env *environmentValue, epochStart uint64, chooser *weightedChooser) *scheduler { + period := env.EpochPeriod.Uint64() + s := &scheduler{ + env: env, + chooser: chooser, + ptrmap: map[common.Address]*common.Address{}, + choices: make([]*common.Address, period), + } + s.turns, _ = lru.New(32) + + for i, addr := range s.chooser.validators { + s.ptrmap[addr] = &s.chooser.validators[i] + } + + for bpos := uint64(0); bpos < period; bpos++ { + s.choices[bpos] = s.ptrmap[s.chooser.random()] + } + return s +} + +func (s *scheduler) exists(validator common.Address) bool { + return s.ptrmap[validator] != nil +} + +func (s *scheduler) schedules() []*common.Address { + return s.choices[:s.env.EpochPeriod.Uint64()] +} + +func (s *scheduler) expect(number uint64) *common.Address { + return s.schedules()[number-s.env.GetFirstBlock(number)] +} + +func (s *scheduler) difficulty(number uint64, validator common.Address, ext bool) *big.Int { + if !ext { + if *s.expect(number) == validator { + return new(big.Int).Set(diffInTurn) + } + return new(big.Int).Set(diffNoTurn) + } + + turn, err := s.turn(number, validator) + if err != nil { + return new(big.Int).Set(diffNoTurn) + } + + // Use the maximum number of validators as the minimum value of difficulty. + // Also, the higher the priority of the validator in the block, the larger the difficulty. + // In other words, during a reorganization, fork chains that closely resemble + // the calculated validator schedule are more likely to be adopted as the main chain. + minDiff := new(big.Int).Div(totalSupply, s.env.ValidatorThreshold) + priority := new(big.Int).SetUint64(uint64(len(s.chooser.validators)) - turn) + return new(big.Int).Mul(minDiff, priority) +} + +func (s *scheduler) turn(number uint64, validator common.Address) (uint64, error) { + if !s.exists(validator) { + return 0, errUnauthorizedValidator + } + + s.mu.Lock() + defer s.mu.Unlock() + + turns := map[*common.Address]uint64{} + if cache, ok := s.turns.Get(number); ok { + turns = cache.(map[*common.Address]uint64) + } else { + s.turns.Add(number, turns) + } + + for bpos := number - s.env.GetFirstBlock(number); ; bpos++ { + if turn, ok := turns[s.ptrmap[validator]]; ok { + return turn, nil + } + if bpos >= uint64(len(s.choices)) { + // Out of the first calculated schedule. + s.choices = append(s.choices, s.ptrmap[s.chooser.random()]) + } + if _, ok := turns[s.choices[bpos]]; !ok { + turns[s.choices[bpos]] = uint64(len(turns)) + } + } +} + +func (s *scheduler) backOffTime(number uint64, validator common.Address) uint64 { + turn, err := s.turn(number, validator) + if errors.Is(err, errUnauthorizedValidator) || turn == 0 { + return 0 + } + return turn + backoffWiggleTime +} + +type validatorAndStake struct { + validator common.Address + stake *big.Int +} + +type validatorsAndValuesAscending []*validatorAndStake + +func (s validatorsAndValuesAscending) Len() int { return len(s) } +func (s validatorsAndValuesAscending) Less(i, j int) bool { + if s[i].stake.Cmp(s[j].stake) == 0 { + return bytes.Compare(s[i].validator[:], s[j].validator[:]) < 0 + } + return s[i].stake.Cmp(s[j].stake) < 0 +} +func (s validatorsAndValuesAscending) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func sortValidatorsAndValues( + validators []common.Address, + stakes []*big.Int, +) ([]common.Address, []*big.Int) { + choices := make([]*validatorAndStake, len(validators)) + for i, validator := range validators { + choices[i] = &validatorAndStake{validator, stakes[i]} + } + sort.Sort(validatorsAndValuesAscending(choices)) + + rvalidators := make([]common.Address, len(choices)) + rvalues := make([]*big.Int, len(choices)) + for i, c := range choices { + rvalidators[i] = c.validator + rvalues[i] = new(big.Int).Set(c.stake) + } + return rvalidators, rvalues +} + +type weightedChooser struct { + mu sync.Mutex + rnd *rand.Rand + validators []common.Address + totals []int + max int +} + +// Return a validator to the scheduler at weighted random based on stake amount. +// First, at the time a new Scheduler is created, it is called +// for the number of blocks in an epoch to determine the validator schedule. +// Next, when the "scheduler.turn()" method is called to determine the validation +// order in a particular block of a certain validator, if it is outside the first +// calculated schedule, it will be called repeatedly until it is found. +func (c *weightedChooser) random() common.Address { + if (c.max) == 0 { + i := rand.Intn(len(c.validators)) + return c.validators[i] + } + + x := c.randInt() + i := 0 + j := len(c.totals) + + for i < j { + h := (i + j) >> 1 + if c.totals[h] < x { + i = h + 1 + } else { + j = h + } + } + + return c.validators[i] +} + +func (c *weightedChooser) randInt() int { + c.mu.Lock() + defer c.mu.Unlock() + + if (c.max) == 0 { + return 0 + } + return c.rnd.Intn(c.max) + 1 +} + +func newWeightedChooser( + validators []common.Address, + stakes []*big.Int, + seed int64, +) *weightedChooser { + validators, stakes = sortValidatorsAndValues(validators, stakes) + chooser := &weightedChooser{ + rnd: rand.New(rand.NewSource(seed)), + validators: make([]common.Address, len(validators)), + totals: make([]int, len(stakes)), + max: 0, + } + // The scheduler uses pointers, so copy it just in case. + copy(chooser.validators, validators) + + for i := range stakes { + chooser.max += int(new(big.Int).Div(stakes[i], ether).Int64()) + chooser.totals[i] = chooser.max + } + return chooser +} + +func getPrevEpochLastBlockHash( + config *params.OasysConfig, + chain consensus.ChainHeaderReader, + env *environmentValue, + header *types.Header, +) (common.Hash, error) { + var ( + number = header.Number.Uint64() + epochStart = env.GetFirstBlock(number) + parent = header.ParentHash + ) + for ; number > epochStart; number-- { + if cache, ok := lastBlockHashes.Get(parent); ok { + parent = cache.(common.Hash) + break + } + + if chain.GetCanonicalHash(number-1) == parent { + // Reached the header of the canonical chain. + if h := chain.GetHeaderByNumber(epochStart); h != nil { + parent = h.ParentHash + break + } + return emptyHash, consensus.ErrUnknownAncestor + } + + if h := chain.GetHeader(parent, number-1); h != nil { + // Reached the committed header (Not necessarily the canonical chain). + parent = h.ParentHash + } else if uncommitted, ok := uncommittedHashes.Get(parent); ok { + // Not committed to the database. + parent = uncommitted.(common.Hash) + } else { + // Something is wrong. + return emptyHash, fmt.Errorf( + "unable to traverse the chain: parent=%s, number=%d, header.Number=%d, header.Hash=%s, header.ParentHash=%s", + parent, number-1, header.Number, header.Hash(), header.ParentHash) + } + } + + lastBlockHashes.ContainsOrAdd(header.ParentHash, parent) + return parent, nil +} diff --git a/consensus/oasys/scheduler_test.go b/consensus/oasys/scheduler_test.go new file mode 100644 index 000000000..a1e540623 --- /dev/null +++ b/consensus/oasys/scheduler_test.go @@ -0,0 +1,198 @@ +package oasys + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +var ( + epochPeriod = big.NewInt(40) + + validators = []common.Address{ + common.HexToAddress("0xd0887E868eCd4b16B75c60595DD0a7bA21Dbc0E9"), + common.HexToAddress("0xEFb5BCC9b58fDaBEc50d3bFb5838f8FBf65B5Bba"), + common.HexToAddress("0xd96FF3bfe34AF3680806d25cbfc1e138924f0Af2"), + common.HexToAddress("0xa0BE9Fa71e130df9B033F24AF4b8cD944976081a"), + } + stakes = []*big.Int{ + new(big.Int).Mul(big.NewInt(10_000_000), ether), + new(big.Int).Mul(big.NewInt(10_000_000), ether), + new(big.Int).Mul(big.NewInt(10_000_000), ether), + new(big.Int).Mul(big.NewInt(10_000_000), ether), + } + names = map[common.Address]string{ + validators[0]: "validator-0", + validators[1]: "validator-1", + validators[2]: "validator-2", + validators[3]: "validator-3", + } + wantSchedules = []struct { + block uint64 + turns []int + }{ + // epoch 1 + {40, []int{1, 3, 2, 0}}, + {41, []int{0, 3, 1, 2}}, + {42, []int{2, 3, 0, 1}}, + {43, []int{1, 3, 2, 0}}, + {44, []int{0, 3, 1, 2}}, + {45, []int{0, 3, 1, 2}}, + {46, []int{1, 3, 0, 2}}, + {47, []int{0, 2, 3, 1}}, + {48, []int{0, 2, 3, 1}}, + {49, []int{1, 2, 3, 0}}, + {50, []int{0, 1, 2, 3}}, + {51, []int{2, 0, 1, 3}}, + {52, []int{2, 0, 1, 3}}, + {53, []int{2, 0, 1, 3}}, + {54, []int{1, 3, 0, 2}}, + {55, []int{0, 3, 2, 1}}, + {56, []int{1, 3, 2, 0}}, + {57, []int{0, 3, 2, 1}}, + {58, []int{0, 3, 2, 1}}, + {59, []int{1, 3, 2, 0}}, + {60, []int{1, 3, 2, 0}}, + {61, []int{1, 3, 2, 0}}, + {62, []int{0, 3, 2, 1}}, + {63, []int{2, 3, 1, 0}}, + {64, []int{1, 3, 0, 2}}, + {65, []int{0, 2, 3, 1}}, + {66, []int{0, 2, 3, 1}}, + {67, []int{3, 1, 2, 0}}, + {68, []int{2, 0, 1, 3}}, + {69, []int{2, 0, 1, 3}}, + {70, []int{1, 3, 0, 2}}, + {71, []int{0, 3, 1, 2}}, + {72, []int{3, 2, 0, 1}}, + {73, []int{3, 2, 1, 0}}, + {74, []int{3, 2, 0, 1}}, + {75, []int{3, 2, 1, 0}}, + {76, []int{3, 1, 0, 2}}, + {77, []int{3, 0, 2, 1}}, + {78, []int{3, 0, 2, 1}}, + {79, []int{3, 2, 1, 0}}, + // epoch 2 + {80, []int{0, 3, 1, 2}}, + {81, []int{0, 3, 1, 2}}, + {82, []int{1, 3, 0, 2}}, + {83, []int{1, 3, 0, 2}}, + {84, []int{0, 3, 1, 2}}, + {85, []int{2, 3, 0, 1}}, + {86, []int{2, 3, 1, 0}}, + {87, []int{2, 3, 0, 1}}, + {88, []int{1, 3, 2, 0}}, + {89, []int{0, 2, 1, 3}}, + {90, []int{2, 1, 0, 3}}, + {91, []int{1, 0, 3, 2}}, + {92, []int{0, 1, 3, 2}}, + {93, []int{1, 0, 3, 2}}, + {94, []int{0, 3, 2, 1}}, + {95, []int{0, 3, 2, 1}}, + {96, []int{3, 2, 1, 0}}, + {97, []int{3, 2, 0, 1}}, + {98, []int{3, 2, 0, 1}}, + {99, []int{3, 2, 0, 1}}, + {100, []int{3, 1, 2, 0}}, + {101, []int{2, 0, 1, 3}}, + {102, []int{2, 0, 1, 3}}, + {103, []int{1, 2, 0, 3}}, + {104, []int{0, 2, 1, 3}}, + {105, []int{3, 1, 0, 2}}, + {106, []int{3, 0, 1, 2}}, + {107, []int{3, 0, 1, 2}}, + {108, []int{3, 1, 0, 2}}, + {109, []int{3, 1, 0, 2}}, + {110, []int{2, 0, 3, 1}}, + {111, []int{2, 0, 3, 1}}, + {112, []int{1, 3, 2, 0}}, + {113, []int{0, 2, 1, 3}}, + {114, []int{3, 1, 0, 2}}, + {115, []int{3, 1, 0, 2}}, + {116, []int{2, 0, 3, 1}}, + {117, []int{1, 2, 3, 0}}, + {118, []int{0, 2, 3, 1}}, + {119, []int{1, 2, 3, 0}}, + } +) + +func TestBackOffTimes(t *testing.T) { + env := &environmentValue{ + StartBlock: common.Big0, + StartEpoch: common.Big1, + EpochPeriod: epochPeriod, + } + + for _, s := range wantSchedules { + chooser := newWeightedChooser(validators, stakes, int64(env.GetFirstBlock(s.block))) + scheduler := newScheduler(env, env.GetFirstBlock(s.block), chooser) + for i, validator := range validators { + want := uint64(s.turns[i]) + if want > 0 { + want += backoffWiggleTime + } + + got := scheduler.backOffTime(s.block, validator) + if got != want { + t.Errorf("backoff mismatch, block %v, validator %v, got %v, want %v", s.block, names[validator], got, want) + } + } + } +} + +func TestExpect(t *testing.T) { + env := &environmentValue{ + StartBlock: common.Big0, + StartEpoch: common.Big1, + EpochPeriod: epochPeriod, + } + + for _, s := range wantSchedules { + chooser := newWeightedChooser(validators, stakes, int64(env.GetFirstBlock(s.block))) + scheduler := newScheduler(env, env.GetFirstBlock(s.block), chooser) + + var want common.Address + for i, validator := range validators { + if s.turns[i] == 0 { + want = validator + } + } + + got := *scheduler.expect(s.block) + if got != want { + t.Errorf("schedule mismatch, block %v, got %v, want %v", s.block, names[got], names[want]) + } + } +} + +func TestDifficulty(t *testing.T) { + env := &environmentValue{ + StartBlock: common.Big0, + StartEpoch: common.Big1, + EpochPeriod: epochPeriod, + ValidatorThreshold: new(big.Int).Mul(ether, big.NewInt(10_000_000)), + } + + for _, s := range wantSchedules { + chooser := newWeightedChooser(validators, stakes, int64(env.GetFirstBlock(s.block))) + scheduler := newScheduler(env, env.GetFirstBlock(s.block), chooser) + + for i, validator := range validators { + want1 := diffNoTurn.Uint64() + if s.turns[i] == 0 { + want1 = diffInTurn.Uint64() + } + want2 := uint64(1000) * uint64(len(validators)-s.turns[i]) + + got1 := scheduler.difficulty(s.block, validator, false).Uint64() + got2 := scheduler.difficulty(s.block, validator, true).Uint64() + if got1 != want1 { + t.Errorf("difficulty mismatch, block %v, validator %v, got %v, want %v", s.block, names[validator], got1, want1) + } + if got2 != want2 { + t.Errorf("difficulty mismatch, block %v, validator %v, got %v, want %v", s.block, names[validator], got2, want2) + } + } + } +} diff --git a/consensus/oasys/snapshot.go b/consensus/oasys/snapshot.go index f19ebd1e0..31817f882 100644 --- a/consensus/oasys/snapshot.go +++ b/consensus/oasys/snapshot.go @@ -177,19 +177,6 @@ func (s *Snapshot) exists(validator common.Address) bool { return ok } -func (s *Snapshot) getValidatorSchedule(chain consensus.ChainHeaderReader, env *environmentValue, number uint64) map[uint64]common.Address { - validators, stakes := s.validatorsToTuple() - return getValidatorSchedule(chain, validators, stakes, env, number) -} - -func (s *Snapshot) backOffTime(chain consensus.ChainHeaderReader, env *environmentValue, number uint64, validator common.Address) uint64 { - if !s.exists(validator) { - return 0 - } - validators, stakes := s.validatorsToTuple() - return backOffTime(chain, validators, stakes, env, number, validator) -} - func (s *Snapshot) validatorsToTuple() ([]common.Address, []*big.Int) { operators := make([]common.Address, len(s.Validators)) stakes := make([]*big.Int, len(s.Validators)) diff --git a/core/blockchain.go b/core/blockchain.go index 48f6cb64d..ba964e961 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2468,13 +2468,35 @@ func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, e ########## BAD BLOCK ######### Chain config: %v +Validator: 0x%x Number: %v Hash: 0x%x +ParentHash: 0x%x +Time: %v +Difficulty: %v +ReceiptHash: 0x%x +Root: 0x%x +Extra: 0x%x +Transactions: %d %v Error: %v ############################## -`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err)) +`, + bc.chainConfig, + block.Coinbase(), + block.Number(), + block.Hash(), + block.ParentHash(), + block.Time(), + block.Difficulty(), + block.ReceiptHash(), + block.Root(), + block.Extra(), + block.Transactions().Len(), + receiptString, + err, + )) } // InsertHeaderChain attempts to insert the given header chain in to the local diff --git a/core/chain_makers.go b/core/chain_makers.go index 9ca2bce9a..0be562756 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -351,3 +351,4 @@ func (cr *fakeChainReader) GetHeaderByHash(hash common.Hash) *types.Header func (cr *fakeChainReader) GetHeader(hash common.Hash, number uint64) *types.Header { return nil } func (cr *fakeChainReader) GetBlock(hash common.Hash, number uint64) *types.Block { return nil } func (cr *fakeChainReader) GetTd(hash common.Hash, number uint64) *big.Int { return nil } +func (cr *fakeChainReader) GetCanonicalHash(number uint64) common.Hash { return common.Hash{} } diff --git a/params/config.go b/params/config.go index d620f759f..b21ca72ee 100644 --- a/params/config.go +++ b/params/config.go @@ -443,7 +443,7 @@ func (c *ChainConfig) String() string { default: engine = "unknown" } - return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, MergeFork: %v, OasysPublication: %v, Engine: %v}", + return fmt.Sprintf("{ChainID: %v Homestead: %v DAO: %v DAOSupport: %v EIP150: %v EIP155: %v EIP158: %v Byzantium: %v Constantinople: %v Petersburg: %v Istanbul: %v, Muir Glacier: %v, Berlin: %v, London: %v, Arrow Glacier: %v, MergeFork: %v, OasysPublication: %v, OasysExtendDifficulty: %v, Engine: %v}", c.ChainID, c.HomesteadBlock, c.DAOForkBlock, @@ -461,6 +461,7 @@ func (c *ChainConfig) String() string { c.ArrowGlacierBlock, c.MergeForkBlock, c.OasysPublicationBlock(), + c.OasysExtendDifficultyBlock(), engine, ) } @@ -546,14 +547,28 @@ func (c *ChainConfig) OasysPublicationBlock() *big.Int { return big.NewInt(2) } -// IsOasysPublication returns true if num is equal to or greater than the Oasys hard fork block. +// IsOasysPublication returns true if num is equal to or greater than the Oasys Publication fork block. func (c *ChainConfig) IsForkedOasysPublication(num *big.Int) bool { return isForked(c.OasysPublicationBlock(), num) } -// IsOasysPublication returns true if num is equal to than the Oasys Publication fork block. -func (c *ChainConfig) IsOnOasysPublication(num *big.Int) bool { - return configNumEqual(c.OasysPublicationBlock(), num) +// OasysExtendDifficultyBlock returns the hard fork of Oasys. +func (c *ChainConfig) OasysExtendDifficultyBlock() *big.Int { + if c.Oasys == nil { + return nil + } + if c.ChainID.Cmp(OasysMainnetChainConfig.ChainID) == 0 { + return big.NewInt(2093240) + } + if c.ChainID.Cmp(OasysTestnetChainConfig.ChainID) == 0 { + return big.NewInt(2082220) + } + return big.NewInt(2) +} + +// IsForkedOasysExtendDifficulty returns true if num is equal to or greater than the Oasys fork block. +func (c *ChainConfig) IsForkedOasysExtendDifficulty(num *big.Int) bool { + return isForked(c.OasysExtendDifficultyBlock(), num) } // IsTerminalPoWBlock returns whether the given block is the last block of PoW stage. diff --git a/params/version.go b/params/version.go index 4087a07da..b0fd2f5a6 100644 --- a/params/version.go +++ b/params/version.go @@ -22,7 +22,7 @@ import ( const ( VersionMajor = 1 // Major version component of the current release - VersionMinor = 2 // Minor version component of the current release + VersionMinor = 3 // Minor version component of the current release VersionPatch = 0 // Patch version component of the current release VersionMeta = "testnet0" // Version metadata to append to the version string )