diff --git a/pkg/ride/diff_state.go b/pkg/ride/diff_state.go index 612befe38c..f84a05d77e 100644 --- a/pkg/ride/diff_state.go +++ b/pkg/ride/diff_state.go @@ -32,6 +32,7 @@ type diffBalance struct { leaseIn int64 leaseOut int64 stateGenerating int64 + challenged bool } func (db *diffBalance) addBalance(amount int64) error { @@ -88,6 +89,9 @@ func (db *diffBalance) checkedSpendableBalance() (uint64, error) { } func (db *diffBalance) effectiveBalance() (int64, error) { + if db.challenged { + return 0, nil // challenged account has zero effective balance at the current block + } v1, err := common.AddInt(db.balance, db.leaseIn) if err != nil { return 0, err @@ -240,6 +244,7 @@ func (ds *diffState) loadWavesBalance(id proto.AddressID) (diffBalance, error) { leaseIn: profile.LeaseIn, leaseOut: profile.LeaseOut, stateGenerating: int64(profile.Generating), + challenged: profile.Challenged, } // Store new diff locally ds.wavesBalances[id] = diff diff --git a/pkg/state/balances.go b/pkg/state/balances.go index af60dd2986..2fed4a141c 100644 --- a/pkg/state/balances.go +++ b/pkg/state/balances.go @@ -35,7 +35,9 @@ type balanceProfile struct { leaseOut int64 } -func (bp *balanceProfile) effectiveBalance() (uint64, error) { +// effectiveBalanceUnchecked returns effective balance without checking for account challenging. +// The function MUST be used ONLY in the context where account challenging IS CHECKED. +func (bp *balanceProfile) effectiveBalanceUnchecked() (uint64, error) { val, err := common.AddInt(int64(bp.balance), bp.leaseIn) if err != nil { return 0, err @@ -43,6 +45,23 @@ func (bp *balanceProfile) effectiveBalance() (uint64, error) { return uint64(val - bp.leaseOut), nil } +type challengedChecker func(proto.AddressID, proto.Height) (bool, error) + +func (bp *balanceProfile) effectiveBalance( + challengedCheck challengedChecker, // Function to check if the account is challenged. + addrID proto.AddressID, // Address ID of the current balanceProfile. + currentHeight proto.Height, // Current height. +) (uint64, error) { + challenged, err := challengedCheck(addrID, currentHeight) + if err != nil { + return 0, err + } + if challenged { + return 0, nil // Challenged account has 0 effective balance. + } + return bp.effectiveBalanceUnchecked() +} + func (bp *balanceProfile) spendableBalance() uint64 { return uint64(int64(bp.balance) - bp.leaseOut) } @@ -577,7 +596,7 @@ func minEffectiveBalanceInRangeCommon(records [][]byte) (uint64, error) { if err := record.unmarshalBinary(recordBytes); err != nil { return 0, err } - effectiveBal, err := record.effectiveBalance() + effectiveBal, err := record.effectiveBalanceUnchecked() if err != nil { return 0, err } @@ -701,6 +720,8 @@ func (s *balances) storeChallengeHeightForAddr( type entryDataGetter func(key []byte) ([]byte, error) +// isChallengedAddressInRangeCommon checks if the address was challenged in the given range of heights. +// startHeight and endHeight are inclusive. func isChallengedAddressInRangeCommon( getEntryData entryDataGetter, addr proto.AddressID, @@ -759,6 +780,14 @@ func (s *balances) isChallengedAddressInRange(addr proto.AddressID, startHeight, return isChallengedAddressInRangeCommon(s.hs.topEntryData, addr, startHeight, endHeight) } +func (s *balances) isChallengedAddress(addr proto.AddressID, height proto.Height) (bool, error) { + var ( + startHeight = height + endHeight = startHeight // we're checking only one height, so the heights are the same + ) + return s.isChallengedAddressInRange(addr, startHeight, endHeight) +} + func (s *balances) newestIsChallengedAddressInRange( addr proto.AddressID, startHeight, endHeight proto.Height, @@ -766,6 +795,14 @@ func (s *balances) newestIsChallengedAddressInRange( return isChallengedAddressInRangeCommon(s.hs.newestTopEntryData, addr, startHeight, endHeight) } +func (s *balances) newestIsChallengedAddress(addr proto.AddressID, height proto.Height) (bool, error) { + var ( + startHeight = height + endHeight = startHeight // we're checking only one height, so the heights are the same + ) + return s.newestIsChallengedAddressInRange(addr, startHeight, endHeight) +} + // minEffectiveBalanceInRange returns minimal effective balance in range [startHeight, endHeight]. // // IMPORTANT NOTE: this method returns saved on disk data, for the newest data use newestMinEffectiveBalanceInRange. diff --git a/pkg/state/state.go b/pkg/state/state.go index 6605cc33df..f9b2ece98c 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1001,7 +1001,7 @@ func (s *stateManager) FullWavesBalance(account proto.Recipient) (*proto.FullWav if err != nil { return nil, errs.Extend(err, "failed to get waves balance") } - effective, err := profile.effectiveBalance() + effective, err := profile.effectiveBalanceUnchecked() if err != nil { return nil, errs.Extend(err, "failed to get effective balance") } @@ -1013,6 +1013,13 @@ func (s *stateManager) FullWavesBalance(account proto.Recipient) (*proto.FullWav if err != nil { return nil, errs.Extend(err, "failed to get generating balance") } + if generating == 0 { // we need to check for challenged addresses only if generating balance is 0 + chEffective, effErr := profile.effectiveBalance(s.stor.balances.isChallengedAddress, addr.ID(), height) + if effErr != nil { + return nil, errs.Extend(effErr, "failed to get checked effective balance") + } + effective = chEffective + } return &proto.FullWavesBalance{ Regular: profile.balance, Generating: generating, @@ -1032,7 +1039,7 @@ func (s *stateManager) NewestFullWavesBalance(account proto.Recipient) (*proto.F if err != nil { return nil, wrapErr(RetrievalError, err) } - effective, err := profile.effectiveBalance() + effective, err := profile.effectiveBalanceUnchecked() if err != nil { return nil, wrapErr(Other, err) } @@ -1044,6 +1051,13 @@ func (s *stateManager) NewestFullWavesBalance(account proto.Recipient) (*proto.F if gb, gbErr := s.NewestGeneratingBalance(account, height); gbErr == nil { generating = gb } + if generating == 0 { // we need to check for challenged addresses only if generating balance is 0 + chEffective, effErr := profile.effectiveBalance(s.stor.balances.newestIsChallengedAddress, addr.ID(), height) + if effErr != nil { + return nil, wrapErr(RetrievalError, effErr) + } + effective = chEffective + } return &proto.FullWavesBalance{ Regular: profile.balance, Generating: generating, @@ -1067,11 +1081,20 @@ func (s *stateManager) WavesBalanceProfile(id proto.AddressID) (*types.WavesBala if gb, gbErr := s.stor.balances.newestGeneratingBalance(id, height); gbErr == nil { generating = gb } + var challenged bool + if generating == 0 { // fast path: we need to check for challenged addresses only if generating balance is 0 + ch, chErr := s.stor.balances.newestIsChallengedAddress(id, height) + if chErr != nil { + return nil, wrapErr(RetrievalError, chErr) + } + challenged = ch + } return &types.WavesBalanceProfile{ Balance: profile.balance, LeaseIn: profile.leaseIn, LeaseOut: profile.leaseOut, Generating: generating, + Challenged: challenged, }, nil } diff --git a/pkg/types/types.go b/pkg/types/types.go index 636002b4cf..fbf871605b 100644 --- a/pkg/types/types.go +++ b/pkg/types/types.go @@ -43,6 +43,7 @@ type WavesBalanceProfile struct { LeaseIn int64 LeaseOut int64 Generating uint64 + Challenged bool // if Challenged true, the account considered as challenged at the current height. } // SmartState is a part of state used by smart contracts.