From 7d47d9e8a9e728399d75199b966aaadf52a4e57a Mon Sep 17 00:00:00 2001 From: Konstantin <355847+Frozen@users.noreply.github.com> Date: Wed, 5 Apr 2023 19:51:05 -0300 Subject: [PATCH] Rotate external validators for non-beacon shards. (#4373) * Rotate only non beacon shards. * Rotate all shards, but only hmy validators for beacon. * Fix type. * Revert "Fix type." This reverts commit 0a8b506c763d9f8609abff7395ba32b18e43b149. * Revert "Rotate all shards, but only hmy validators for beacon." This reverts commit 70b09e2de81aa2cbffae3ccdfd4e334e7d938759. * Fixed failed test. * Revert "Revert "Rotate all shards, but only hmy validators for beacon."" This reverts commit 66cfaa9817488be60ed5b5cfee1fe833ede237c8. * Frequency by slots count. * Fix config. * First validator produce rest blocks. * Updated. * Add lock. --- consensus/consensus.go | 2 + consensus/consensus_service.go | 2 +- consensus/consensus_v2.go | 85 ++-- consensus/validator.go | 3 +- consensus/view_change.go | 8 +- core/blockchain.go | 2 + core/blockchain_impl.go | 56 ++- core/blockchain_stub.go | 4 + core/rawdb/accessors_metadata.go | 34 ++ core/rawdb/accessors_metadata_test.go | 32 ++ core/rawdb/schema.go | 5 + internal/configs/sharding/shardingconfig.go | 3 +- internal/params/config.go | 468 ++++++++++---------- numeric/decimal.go | 6 + numeric/decimal_test.go | 24 + 15 files changed, 468 insertions(+), 266 deletions(-) create mode 100644 core/rawdb/accessors_metadata_test.go diff --git a/consensus/consensus.go b/consensus/consensus.go index e0cc591df..d5130e22c 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -218,6 +218,8 @@ func (consensus *Consensus) getConsensusLeaderPrivateKey() (*bls.PrivateKeyWrapp // SetBlockVerifier sets the block verifier func (consensus *Consensus) SetBlockVerifier(verifier VerifyBlockFunc) { + consensus.mutex.Lock() + defer consensus.mutex.Unlock() consensus.BlockVerifier = verifier consensus.vc.SetVerifyBlock(consensus.verifyBlock) } diff --git a/consensus/consensus_service.go b/consensus/consensus_service.go index 0e4bb6814..3ca29812d 100644 --- a/consensus/consensus_service.go +++ b/consensus/consensus_service.go @@ -547,7 +547,7 @@ func (consensus *Consensus) GetFinality() int64 { return consensus.finality } -// switchPhase will switch FBFTPhase to nextPhase if the desirePhase equals the nextPhase +// switchPhase will switch FBFTPhase to desired phase. func (consensus *Consensus) switchPhase(subject string, desired FBFTPhase) { consensus.getLogger().Info(). Str("from:", consensus.phase.String()). diff --git a/consensus/consensus_v2.go b/consensus/consensus_v2.go index 99bccf755..b00e0d0d3 100644 --- a/consensus/consensus_v2.go +++ b/consensus/consensus_v2.go @@ -13,7 +13,6 @@ import ( "github.com/harmony-one/harmony/consensus/signature" nodeconfig "github.com/harmony-one/harmony/internal/configs/node" "github.com/harmony-one/harmony/internal/utils" - "github.com/rs/zerolog" msg_pb "github.com/harmony-one/harmony/api/proto/message" @@ -688,40 +687,70 @@ func (consensus *Consensus) commitBlock(blk *types.Block, committedMsg *FBFTMess // rotateLeader rotates the leader to the next leader in the committee. // This function must be called with enabled leader rotation. func (consensus *Consensus) rotateLeader(epoch *big.Int) { - prev := consensus.getLeaderPubKey() - bc := consensus.Blockchain() - curNumber := bc.CurrentHeader().Number().Uint64() - utils.Logger().Info().Msgf("[Rotating leader] epoch: %v rotation:%v numblocks:%d", epoch.Uint64(), bc.Config().IsLeaderRotation(epoch), bc.Config().LeaderRotationBlocksCount) - leader := consensus.getLeaderPubKey() - for i := 0; i < bc.Config().LeaderRotationBlocksCount; i++ { - header := bc.GetHeaderByNumber(curNumber - uint64(i)) - if header == nil { - return - } - // Previous epoch, we should not change leader. - if header.Epoch().Uint64() != epoch.Uint64() { - return - } - // Check if the same leader. - pub, err := bc.GetLeaderPubKeyFromCoinbase(header) - if err != nil { - utils.Logger().Error().Err(err).Msg("Failed to get leader public key from coinbase") - return - } - if !pub.Object.IsEqual(leader.Object) { - // Another leader. - return - } + var ( + bc = consensus.Blockchain() + prev = consensus.getLeaderPubKey() + leader = consensus.getLeaderPubKey() + ) + utils.Logger().Info().Msgf("[Rotating leader] epoch: %v rotation:%v external rotation %v", epoch.Uint64(), bc.Config().IsLeaderRotation(epoch), bc.Config().IsLeaderRotationExternalValidatorsAllowed(epoch, consensus.ShardID)) + ss, err := bc.ReadShardState(epoch) + if err != nil { + utils.Logger().Error().Err(err).Msg("Failed to read shard state") + return + } + committee, err := ss.FindCommitteeByID(consensus.ShardID) + if err != nil { + utils.Logger().Error().Err(err).Msg("Failed to find committee") + return + } + slotsCount := len(committee.Slots) + blocksPerEpoch := shard.Schedule.InstanceForEpoch(epoch).BlocksPerEpoch() + if blocksPerEpoch == 0 { + utils.Logger().Error().Msg("[Rotating leader] blocks per epoch is 0") + return + } + if slotsCount == 0 { + utils.Logger().Error().Msg("[Rotating leader] slots count is 0") + return + } + numBlocksProducedByLeader := blocksPerEpoch / uint64(slotsCount) + rest := blocksPerEpoch % uint64(slotsCount) + const minimumBlocksForLeaderInRow = 3 + if numBlocksProducedByLeader < minimumBlocksForLeaderInRow { + // mine no less than 3 blocks in a row + numBlocksProducedByLeader = minimumBlocksForLeaderInRow + } + type stored struct { + pub []byte + epoch uint64 + count uint64 + shifts uint64 // count how much changes validator per epoch + } + var s stored + s.pub, s.epoch, s.count, s.shifts, _ = bc.LeaderRotationMeta() + if !bytes.Equal(leader.Bytes[:], s.pub) { + // Another leader. + return + } + // if it is the first validator which produce blocks, then it should produce `rest` blocks too. + if s.shifts == 0 { + numBlocksProducedByLeader += rest + } + if s.count < numBlocksProducedByLeader { + // Not enough blocks produced by the leader. + return } // Passed all checks, we can change leader. + // NthNext will move the leader to the next leader in the committee. + // It does not know anything about external or internal validators. var ( wasFound bool next *bls.PublicKeyWrapper ) - if consensus.ShardID == shard.BeaconChainShardID { - wasFound, next = consensus.Decider.NthNextHmy(shard.Schedule.InstanceForEpoch(epoch), leader, 1) - } else { + if bc.Config().IsLeaderRotationExternalValidatorsAllowed(epoch, consensus.ShardID) { wasFound, next = consensus.Decider.NthNext(leader, 1) + } else { + wasFound, next = consensus.Decider.NthNextHmy(shard.Schedule.InstanceForEpoch(epoch), leader, 1) } if !wasFound { utils.Logger().Error().Msg("Failed to get next leader") diff --git a/consensus/validator.go b/consensus/validator.go index f85cb8e3d..92008a91e 100644 --- a/consensus/validator.go +++ b/consensus/validator.go @@ -63,9 +63,8 @@ func (consensus *Consensus) onAnnounce(msg *msg_pb.Message) { go func() { // Best effort check, no need to error out. _, err := consensus.ValidateNewBlock(recvMsg) - if err == nil { - consensus.getLogger().Info(). + consensus.GetLogger().Info(). Msg("[Announce] Block verified") } }() diff --git a/consensus/view_change.go b/consensus/view_change.go index aafdfd121..45838f9bc 100644 --- a/consensus/view_change.go +++ b/consensus/view_change.go @@ -203,13 +203,13 @@ func (consensus *Consensus) getNextLeaderKey(viewID uint64) *bls.PublicKeyWrappe var wasFound bool var next *bls.PublicKeyWrapper if blockchain != nil && blockchain.Config().IsLeaderRotation(epoch) { - if consensus.ShardID == shard.BeaconChainShardID { - wasFound, next = consensus.Decider.NthNextHmy( - shard.Schedule.InstanceForEpoch(epoch), + if blockchain.Config().IsLeaderRotationExternalValidatorsAllowed(epoch, consensus.ShardID) { + wasFound, next = consensus.Decider.NthNext( lastLeaderPubKey, gap) } else { - wasFound, next = consensus.Decider.NthNext( + wasFound, next = consensus.Decider.NthNextHmy( + shard.Schedule.InstanceForEpoch(epoch), lastLeaderPubKey, gap) } diff --git a/core/blockchain.go b/core/blockchain.go index fda483165..8afe622c7 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -107,6 +107,8 @@ type BlockChain interface { // // After insertion is done, all accumulated events will be fired. InsertChain(chain types.Blocks, verifyHeaders bool) (int, error) + // LeaderRotationMeta returns the number of continuous blocks by the leader. + LeaderRotationMeta() (publicKeyBytes []byte, epoch, count, shifts uint64, err error) // BadBlocks returns a list of the last 'bad blocks' that // the client has seen on the network. BadBlocks() []BadBlock diff --git a/core/blockchain_impl.go b/core/blockchain_impl.go index 148176f23..f1d096842 100644 --- a/core/blockchain_impl.go +++ b/core/blockchain_impl.go @@ -114,6 +114,7 @@ const ( validatorListByDelegatorCacheLimit = 128 pendingCrossLinksCacheLimit = 2 blockAccumulatorCacheLimit = 64 + leaderPubKeyFromCoinbaseLimit = 8 maxPendingSlashes = 256 // BlockChainVersion ensures that an incompatible database forces a resync from scratch. BlockChainVersion = 3 @@ -240,7 +241,7 @@ func newBlockChainWithOptions( validatorListByDelegatorCache, _ := lru.New(validatorListByDelegatorCacheLimit) pendingCrossLinksCache, _ := lru.New(pendingCrossLinksCacheLimit) blockAccumulatorCache, _ := lru.New(blockAccumulatorCacheLimit) - leaderPubKeyFromCoinbase, _ := lru.New(chainConfig.LeaderRotationBlocksCount + 2) + leaderPubKeyFromCoinbase, _ := lru.New(leaderPubKeyFromCoinbaseLimit) bc := &BlockChainImpl{ chainConfig: chainConfig, @@ -1522,6 +1523,18 @@ func (bc *BlockChainImpl) InsertChain(chain types.Blocks, verifyHeaders bool) (i n, events, logs, err := bc.insertChain(chain, verifyHeaders) bc.PostChainEvents(events, logs) + if err == nil { + // there should be only 1 block. + for _, b := range chain { + if b.Epoch().Uint64() > 0 { + err := bc.saveLeaderRotationMeta(b.Header()) + if err != nil { + utils.Logger().Error().Err(err).Msg("save leader continuous blocks count error") + return n, err + } + } + } + } if bc.isInitTiKV() && err != nil { // if has some error, master writer node will release the permission _, _ = bc.redisPreempt.Unlock() @@ -1529,6 +1542,47 @@ func (bc *BlockChainImpl) InsertChain(chain types.Blocks, verifyHeaders bool) (i return n, err } +func (bc *BlockChainImpl) saveLeaderRotationMeta(h *block.Header) error { + blockPubKey, err := bc.getLeaderPubKeyFromCoinbase(h) + if err != nil { + return err + } + type stored struct { + pub []byte + epoch uint64 + count uint64 + shifts uint64 + } + var s stored + // error is possible here only on the first iteration, so we can ignore it + s.pub, s.epoch, s.count, s.shifts, _ = rawdb.ReadLeaderRotationMeta(bc.db) + + // increase counter only if the same leader and epoch + if bytes.Equal(s.pub, blockPubKey.Bytes[:]) && s.epoch == h.Epoch().Uint64() { + s.count++ + } else { + s.count = 1 + } + // we should increase shifts if the leader is changed. + if !bytes.Equal(s.pub, blockPubKey.Bytes[:]) { + s.shifts++ + } + // but set to zero if new + if s.epoch != h.Epoch().Uint64() { + s.shifts = 0 + } + + err = rawdb.WriteLeaderRotationMeta(bc.db, blockPubKey.Bytes[:], h.Epoch().Uint64(), s.count, s.shifts) + if err != nil { + return err + } + return nil +} + +func (bc *BlockChainImpl) LeaderRotationMeta() (publicKeyBytes []byte, epoch, count, shifts uint64, err error) { + return rawdb.ReadLeaderRotationMeta(bc.db) +} + // insertChain will execute the actual chain insertion and event aggregation. The // only reason this method exists as a separate one is to make locking cleaner // with deferred statements. diff --git a/core/blockchain_stub.go b/core/blockchain_stub.go index f9e9111ea..cfe72eed6 100644 --- a/core/blockchain_stub.go +++ b/core/blockchain_stub.go @@ -423,3 +423,7 @@ func (a Stub) SyncFromTiKVWriter(newBlkNum uint64, logs []*types.Log) error { func (a Stub) InitTiKV(conf *harmonyconfig.TiKVConfig) { return } + +func (a Stub) LeaderRotationMeta() (publicKeyBytes []byte, epoch, count, shifts uint64, err error) { + return nil, 0, 0, 0, errors.Errorf("method LeaderRotationMeta not implemented for %s", a.Name) +} diff --git a/core/rawdb/accessors_metadata.go b/core/rawdb/accessors_metadata.go index 6fef26d09..69a7522f6 100644 --- a/core/rawdb/accessors_metadata.go +++ b/core/rawdb/accessors_metadata.go @@ -17,14 +17,17 @@ package rawdb import ( + "encoding/binary" "encoding/json" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/rlp" + "github.com/harmony-one/harmony/crypto/bls" "github.com/harmony-one/harmony/internal/params" "github.com/harmony-one/harmony/internal/utils" + "github.com/pkg/errors" ) // ReadDatabaseVersion retrieves the version number of the database. @@ -192,3 +195,34 @@ func WriteTransitionStatus(db ethdb.KeyValueWriter, data []byte) { utils.Logger().Error().Err(err).Msg("Failed to store the eth2 transition status") } } + +// WriteLeaderRotationMeta writes the leader continuous blocks count to the database. +func WriteLeaderRotationMeta(db DatabaseWriter, leader []byte, epoch uint64, count, shifts uint64) error { + if len(leader) != bls.PublicKeySizeInBytes { + return errors.New("invalid leader public key size") + } + value := make([]byte, bls.PublicKeySizeInBytes+8*3) + copy(value, leader) + binary.LittleEndian.PutUint64(value[len(leader)+8*0:], epoch) + binary.LittleEndian.PutUint64(value[len(leader)+8*1:], count) + binary.LittleEndian.PutUint64(value[len(leader)+8*2:], shifts) + if err := db.Put(leaderContinuousBlocksCountKey(), value); err != nil { + utils.Logger().Error().Err(err).Msg("Failed to store leader continuous blocks count") + return err + } + return nil +} + +// ReadLeaderRotationMeta retrieves the leader continuous blocks count from the database. +func ReadLeaderRotationMeta(db DatabaseReader) (pubKeyBytes []byte, epoch, count, shifts uint64, err error) { + data, _ := db.Get(leaderContinuousBlocksCountKey()) + if len(data) != bls.PublicKeySizeInBytes+24 { + return nil, 0, 0, 0, errors.New("invalid leader continuous blocks count") + } + + pubKeyBytes = data[:bls.PublicKeySizeInBytes] + epoch = binary.LittleEndian.Uint64(data[bls.PublicKeySizeInBytes:]) + count = binary.LittleEndian.Uint64(data[bls.PublicKeySizeInBytes+8:]) + shifts = binary.LittleEndian.Uint64(data[bls.PublicKeySizeInBytes+16:]) + return pubKeyBytes, epoch, count, shifts, nil +} diff --git a/core/rawdb/accessors_metadata_test.go b/core/rawdb/accessors_metadata_test.go new file mode 100644 index 000000000..62cfac062 --- /dev/null +++ b/core/rawdb/accessors_metadata_test.go @@ -0,0 +1,32 @@ +package rawdb + +import ( + "testing" + + ethRawDB "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/harmony-one/harmony/crypto/bls" +) + +func TestLeaderRotationMeta(t *testing.T) { + db := ethRawDB.NewMemoryDatabase() + err := WriteLeaderRotationMeta(db, make([]byte, bls.PublicKeySizeInBytes), 1, 2, 3) + if err != nil { + t.Fatal(err) + } + pub, epoch, count, shifts, err := ReadLeaderRotationMeta(db) + if err != nil { + t.Fatal(err) + } + if len(pub) != bls.PublicKeySizeInBytes { + t.Fatal("invalid leader public key size") + } + if epoch != 1 { + t.Fatal("invalid epoch") + } + if count != 2 { + t.Fatal("invalid count") + } + if shifts != 3 { + t.Fatal("invalid shifts") + } +} diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 34e818b32..798f27f59 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -100,6 +100,7 @@ var ( pendingCrosslinkKey = []byte("pendingCL") // prefix for shard last pending crosslink pendingSlashingKey = []byte("pendingSC") // prefix for shard last pending slashing record preimagePrefix = []byte("secure-key-") // preimagePrefix + hash -> preimage + continuousBlocksCountKey = []byte("continuous") // key for continuous blocks count configPrefix = []byte("ethereum-config-") // config prefix for the db crosslinkPrefix = []byte("cl") // prefix for crosslink delegatorValidatorListPrefix = []byte("dvl") // prefix for delegator's validator list @@ -285,6 +286,10 @@ func preimageKey(hash common.Hash) []byte { return append(preimagePrefix, hash.Bytes()...) } +func leaderContinuousBlocksCountKey() []byte { + return continuousBlocksCountKey +} + // configKey = configPrefix + hash func configKey(hash common.Hash) []byte { return append(configPrefix, hash.Bytes()...) diff --git a/internal/configs/sharding/shardingconfig.go b/internal/configs/sharding/shardingconfig.go index 30b4dca4c..91b4458dd 100644 --- a/internal/configs/sharding/shardingconfig.go +++ b/internal/configs/sharding/shardingconfig.go @@ -72,8 +72,9 @@ type Instance interface { // ReshardingEpoch returns a list of Epoch while off-chain resharding happens ReshardingEpoch() []*big.Int - // Count of blocks per epoch + // BlocksPerEpoch returns the number of blocks per epoch. BlocksPerEpoch() uint64 + // HIP-16: The absolute number of maximum effective slots per shard limit for each validator. 0 means no limit. SlotsLimit() int diff --git a/internal/params/config.go b/internal/params/config.go index d90038630..a34dcd397 100644 --- a/internal/params/config.go +++ b/internal/params/config.go @@ -36,250 +36,250 @@ var once sync.Once var ( // MainnetChainConfig is the chain parameters to run a node on the main network. MainnetChainConfig = &ChainConfig{ - ChainID: MainnetChainID, - EthCompatibleChainID: EthMainnetShard0ChainID, - EthCompatibleShard0ChainID: EthMainnetShard0ChainID, - EthCompatibleEpoch: big.NewInt(442), // Around Thursday Feb 4th 2020, 10AM PST - CrossTxEpoch: big.NewInt(28), - CrossLinkEpoch: big.NewInt(186), - AggregatedRewardEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time - StakingEpoch: big.NewInt(186), - PreStakingEpoch: big.NewInt(185), - QuickUnlockEpoch: big.NewInt(191), - FiveSecondsEpoch: big.NewInt(230), - TwoSecondsEpoch: big.NewInt(366), // Around Tuesday Dec 8th 2020, 8AM PST - SixtyPercentEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC - RedelegationEpoch: big.NewInt(290), - NoEarlyUnlockEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC - VRFEpoch: big.NewInt(631), // Around Wed July 7th 2021 - PrevVRFEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time - MinDelegation100Epoch: big.NewInt(631), // Around Wed July 7th 2021 - MinCommissionRateEpoch: big.NewInt(631), // Around Wed July 7th 2021 - MinCommissionPromoPeriod: big.NewInt(100), - EPoSBound35Epoch: big.NewInt(631), // Around Wed July 7th 2021 - EIP155Epoch: big.NewInt(28), - S3Epoch: big.NewInt(28), - DataCopyFixEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time - IstanbulEpoch: big.NewInt(314), - ReceiptLogEpoch: big.NewInt(101), - SHA3Epoch: big.NewInt(725), // Around Mon Oct 11 2021, 19:00 UTC - HIP6And8Epoch: big.NewInt(725), // Around Mon Oct 11 2021, 19:00 UTC - StakingPrecompileEpoch: big.NewInt(871), // Around Tue Feb 11 2022 - ChainIdFixEpoch: big.NewInt(1323), // Around Wed 8 Feb 11:30PM UTC - SlotsLimitedEpoch: big.NewInt(999), // Around Fri, 27 May 2022 09:41:02 UTC with 2s block time - CrossShardXferPrecompileEpoch: big.NewInt(1323), // Around Wed 8 Feb 11:30PM UTC - AllowlistEpoch: EpochTBD, - FeeCollectEpoch: EpochTBD, - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 64, - ValidatorCodeFixEpoch: EpochTBD, + ChainID: MainnetChainID, + EthCompatibleChainID: EthMainnetShard0ChainID, + EthCompatibleShard0ChainID: EthMainnetShard0ChainID, + EthCompatibleEpoch: big.NewInt(442), // Around Thursday Feb 4th 2020, 10AM PST + CrossTxEpoch: big.NewInt(28), + CrossLinkEpoch: big.NewInt(186), + AggregatedRewardEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time + StakingEpoch: big.NewInt(186), + PreStakingEpoch: big.NewInt(185), + QuickUnlockEpoch: big.NewInt(191), + FiveSecondsEpoch: big.NewInt(230), + TwoSecondsEpoch: big.NewInt(366), // Around Tuesday Dec 8th 2020, 8AM PST + SixtyPercentEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC + RedelegationEpoch: big.NewInt(290), + NoEarlyUnlockEpoch: big.NewInt(530), // Around Monday Apr 12th 2021, 22:30 UTC + VRFEpoch: big.NewInt(631), // Around Wed July 7th 2021 + PrevVRFEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time + MinDelegation100Epoch: big.NewInt(631), // Around Wed July 7th 2021 + MinCommissionRateEpoch: big.NewInt(631), // Around Wed July 7th 2021 + MinCommissionPromoPeriod: big.NewInt(100), + EPoSBound35Epoch: big.NewInt(631), // Around Wed July 7th 2021 + EIP155Epoch: big.NewInt(28), + S3Epoch: big.NewInt(28), + DataCopyFixEpoch: big.NewInt(689), // Around Wed Sept 15th 2021 with 3.5s block time + IstanbulEpoch: big.NewInt(314), + ReceiptLogEpoch: big.NewInt(101), + SHA3Epoch: big.NewInt(725), // Around Mon Oct 11 2021, 19:00 UTC + HIP6And8Epoch: big.NewInt(725), // Around Mon Oct 11 2021, 19:00 UTC + StakingPrecompileEpoch: big.NewInt(871), // Around Tue Feb 11 2022 + ChainIdFixEpoch: big.NewInt(1323), // Around Wed 8 Feb 11:30PM UTC + SlotsLimitedEpoch: big.NewInt(999), // Around Fri, 27 May 2022 09:41:02 UTC with 2s block time + CrossShardXferPrecompileEpoch: big.NewInt(1323), // Around Wed 8 Feb 11:30PM UTC + AllowlistEpoch: EpochTBD, + FeeCollectEpoch: EpochTBD, + LeaderRotationExternalNonBeaconLeaders: EpochTBD, + LeaderRotationExternalBeaconLeaders: EpochTBD, + ValidatorCodeFixEpoch: EpochTBD, } // TestnetChainConfig contains the chain parameters to run a node on the harmony test network. TestnetChainConfig = &ChainConfig{ - ChainID: TestnetChainID, - EthCompatibleChainID: EthTestnetShard0ChainID, - EthCompatibleShard0ChainID: EthTestnetShard0ChainID, - EthCompatibleEpoch: big.NewInt(0), - CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(2), - AggregatedRewardEpoch: big.NewInt(2), - StakingEpoch: big.NewInt(2), - PreStakingEpoch: big.NewInt(1), - QuickUnlockEpoch: big.NewInt(0), - FiveSecondsEpoch: big.NewInt(0), - TwoSecondsEpoch: big.NewInt(2), - SixtyPercentEpoch: big.NewInt(2), - RedelegationEpoch: big.NewInt(2), - NoEarlyUnlockEpoch: big.NewInt(2), - VRFEpoch: big.NewInt(2), - PrevVRFEpoch: big.NewInt(2), - MinDelegation100Epoch: big.NewInt(2), - MinCommissionRateEpoch: big.NewInt(2), - MinCommissionPromoPeriod: big.NewInt(2), - EPoSBound35Epoch: big.NewInt(2), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), - DataCopyFixEpoch: big.NewInt(0), - IstanbulEpoch: big.NewInt(0), - ReceiptLogEpoch: big.NewInt(0), - SHA3Epoch: big.NewInt(0), - HIP6And8Epoch: big.NewInt(2), - StakingPrecompileEpoch: big.NewInt(2), - SlotsLimitedEpoch: big.NewInt(2), - ChainIdFixEpoch: big.NewInt(0), - CrossShardXferPrecompileEpoch: big.NewInt(2), - AllowlistEpoch: big.NewInt(2), - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 64, - FeeCollectEpoch: EpochTBD, - ValidatorCodeFixEpoch: EpochTBD, + ChainID: TestnetChainID, + EthCompatibleChainID: EthTestnetShard0ChainID, + EthCompatibleShard0ChainID: EthTestnetShard0ChainID, + EthCompatibleEpoch: big.NewInt(0), + CrossTxEpoch: big.NewInt(0), + CrossLinkEpoch: big.NewInt(2), + AggregatedRewardEpoch: big.NewInt(2), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(1), + QuickUnlockEpoch: big.NewInt(0), + FiveSecondsEpoch: big.NewInt(0), + TwoSecondsEpoch: big.NewInt(2), + SixtyPercentEpoch: big.NewInt(2), + RedelegationEpoch: big.NewInt(2), + NoEarlyUnlockEpoch: big.NewInt(2), + VRFEpoch: big.NewInt(2), + PrevVRFEpoch: big.NewInt(2), + MinDelegation100Epoch: big.NewInt(2), + MinCommissionRateEpoch: big.NewInt(2), + MinCommissionPromoPeriod: big.NewInt(2), + EPoSBound35Epoch: big.NewInt(2), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), + DataCopyFixEpoch: big.NewInt(0), + IstanbulEpoch: big.NewInt(0), + ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), + HIP6And8Epoch: big.NewInt(2), + StakingPrecompileEpoch: big.NewInt(2), + SlotsLimitedEpoch: big.NewInt(2), + ChainIdFixEpoch: big.NewInt(0), + CrossShardXferPrecompileEpoch: big.NewInt(2), + AllowlistEpoch: big.NewInt(2), + LeaderRotationExternalNonBeaconLeaders: EpochTBD, + LeaderRotationExternalBeaconLeaders: EpochTBD, + FeeCollectEpoch: EpochTBD, + ValidatorCodeFixEpoch: EpochTBD, } // PangaeaChainConfig contains the chain parameters for the Pangaea network. // All features except for CrossLink are enabled at launch. PangaeaChainConfig = &ChainConfig{ - ChainID: PangaeaChainID, - EthCompatibleChainID: EthPangaeaShard0ChainID, - EthCompatibleShard0ChainID: EthPangaeaShard0ChainID, - EthCompatibleEpoch: big.NewInt(0), - CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(2), - AggregatedRewardEpoch: big.NewInt(3), - StakingEpoch: big.NewInt(2), - PreStakingEpoch: big.NewInt(1), - QuickUnlockEpoch: big.NewInt(0), - FiveSecondsEpoch: big.NewInt(0), - TwoSecondsEpoch: big.NewInt(0), - SixtyPercentEpoch: big.NewInt(0), - RedelegationEpoch: big.NewInt(0), - NoEarlyUnlockEpoch: big.NewInt(0), - VRFEpoch: big.NewInt(0), - PrevVRFEpoch: big.NewInt(0), - MinDelegation100Epoch: big.NewInt(0), - MinCommissionRateEpoch: big.NewInt(0), - MinCommissionPromoPeriod: big.NewInt(10), - EPoSBound35Epoch: big.NewInt(0), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), - DataCopyFixEpoch: big.NewInt(0), - IstanbulEpoch: big.NewInt(0), - ReceiptLogEpoch: big.NewInt(0), - SHA3Epoch: big.NewInt(0), - HIP6And8Epoch: big.NewInt(0), - StakingPrecompileEpoch: big.NewInt(2), // same as staking - ChainIdFixEpoch: big.NewInt(0), - SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 - CrossShardXferPrecompileEpoch: big.NewInt(1), - AllowlistEpoch: EpochTBD, - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 64, - FeeCollectEpoch: EpochTBD, - ValidatorCodeFixEpoch: EpochTBD, + ChainID: PangaeaChainID, + EthCompatibleChainID: EthPangaeaShard0ChainID, + EthCompatibleShard0ChainID: EthPangaeaShard0ChainID, + EthCompatibleEpoch: big.NewInt(0), + CrossTxEpoch: big.NewInt(0), + CrossLinkEpoch: big.NewInt(2), + AggregatedRewardEpoch: big.NewInt(3), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(1), + QuickUnlockEpoch: big.NewInt(0), + FiveSecondsEpoch: big.NewInt(0), + TwoSecondsEpoch: big.NewInt(0), + SixtyPercentEpoch: big.NewInt(0), + RedelegationEpoch: big.NewInt(0), + NoEarlyUnlockEpoch: big.NewInt(0), + VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), + MinDelegation100Epoch: big.NewInt(0), + MinCommissionRateEpoch: big.NewInt(0), + MinCommissionPromoPeriod: big.NewInt(10), + EPoSBound35Epoch: big.NewInt(0), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), + DataCopyFixEpoch: big.NewInt(0), + IstanbulEpoch: big.NewInt(0), + ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), + HIP6And8Epoch: big.NewInt(0), + StakingPrecompileEpoch: big.NewInt(2), // same as staking + ChainIdFixEpoch: big.NewInt(0), + SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 + CrossShardXferPrecompileEpoch: big.NewInt(1), + AllowlistEpoch: EpochTBD, + LeaderRotationExternalNonBeaconLeaders: EpochTBD, + LeaderRotationExternalBeaconLeaders: EpochTBD, + FeeCollectEpoch: EpochTBD, + ValidatorCodeFixEpoch: EpochTBD, } // PartnerChainConfig contains the chain parameters for the Partner network. // This is the Devnet config PartnerChainConfig = &ChainConfig{ - ChainID: PartnerChainID, - EthCompatibleChainID: EthPartnerShard0ChainID, - EthCompatibleShard0ChainID: EthPartnerShard0ChainID, - EthCompatibleEpoch: big.NewInt(0), - CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(2), - AggregatedRewardEpoch: big.NewInt(3), - StakingEpoch: big.NewInt(2), - PreStakingEpoch: big.NewInt(1), - QuickUnlockEpoch: big.NewInt(0), - FiveSecondsEpoch: big.NewInt(0), - TwoSecondsEpoch: big.NewInt(0), - SixtyPercentEpoch: big.NewInt(4), - RedelegationEpoch: big.NewInt(0), - NoEarlyUnlockEpoch: big.NewInt(0), - VRFEpoch: big.NewInt(0), - PrevVRFEpoch: big.NewInt(0), - MinDelegation100Epoch: big.NewInt(0), - MinCommissionRateEpoch: big.NewInt(0), - MinCommissionPromoPeriod: big.NewInt(10), - EPoSBound35Epoch: big.NewInt(0), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), - DataCopyFixEpoch: big.NewInt(0), - IstanbulEpoch: big.NewInt(0), - ReceiptLogEpoch: big.NewInt(0), - SHA3Epoch: big.NewInt(0), - HIP6And8Epoch: big.NewInt(0), - StakingPrecompileEpoch: big.NewInt(2), - ChainIdFixEpoch: big.NewInt(0), - SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 - CrossShardXferPrecompileEpoch: big.NewInt(1), - AllowlistEpoch: EpochTBD, - FeeCollectEpoch: big.NewInt(574), - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 64, - ValidatorCodeFixEpoch: EpochTBD, + ChainID: PartnerChainID, + EthCompatibleChainID: EthPartnerShard0ChainID, + EthCompatibleShard0ChainID: EthPartnerShard0ChainID, + EthCompatibleEpoch: big.NewInt(0), + CrossTxEpoch: big.NewInt(0), + CrossLinkEpoch: big.NewInt(2), + AggregatedRewardEpoch: big.NewInt(3), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(1), + QuickUnlockEpoch: big.NewInt(0), + FiveSecondsEpoch: big.NewInt(0), + TwoSecondsEpoch: big.NewInt(0), + SixtyPercentEpoch: big.NewInt(4), + RedelegationEpoch: big.NewInt(0), + NoEarlyUnlockEpoch: big.NewInt(0), + VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), + MinDelegation100Epoch: big.NewInt(0), + MinCommissionRateEpoch: big.NewInt(0), + MinCommissionPromoPeriod: big.NewInt(10), + EPoSBound35Epoch: big.NewInt(0), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), + DataCopyFixEpoch: big.NewInt(0), + IstanbulEpoch: big.NewInt(0), + ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), + HIP6And8Epoch: big.NewInt(0), + StakingPrecompileEpoch: big.NewInt(2), + ChainIdFixEpoch: big.NewInt(0), + SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 + CrossShardXferPrecompileEpoch: big.NewInt(1), + AllowlistEpoch: EpochTBD, + FeeCollectEpoch: big.NewInt(574), + LeaderRotationExternalNonBeaconLeaders: EpochTBD, + LeaderRotationExternalBeaconLeaders: EpochTBD, + ValidatorCodeFixEpoch: EpochTBD, } // StressnetChainConfig contains the chain parameters for the Stress test network. // All features except for CrossLink are enabled at launch. StressnetChainConfig = &ChainConfig{ - ChainID: StressnetChainID, - EthCompatibleChainID: EthStressnetShard0ChainID, - EthCompatibleShard0ChainID: EthStressnetShard0ChainID, - EthCompatibleEpoch: big.NewInt(0), - CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(2), - AggregatedRewardEpoch: big.NewInt(3), - StakingEpoch: big.NewInt(2), - PreStakingEpoch: big.NewInt(1), - QuickUnlockEpoch: big.NewInt(0), - FiveSecondsEpoch: big.NewInt(0), - TwoSecondsEpoch: big.NewInt(0), - SixtyPercentEpoch: big.NewInt(10), - RedelegationEpoch: big.NewInt(0), - NoEarlyUnlockEpoch: big.NewInt(0), - VRFEpoch: big.NewInt(0), - PrevVRFEpoch: big.NewInt(0), - MinDelegation100Epoch: big.NewInt(0), - MinCommissionRateEpoch: big.NewInt(0), - MinCommissionPromoPeriod: big.NewInt(10), - EPoSBound35Epoch: big.NewInt(0), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), - DataCopyFixEpoch: big.NewInt(0), - IstanbulEpoch: big.NewInt(0), - ReceiptLogEpoch: big.NewInt(0), - SHA3Epoch: big.NewInt(0), - HIP6And8Epoch: big.NewInt(0), - StakingPrecompileEpoch: big.NewInt(2), - ChainIdFixEpoch: big.NewInt(0), - SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 - CrossShardXferPrecompileEpoch: big.NewInt(1), - AllowlistEpoch: EpochTBD, - FeeCollectEpoch: EpochTBD, - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 64, - ValidatorCodeFixEpoch: EpochTBD, + ChainID: StressnetChainID, + EthCompatibleChainID: EthStressnetShard0ChainID, + EthCompatibleShard0ChainID: EthStressnetShard0ChainID, + EthCompatibleEpoch: big.NewInt(0), + CrossTxEpoch: big.NewInt(0), + CrossLinkEpoch: big.NewInt(2), + AggregatedRewardEpoch: big.NewInt(3), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(1), + QuickUnlockEpoch: big.NewInt(0), + FiveSecondsEpoch: big.NewInt(0), + TwoSecondsEpoch: big.NewInt(0), + SixtyPercentEpoch: big.NewInt(10), + RedelegationEpoch: big.NewInt(0), + NoEarlyUnlockEpoch: big.NewInt(0), + VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), + MinDelegation100Epoch: big.NewInt(0), + MinCommissionRateEpoch: big.NewInt(0), + MinCommissionPromoPeriod: big.NewInt(10), + EPoSBound35Epoch: big.NewInt(0), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), + DataCopyFixEpoch: big.NewInt(0), + IstanbulEpoch: big.NewInt(0), + ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), + HIP6And8Epoch: big.NewInt(0), + StakingPrecompileEpoch: big.NewInt(2), + ChainIdFixEpoch: big.NewInt(0), + SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 + CrossShardXferPrecompileEpoch: big.NewInt(1), + AllowlistEpoch: EpochTBD, + FeeCollectEpoch: EpochTBD, + LeaderRotationExternalNonBeaconLeaders: EpochTBD, + LeaderRotationExternalBeaconLeaders: EpochTBD, + ValidatorCodeFixEpoch: EpochTBD, } // LocalnetChainConfig contains the chain parameters to run for local development. LocalnetChainConfig = &ChainConfig{ - ChainID: TestnetChainID, - EthCompatibleChainID: EthTestnetShard0ChainID, - EthCompatibleShard0ChainID: EthTestnetShard0ChainID, - EthCompatibleEpoch: big.NewInt(0), - CrossTxEpoch: big.NewInt(0), - CrossLinkEpoch: big.NewInt(2), - AggregatedRewardEpoch: big.NewInt(3), - StakingEpoch: big.NewInt(2), - PreStakingEpoch: big.NewInt(0), - QuickUnlockEpoch: big.NewInt(0), - FiveSecondsEpoch: big.NewInt(0), - TwoSecondsEpoch: big.NewInt(0), - SixtyPercentEpoch: EpochTBD, // Never enable it for localnet as localnet has no external validator setup - RedelegationEpoch: big.NewInt(0), - NoEarlyUnlockEpoch: big.NewInt(0), - VRFEpoch: big.NewInt(0), - PrevVRFEpoch: big.NewInt(0), - MinDelegation100Epoch: big.NewInt(0), - MinCommissionRateEpoch: big.NewInt(0), - MinCommissionPromoPeriod: big.NewInt(10), - EPoSBound35Epoch: big.NewInt(0), - EIP155Epoch: big.NewInt(0), - S3Epoch: big.NewInt(0), - DataCopyFixEpoch: big.NewInt(0), - IstanbulEpoch: big.NewInt(0), - ReceiptLogEpoch: big.NewInt(0), - SHA3Epoch: big.NewInt(0), - HIP6And8Epoch: EpochTBD, // Never enable it for localnet as localnet has no external validator setup - StakingPrecompileEpoch: big.NewInt(2), - ChainIdFixEpoch: big.NewInt(0), - SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 - CrossShardXferPrecompileEpoch: big.NewInt(1), - AllowlistEpoch: EpochTBD, - LeaderRotationEpoch: EpochTBD, - LeaderRotationBlocksCount: 5, - FeeCollectEpoch: big.NewInt(5), - ValidatorCodeFixEpoch: EpochTBD, + ChainID: TestnetChainID, + EthCompatibleChainID: EthTestnetShard0ChainID, + EthCompatibleShard0ChainID: EthTestnetShard0ChainID, + EthCompatibleEpoch: big.NewInt(0), + CrossTxEpoch: big.NewInt(0), + CrossLinkEpoch: big.NewInt(2), + AggregatedRewardEpoch: big.NewInt(3), + StakingEpoch: big.NewInt(2), + PreStakingEpoch: big.NewInt(0), + QuickUnlockEpoch: big.NewInt(0), + FiveSecondsEpoch: big.NewInt(0), + TwoSecondsEpoch: big.NewInt(0), + SixtyPercentEpoch: EpochTBD, // Never enable it for localnet as localnet has no external validator setup + RedelegationEpoch: big.NewInt(0), + NoEarlyUnlockEpoch: big.NewInt(0), + VRFEpoch: big.NewInt(0), + PrevVRFEpoch: big.NewInt(0), + MinDelegation100Epoch: big.NewInt(0), + MinCommissionRateEpoch: big.NewInt(0), + MinCommissionPromoPeriod: big.NewInt(10), + EPoSBound35Epoch: big.NewInt(0), + EIP155Epoch: big.NewInt(0), + S3Epoch: big.NewInt(0), + DataCopyFixEpoch: big.NewInt(0), + IstanbulEpoch: big.NewInt(0), + ReceiptLogEpoch: big.NewInt(0), + SHA3Epoch: big.NewInt(0), + HIP6And8Epoch: EpochTBD, // Never enable it for localnet as localnet has no external validator setup + StakingPrecompileEpoch: big.NewInt(2), + ChainIdFixEpoch: big.NewInt(0), + SlotsLimitedEpoch: EpochTBD, // epoch to enable HIP-16 + CrossShardXferPrecompileEpoch: big.NewInt(1), + AllowlistEpoch: EpochTBD, + LeaderRotationExternalNonBeaconLeaders: big.NewInt(5), + LeaderRotationExternalBeaconLeaders: big.NewInt(6), + FeeCollectEpoch: big.NewInt(5), + ValidatorCodeFixEpoch: EpochTBD, } // AllProtocolChanges ... @@ -319,8 +319,8 @@ var ( big.NewInt(0), // SlotsLimitedEpoch big.NewInt(1), // CrossShardXferPrecompileEpoch big.NewInt(0), // AllowlistEpoch - big.NewInt(1), // LeaderRotationEpoch - 64, // LeaderRotationBlocksCount + big.NewInt(1), // LeaderRotationExternalNonBeaconLeaders + big.NewInt(1), // LeaderRotationExternalBeaconLeaders big.NewInt(0), // FeeCollectEpoch big.NewInt(0), // ValidatorCodeFixEpoch } @@ -362,8 +362,8 @@ var ( big.NewInt(0), // SlotsLimitedEpoch big.NewInt(1), // CrossShardXferPrecompileEpoch big.NewInt(0), // AllowlistEpoch - big.NewInt(1), // LeaderRotationEpoch - 64, // LeaderRotationBlocksCount + big.NewInt(1), // LeaderRotationExternalNonBeaconLeaders + big.NewInt(1), // LeaderRotationExternalBeaconLeaders big.NewInt(0), // FeeCollectEpoch big.NewInt(0), // ValidatorCodeFixEpoch } @@ -505,9 +505,9 @@ type ChainConfig struct { // AllowlistEpoch is the first epoch to support allowlist of HIP18 AllowlistEpoch *big.Int - LeaderRotationEpoch *big.Int `json:"leader-rotation-epoch,omitempty"` + LeaderRotationExternalNonBeaconLeaders *big.Int `json:"leader-rotation-external-non-beacon-leaders,omitempty"` - LeaderRotationBlocksCount int `json:"leader-rotation-blocks-count,omitempty"` + LeaderRotationExternalBeaconLeaders *big.Int `json:"leader-rotation-external-beacon-leaders,omitempty"` // FeeCollectEpoch is the first epoch that enables txn fees to be collected into the community-managed account. // It should >= StakingEpoch. @@ -725,7 +725,17 @@ func (c *ChainConfig) IsAllowlistEpoch(epoch *big.Int) bool { } func (c *ChainConfig) IsLeaderRotation(epoch *big.Int) bool { - return isForked(c.LeaderRotationEpoch, epoch) + return isForked(c.LeaderRotationExternalNonBeaconLeaders, epoch) +} + +func (c *ChainConfig) IsLeaderRotationExternalValidatorsAllowed(epoch *big.Int, shardID uint32) bool { + if !c.IsLeaderRotation(epoch) { + return false + } + if shardID == 0 { + return isForked(c.LeaderRotationExternalBeaconLeaders, epoch) + } + return true } // IsFeeCollectEpoch determines whether Txn Fees will be collected into the community-managed account. diff --git a/numeric/decimal.go b/numeric/decimal.go index e4236daa4..f80c9eca1 100644 --- a/numeric/decimal.go +++ b/numeric/decimal.go @@ -202,6 +202,12 @@ func (d Dec) Copy() Dec { } } +func (d Dec) Div(d2 Dec) Dec { + return Dec{ + new(big.Int).Div(d.Int, d2.Int), + } +} + // IsNil ... func (d Dec) IsNil() bool { return d.Int == nil } // is decimal nil // IsZero ... diff --git a/numeric/decimal_test.go b/numeric/decimal_test.go index a7937d9a7..7b790f20d 100644 --- a/numeric/decimal_test.go +++ b/numeric/decimal_test.go @@ -368,3 +368,27 @@ func TestDecCeil(t *testing.T) { require.Equal(t, tc.expected, res, "unexpected result for test case %d, input: %v", i, tc.input) } } + +func TestDiv(t *testing.T) { + tests := []struct { + d1, d2, exp Dec + }{ + {mustNewDecFromStr(t, "0"), mustNewDecFromStr(t, "1"), ZeroDec()}, + {mustNewDecFromStr(t, "1"), mustNewDecFromStr(t, "1"), NewDec(1)}, + {mustNewDecFromStr(t, "1"), mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "0.5")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "1"), NewDec(2)}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "3"), mustNewDecFromStr(t, "0.666666666666666667")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "4"), mustNewDecFromStr(t, "0.5")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "5"), mustNewDecFromStr(t, "0.4")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "6"), mustNewDecFromStr(t, "0.333333333333333333")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "7"), mustNewDecFromStr(t, "0.285714285714285714")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "8"), mustNewDecFromStr(t, "0.25")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "9"), mustNewDecFromStr(t, "0.222222222222222222")}, + {mustNewDecFromStr(t, "2"), mustNewDecFromStr(t, "10"), mustNewDecFromStr(t, "0.2")}, + } + for i, tc := range tests { + res := tc.d1.Quo(tc.d2) + require.True(t, res.Equal(tc.exp), "unexpected result for test case %d, input: %s %s %s", i, tc.d1, tc.d2, tc.exp) + } + +}