func (c *Candidates) CreateWithID(ownerAddress, rewardAddress, controlAddress types.Address, pubkey types.Pubkey, commission, id uint32, commissionEditHeight, jail uint64) {
	c.setPubKeyID(pubkey, id)
	c.Create(ownerAddress, rewardAddress, controlAddress, pubkey, commission, commissionEditHeight, jail)
}
CreateWithID creates a new candidate with given params and adds it to state
CreateWithID uses given ID to be associated with public key of a candidate
package candidates
import (
	"bytes"
	"encoding/binary"
	"fmt"
	"sync/atomic"
	eventsdb "github.com/MinterTeam/minter-go-node/coreV2/events"
	"github.com/MinterTeam/minter-go-node/coreV2/state/bus"
	"github.com/MinterTeam/minter-go-node/coreV2/types"
	"github.com/MinterTeam/minter-go-node/formula"
	"github.com/MinterTeam/minter-go-node/helpers"
	"github.com/MinterTeam/minter-go-node/rlp"
	"github.com/cosmos/iavl"
	"math/big"
	"sort"
	"sync"
)
const (
	CandidateStatusOffline = 0x01
	CandidateStatusOnline  = 0x02
	MaxDelegatorsPerCandidate = 1000
)
const (
	mainPrefix       = 'c'
	pubKeyIDPrefix   = mainPrefix + 'p'
	blockListPrefix  = mainPrefix + 'b'
	maxIDPrefix      = mainPrefix + 'i'
	stakesPrefix     = 's'
	totalStakePrefix = 't'
	updatesPrefix    = 'u'
)
var (
	minValidatorBipStake = helpers.BipToPip(big.NewInt(1000))
)
type RCandidates interface {
	ExportV1(state *types.AppState, height uint64, validators []*types.Candidate) []uint32
	Export(state *types.AppState)
	Exists(pubkey types.Pubkey) bool
	IsBlockedPubKey(pubkey types.Pubkey) bool
	PubKey(id uint32) types.Pubkey
	Count() int
	IsNewCandidateStakeSufficient(coin types.CoinID, stake *big.Int, limit int) bool
	IsDelegatorStakeSufficient(address types.Address, pubkey types.Pubkey, coin types.CoinID, amount *big.Int) bool
	GetStakeValueOfAddress(pubkey types.Pubkey, address types.Address, coin types.CoinID) *big.Int
	GetCandidateOwner(pubkey types.Pubkey) types.Address
	GetCandidateControl(pubkey types.Pubkey) types.Address
	GetTotalStake(pubkey types.Pubkey) *big.Int
	LoadCandidates()
	LoadStakesOfCandidate(pubkey types.Pubkey)
	GetCandidate(pubkey types.Pubkey) *Candidate
	LoadStakes()
	GetCandidates() []*Candidate
	GetStakes(pubkey types.Pubkey) []*stake
	IsCandidateJailed(pubkey types.Pubkey, block uint64) bool
}
type Candidates struct {
	list map[uint32]*Candidate
	isDirty   bool
	blockList map[types.Pubkey]struct{}
	pubKeyIDs map[types.Pubkey]uint32
	maxID     uint32
	db  atomic.Value
	bus *bus.Bus
	lock                sync.RWMutex
	loaded              bool
	isChangedPublicKeys bool
}
func NewCandidates(bus *bus.Bus, db *iavl.ImmutableTree) *Candidates {
	immutableTree := atomic.Value{}
	loaded := false
	if db != nil {
		immutableTree.Store(db)
	} else {
		loaded = true
	}
	candidates := &Candidates{
		db:        immutableTree,
		loaded:    loaded,
		bus:       bus,
		blockList: map[types.Pubkey]struct{}{},
		pubKeyIDs: map[types.Pubkey]uint32{},
		list:      map[uint32]*Candidate{},
	}
	candidates.bus.SetCandidates(NewBus(candidates))
	return candidates
}
func (c *Candidates) immutableTree() *iavl.ImmutableTree {
	db := c.db.Load()
	if db == nil {
		return nil
	}
	return db.(*iavl.ImmutableTree)
}
func (c *Candidates) SetImmutableTree(immutableTree *iavl.ImmutableTree) {
	if c.immutableTree() == nil && c.loaded {
		c.loaded = false
	}
	c.db.Store(immutableTree)
}
func (c *Candidates) IsChangedPublicKeys() bool {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.isChangedPublicKeys
}
func (c *Candidates) ResetIsChangedPublicKeys() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.isChangedPublicKeys = false
}
func (c *Candidates) IsCandidateJailed(pubkey types.Pubkey, block uint64) bool {
	candidate := c.GetCandidate(pubkey)
	return candidate.JailedUntil >= block
}
func (c *Candidates) Commit(db *iavl.MutableTree, version int64) error {
	keys := c.getOrderedCandidates()
	hasDirty := false
	for _, candidate := range keys {
		candidate.lock.RLock()
		if candidate.isDirty {
			hasDirty = true
			candidate.lock.RUnlock()
			break
		}
		candidate.lock.RUnlock()
	}
	if hasDirty {
		var candidates []Candidate
		for _, candidate := range keys {
			candidates = append(candidates, *candidate)
		}
		data, err := rlp.EncodeToBytes(candidates)
		if err != nil {
			return fmt.Errorf("can't encode candidates: %v", err)
		}
		path := []byte{mainPrefix}
		db.Set(path, data)
	}
	c.lock.Lock()
	if c.isDirty {
		c.isDirty = false
		var pubIDs []pubkeyID
		for pk, v := range c.pubKeyIDs {
			pubIDs = append(pubIDs, pubkeyID{
				PubKey: pk,
				ID:     v,
			})
		}
		sort.SliceStable(pubIDs, func(i, j int) bool {
			return pubIDs[i].ID < pubIDs[j].ID
		})
		pubIDData, err := rlp.EncodeToBytes(pubIDs)
		if err != nil {
			panic(fmt.Sprintf("failed to encode candidates public key with ID: %s", err))
		}
		db.Set([]byte{pubKeyIDPrefix}, pubIDData)
		var blockList []types.Pubkey
		for pubKey := range c.blockList {
			blockList = append(blockList, pubKey)
		}
		sort.SliceStable(blockList, func(i, j int) bool {
			return bytes.Compare(blockList[i].Bytes(), blockList[j].Bytes()) == 1
		})
		blockListData, err := rlp.EncodeToBytes(blockList)
		if err != nil {
			return fmt.Errorf("can't encode block list of candidates: %v", err)
		}
		db.Set([]byte{blockListPrefix}, blockListData)
		db.Set([]byte{maxIDPrefix}, c.maxIDBytes())
	}
	c.lock.Unlock()
	for _, candidate := range keys {
		candidate.lock.Lock()
		candidate.isDirty = false
		dirty := candidate.isTotalStakeDirty
		candidate.lock.Unlock()
		if dirty {
			candidate.lock.Lock()
			candidate.isTotalStakeDirty = false
			totalStakeBytes := candidate.totalBipStake.Bytes()
			candidate.lock.Unlock()
			path := []byte{mainPrefix}
			path = append(path, candidate.idBytes()...)
			path = append(path, totalStakePrefix)
			db.Set(path, totalStakeBytes)
		}
		for index, stake := range candidate.stakes {
			candidate.lock.RLock()
			dirtyStakes := candidate.dirtyStakes[index]
			candidate.lock.RUnlock()
			if !dirtyStakes {
				continue
			}
			candidate.lock.Lock()
			candidate.dirtyStakes[index] = false
			candidate.lock.Unlock()
			path := []byte{mainPrefix}
			path = append(path, candidate.idBytes()...)
			path = append(path, stakesPrefix)
			path = append(path, big.NewInt(int64(index)).Bytes()...)
			isEmpty := stake == nil
			if !isEmpty {
				stake.lock.RLock()
				isEmpty = stake.Value.Sign() == 0
				stake.lock.RUnlock()
			}
			if isEmpty {
				db.Remove(path)
				candidate.lock.Lock()
				candidate.stakes[index] = nil
				candidate.lock.Unlock()
				continue
			}
			data, err := rlp.EncodeToBytes(stake)
			if err != nil {
				return fmt.Errorf("can't encode stake: %v", err)
			}
			db.Set(path, data)
		}
		candidate.lock.RLock()
		updatesDirty := candidate.isUpdatesDirty
		candidate.lock.RUnlock()
		if updatesDirty {
			candidate.lock.Lock()
			candidate.isUpdatesDirty = false
			data, err := rlp.EncodeToBytes(candidate.updates)
			candidate.lock.Unlock()
			if err != nil {
				return fmt.Errorf("can't encode candidates updates: %v", err)
			}
			path := []byte{mainPrefix}
			path = append(path, candidate.idBytes()...)
			path = append(path, updatesPrefix)
			db.Set(path, data)
		}
	}
	return nil
}
func (c *Candidates) GetNewCandidates(valCount int) []*Candidate {
	var result []*Candidate
	candidates := c.GetCandidates()
	for _, candidate := range candidates {
		if candidate.GetStatus() != CandidateStatusOnline {
			continue
		}
		if candidate.GetTotalBipStake().Cmp(minValidatorBipStake) == -1 {
			continue
		}
		result = append(result, candidate)
	}
	if len(result) > valCount {
		result = result[:valCount]
	}
	return result
}
func (c *Candidates) Create(ownerAddress, rewardAddress, controlAddress types.Address, pubkey types.Pubkey, commission uint32, block uint64, jailedUntil uint64) {
	candidate := &Candidate{
		ID:                       0,
		PubKey:                   pubkey,
		RewardAddress:            rewardAddress,
		OwnerAddress:             ownerAddress,
		ControlAddress:           controlAddress,
		Commission:               commission,
		LastEditCommissionHeight: block,
		JailedUntil:              jailedUntil,
		Status:                   CandidateStatusOffline,
		totalBipStake:            big.NewInt(0),
		stakes:                   [MaxDelegatorsPerCandidate]*stake{},
		isDirty:                  true,
		isTotalStakeDirty:        true,
	}
	candidate.setTmAddress()
	c.setToMap(pubkey, candidate)
}