diff --git a/core/state/candidates/candidates.go b/core/state/candidates/candidates.go index d9ec607c7..05ab96a27 100644 --- a/core/state/candidates/candidates.go +++ b/core/state/candidates/candidates.go @@ -10,6 +10,7 @@ import ( "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" "github.com/MinterTeam/minter-go-node/tree" + "github.com/MinterTeam/minter-go-node/upgrades" "math/big" "sort" "sync" @@ -219,6 +220,16 @@ func (c *Candidates) GetCandidateByTendermintAddress(address types.TmAddress) *C } func (c *Candidates) RecalculateStakes(height uint64) { + if height >= upgrades.UpgradeBlock3 { + c.recalculateStakesNew(height) + } else if height >= upgrades.UpgradeBlock2 { + c.recalculateStakesOld2(height) + } else { + c.recalculateStakesOld1(height) + } +} + +func (c *Candidates) recalculateStakesOld1(height uint64) { coinsCache := newCoinsCache() for _, pubkey := range c.getOrderedCandidates() { @@ -313,6 +324,199 @@ func (c *Candidates) RecalculateStakes(height uint64) { } } +func (c *Candidates) recalculateStakesOld2(height uint64) { + coinsCache := newCoinsCache() + + for _, pubkey := range c.getOrderedCandidates() { + candidate := c.getFromMap(pubkey) + stakes := c.GetStakes(candidate.PubKey) + for _, stake := range stakes { + stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) + } + + // apply updates for existing stakes + candidate.FilterUpdates() + for _, update := range candidate.updates { + stake := c.GetStakeOfAddress(candidate.PubKey, update.Owner, update.Coin) + if stake != nil { + stake.addValue(update.Value) + update.setValue(big.NewInt(0)) + stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) + } + } + + candidate.FilterUpdates() + for _, update := range candidate.updates { + update.setBipValue(c.calculateBipValue(update.Coin, update.Value, false, true, coinsCache)) + } + + for _, update := range candidate.updates { + // find and replace smallest stake + index := -1 + var smallestStake *big.Int + + if len(stakes) == 0 { + index = 0 + smallestStake = big.NewInt(0) + } else if len(stakes) < MaxDelegatorsPerCandidate { + for i, stake := range stakes { + if stake == nil { + index = i + break + } + } + + if index == -1 { + index = len(stakes) + } + + smallestStake = big.NewInt(0) + } else { + for i, stake := range stakes { + if stake == nil { + index = i + smallestStake = big.NewInt(0) + break + } + + if smallestStake == nil || smallestStake.Cmp(stake.BipValue) == 1 { + smallestStake = big.NewInt(0).Set(stake.BipValue) + index = i + } + } + } + + if index == -1 || smallestStake.Cmp(update.BipValue) == 1 { + c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ + Address: update.Owner, + Amount: update.Value.String(), + Coin: update.Coin, + ValidatorPubKey: candidate.PubKey, + }) + c.bus.Accounts().AddBalance(update.Owner, update.Coin, update.Value) + c.bus.Checker().AddCoin(update.Coin, big.NewInt(0).Neg(update.Value)) + update.setValue(big.NewInt(0)) + continue + } + + if len(stakes) > index && stakes[index] != nil { + c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ + Address: stakes[index].Owner, + Amount: stakes[index].Value.String(), + Coin: stakes[index].Coin, + ValidatorPubKey: candidate.PubKey, + }) + c.bus.Accounts().AddBalance(stakes[index].Owner, stakes[index].Coin, stakes[index].Value) + c.bus.Checker().AddCoin(stakes[index].Coin, big.NewInt(0).Neg(stakes[index].Value)) + } + + candidate.SetStakeAtIndex(index, update, true) + stakes = c.GetStakes(candidate.PubKey) + } + + candidate.clearUpdates() + + totalBipValue := big.NewInt(0) + for _, stake := range c.GetStakes(candidate.PubKey) { + if stake == nil { + continue + } + totalBipValue.Add(totalBipValue, stake.BipValue) + } + + candidate.setTotalBipStake(totalBipValue) + candidate.updateStakesCount() + } +} + +func (c *Candidates) recalculateStakesNew(height uint64) { + coinsCache := newCoinsCache() + + for _, pubkey := range c.getOrderedCandidates() { + candidate := c.getFromMap(pubkey) + stakes := &candidate.stakes + for _, stake := range stakes { + if stake == nil { + continue + } + stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) + } + + // apply updates for existing stakes + for _, update := range candidate.updates { + stake := c.GetStakeOfAddress(candidate.PubKey, update.Owner, update.Coin) + if stake != nil { + stake.addValue(update.Value) + update.setValue(big.NewInt(0)) + stake.setBipValue(c.calculateBipValue(stake.Coin, stake.Value, false, true, coinsCache)) + } + } + + candidate.FilterUpdates() + for _, update := range candidate.updates { + update.setBipValue(c.calculateBipValue(update.Coin, update.Value, false, true, coinsCache)) + } + + for _, update := range candidate.updates { + // find and replace smallest stake + index := -1 + var smallestStake *big.Int + + for i, stake := range stakes { + if stake == nil { + index = i + smallestStake = big.NewInt(0) + break + } + + if smallestStake == nil || smallestStake.Cmp(stake.BipValue) == 1 { + smallestStake = big.NewInt(0).Set(stake.BipValue) + index = i + } + } + + if index == -1 || smallestStake.Cmp(update.BipValue) == 1 { + c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ + Address: update.Owner, + Amount: update.Value.String(), + Coin: update.Coin, + ValidatorPubKey: candidate.PubKey, + }) + c.bus.Accounts().AddBalance(update.Owner, update.Coin, update.Value) + c.bus.Checker().AddCoin(update.Coin, big.NewInt(0).Neg(update.Value)) + update.setValue(big.NewInt(0)) + continue + } + + if len(stakes) > index && stakes[index] != nil { + c.bus.Events().AddEvent(uint32(height), eventsdb.UnbondEvent{ + Address: stakes[index].Owner, + Amount: stakes[index].Value.String(), + Coin: stakes[index].Coin, + ValidatorPubKey: candidate.PubKey, + }) + c.bus.Accounts().AddBalance(stakes[index].Owner, stakes[index].Coin, stakes[index].Value) + c.bus.Checker().AddCoin(stakes[index].Coin, big.NewInt(0).Neg(stakes[index].Value)) + } + + candidate.SetStakeAtIndex(index, update, true) + } + + candidate.clearUpdates() + + totalBipValue := big.NewInt(0) + for _, stake := range stakes { + if stake == nil { + continue + } + totalBipValue.Add(totalBipValue, stake.BipValue) + } + + candidate.setTotalBipStake(totalBipValue) + candidate.updateStakesCount() + } +} + func (c *Candidates) Exists(pubkey types.Pubkey) bool { c.lock.RLock() defer c.lock.RUnlock() diff --git a/core/state/candidates/model.go b/core/state/candidates/model.go index 5d800d2c1..6b6830b09 100644 --- a/core/state/candidates/model.go +++ b/core/state/candidates/model.go @@ -4,6 +4,7 @@ import ( "github.com/MinterTeam/minter-go-node/core/types" "github.com/tendermint/tendermint/crypto/ed25519" "math/big" + "sort" ) type Candidate struct { @@ -105,6 +106,37 @@ func (candidate *Candidate) GetFilteredUpdates() []*Stake { return updates } +func (candidate *Candidate) FilterUpdates() { + var updates []*Stake + for _, update := range candidate.updates { + // skip updates with 0 stakes + if update.Value.Cmp(big.NewInt(0)) != 1 { + continue + } + + // merge updates + merged := false + for _, u := range updates { + if u.Coin == update.Coin && u.Owner == update.Owner { + u.Value.Add(u.Value, update.Value) + merged = true + break + } + } + + if !merged { + updates = append(updates, update) + } + } + + sort.SliceStable(updates, func(i, j int) bool { + return updates[i].BipValue.Cmp(updates[j].BipValue) == 1 + }) + + candidate.updates = updates + candidate.isUpdatesDirty = true +} + func (candidate *Candidate) updateStakesCount() { count := 0 for _, stake := range candidate.stakes { diff --git a/core/state/candidates_test.go b/core/state/candidates_test.go index 6ca43b1d6..2162f4cc9 100644 --- a/core/state/candidates_test.go +++ b/core/state/candidates_test.go @@ -6,12 +6,15 @@ import ( eventsdb "github.com/MinterTeam/events-db" "github.com/MinterTeam/minter-go-node/core/state/candidates" "github.com/MinterTeam/minter-go-node/core/types" + "github.com/MinterTeam/minter-go-node/upgrades" "github.com/tendermint/tendermint/crypto/ed25519" db "github.com/tendermint/tm-db" "math/big" "testing" ) +const height = upgrades.UpgradeBlock2 + func TestSimpleDelegate(t *testing.T) { st := getState() @@ -21,7 +24,7 @@ func TestSimpleDelegate(t *testing.T) { pubkey := createTestCandidate(st) st.Candidates.Delegate(address, pubkey, coin, amount, big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) stake := st.Candidates.GetStakeOfAddress(pubkey, address, coin) if stake == nil { @@ -51,7 +54,7 @@ func TestDelegate(t *testing.T) { totalAmount.Add(totalAmount, amount) } - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) stake := st.Candidates.GetStakeOfAddress(pubkey, address, coin) if stake == nil { @@ -80,7 +83,7 @@ func TestComplexDelegate(t *testing.T) { st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) } - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) for i := uint64(0); i < 1000; i++ { var addr types.Address @@ -116,7 +119,7 @@ func TestComplexDelegate(t *testing.T) { binary.BigEndian.PutUint64(addr[:], 3000) st.Candidates.Delegate(addr, pubkey, coin, big.NewInt(3000), big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) replacedAddress := types.HexToAddress("Mx00000000000003e7000000000000000000000000") stake := st.Candidates.GetStakeOfAddress(pubkey, replacedAddress, coin) @@ -139,7 +142,7 @@ func TestComplexDelegate(t *testing.T) { binary.BigEndian.PutUint64(addr2[:], 3500) st.Candidates.Delegate(addr2, pubkey, coin, big.NewInt(3500), big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) stake := st.Candidates.GetStakeOfAddress(pubkey, addr, coin) if stake == nil { @@ -158,7 +161,7 @@ func TestComplexDelegate(t *testing.T) { binary.BigEndian.PutUint64(addr[:], 4001) st.Candidates.Delegate(addr, pubkey, coin, big.NewInt(900), big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) stake := st.Candidates.GetStakeOfAddress(pubkey, addr, coin) if stake != nil { @@ -180,7 +183,7 @@ func TestStakeSufficiency(t *testing.T) { st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) } - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) { stake := big.NewInt(1) @@ -227,7 +230,7 @@ func TestDoubleSignPenalty(t *testing.T) { binary.BigEndian.PutUint64(addr[:], 1) st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) var pk ed25519.PubKeyEd25519 copy(pk[:], pubkey[:]) @@ -274,7 +277,7 @@ func TestAbsentPenalty(t *testing.T) { binary.BigEndian.PutUint64(addr[:], 1) st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) var pk ed25519.PubKeyEd25519 copy(pk[:], pubkey[:]) @@ -305,7 +308,7 @@ func TestDoubleAbsentPenalty(t *testing.T) { st.Candidates.Delegate(addr, pubkey, coin, amount, big.NewInt(0)) st.Candidates.SetOnline(pubkey) - st.Candidates.RecalculateStakes(1) + st.Candidates.RecalculateStakes(height) var pk ed25519.PubKeyEd25519 copy(pk[:], pubkey[:]) diff --git a/core/transaction/declare_candidacy_test.go b/core/transaction/declare_candidacy_test.go index 4749cf40d..48e710afb 100644 --- a/core/transaction/declare_candidacy_test.go +++ b/core/transaction/declare_candidacy_test.go @@ -8,6 +8,7 @@ import ( "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/upgrades" "math/big" "sync" "testing" @@ -113,7 +114,7 @@ func TestDeclareCandidacyTxOverflow(t *testing.T) { cState.Candidates.Delegate(types.Address{}, pubkey, types.GetBaseCoin(), helpers.BipToPip(big.NewInt(10)), helpers.BipToPip(big.NewInt(10))) } - cState.Candidates.RecalculateStakes(0) + cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock2) privateKey, _ := crypto.GenerateKey() addr := crypto.PubkeyToAddress(privateKey.PublicKey) diff --git a/core/transaction/delegate_test.go b/core/transaction/delegate_test.go index df108c344..c70d8235f 100644 --- a/core/transaction/delegate_test.go +++ b/core/transaction/delegate_test.go @@ -6,6 +6,7 @@ import ( "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/upgrades" "math/big" "math/rand" "sync" @@ -80,7 +81,7 @@ func TestDelegateTx(t *testing.T) { t.Fatalf("Target %s balance is not correct. Expected %s, got %s", coin, targetBalance, balance) } - cState.Candidates.RecalculateStakes(1) + cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock2) stake := cState.Candidates.GetStakeOfAddress(pubkey, addr, coin) diff --git a/core/transaction/unbond_test.go b/core/transaction/unbond_test.go index 6c7ee808e..69e6efff9 100644 --- a/core/transaction/unbond_test.go +++ b/core/transaction/unbond_test.go @@ -5,6 +5,7 @@ import ( "github.com/MinterTeam/minter-go-node/crypto" "github.com/MinterTeam/minter-go-node/helpers" "github.com/MinterTeam/minter-go-node/rlp" + "github.com/MinterTeam/minter-go-node/upgrades" "math/big" "sync" "testing" @@ -24,7 +25,7 @@ func TestUnbondTx(t *testing.T) { value := helpers.BipToPip(big.NewInt(100)) cState.Candidates.Delegate(addr, pubkey, coin, value, big.NewInt(0)) - cState.Candidates.RecalculateStakes(1) + cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock2) data := UnbondData{ PubKey: pubkey, @@ -64,7 +65,7 @@ func TestUnbondTx(t *testing.T) { t.Fatalf("Response code is not 0. Error %s", response.Log) } - cState.Candidates.RecalculateStakes(1) + cState.Candidates.RecalculateStakes(upgrades.UpgradeBlock2) targetBalance, _ := big.NewInt(0).SetString("999999800000000000000000", 10) balance := cState.Accounts.GetBalance(addr, coin) diff --git a/upgrades/blocks.go b/upgrades/blocks.go index 97617c5bf..785f7f300 100644 --- a/upgrades/blocks.go +++ b/upgrades/blocks.go @@ -1,3 +1,5 @@ package upgrades const UpgradeBlock1 = 0 +const UpgradeBlock2 = 0 +const UpgradeBlock3 = 0 diff --git a/upgrades/grace.go b/upgrades/grace.go index 98540e9f6..869fa4bc0 100644 --- a/upgrades/grace.go +++ b/upgrades/grace.go @@ -3,6 +3,8 @@ package upgrades var gracePeriods = []*gracePeriod{ NewGracePeriod(1, 120), NewGracePeriod(UpgradeBlock1, UpgradeBlock1+120), + NewGracePeriod(UpgradeBlock2, UpgradeBlock2+120), + NewGracePeriod(UpgradeBlock3, UpgradeBlock3+120), } type gracePeriod struct { diff --git a/version/version.go b/version/version.go index 36371a42a..6d46097ee 100755 --- a/version/version.go +++ b/version/version.go @@ -4,14 +4,14 @@ package version const ( Maj = "1" Min = "1" - Fix = "1" + Fix = "3" AppVer = 6 ) var ( // Must be a string because scripts like dist.sh read this file. - Version = "1.1.1-testnet" + Version = "1.1.3-testnet" // GitCommit is the current HEAD set using ldflags. GitCommit string