code.vegaprotocol.io/vega@v0.79.0/core/spam/engine.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package spam 17 18 import ( 19 "context" 20 "encoding/hex" 21 "sync" 22 23 "code.vegaprotocol.io/vega/core/blockchain/abci" 24 "code.vegaprotocol.io/vega/core/netparams" 25 "code.vegaprotocol.io/vega/core/txn" 26 "code.vegaprotocol.io/vega/core/types" 27 "code.vegaprotocol.io/vega/libs/num" 28 "code.vegaprotocol.io/vega/logging" 29 protoapi "code.vegaprotocol.io/vega/protos/vega/api/v1" 30 ) 31 32 type StakingAccounts interface { 33 GetAvailableBalance(party string) (*num.Uint, error) 34 } 35 36 type EpochEngine interface { 37 NotifyOnEpoch(f func(context.Context, types.Epoch), r func(context.Context, types.Epoch)) 38 } 39 40 type Engine struct { 41 log *logging.Logger 42 config Config 43 accounting StakingAccounts 44 cfgMu sync.Mutex 45 46 transactionTypeToPolicy map[txn.Command]Policy 47 currentEpoch *types.Epoch 48 policyNameToPolicy map[string]Policy 49 hashKeys []string 50 noSpamProtection bool // flag that disables chesk for the spam policies, that is useful for the nullchain 51 } 52 53 type Policy interface { 54 Reset(epoch types.Epoch) 55 UpdateTx(tx abci.Tx) 56 RollbackProposal() 57 CheckBlockTx(abci.Tx) error 58 PreBlockAccept(tx abci.Tx) error 59 UpdateUintParam(name string, value *num.Uint) error 60 UpdateIntParam(name string, value int64) error 61 Serialise() ([]byte, error) 62 Deserialise(payload *types.Payload) error 63 GetSpamStats(partyID string) *protoapi.SpamStatistic 64 GetVoteSpamStats(partyID string) *protoapi.VoteSpamStatistics 65 } 66 67 // ReloadConf updates the internal configuration of the spam engine. 68 func (e *Engine) ReloadConf(cfg Config) { 69 e.log.Info("reloading configuration") 70 if e.log.GetLevel() != cfg.Level.Get() { 71 e.log.Info("updating log level", 72 logging.String("old", e.log.GetLevel().String()), 73 logging.String("new", cfg.Level.String()), 74 ) 75 e.log.SetLevel(cfg.Level.Get()) 76 } 77 78 e.cfgMu.Lock() 79 e.config = cfg 80 e.cfgMu.Unlock() 81 } 82 83 // New instantiates a new spam engine. 84 func New(log *logging.Logger, config Config, epochEngine EpochEngine, accounting StakingAccounts) *Engine { 85 log = log.Named(namedLogger) 86 log.SetLevel(config.Level.Get()) 87 e := &Engine{ 88 config: config, 89 log: log, 90 accounting: accounting, 91 transactionTypeToPolicy: map[txn.Command]Policy{}, 92 } 93 94 // simple policies 95 proposalPolicy := NewSimpleSpamPolicy("proposal", netparams.SpamProtectionMinTokensForProposal, netparams.SpamProtectionMaxProposals, log, accounting) 96 valJoinPolicy := NewSimpleSpamPolicy("validatorJoin", netparams.StakingAndDelegationRewardMinimumValidatorStake, "", log, accounting) 97 delegationPolicy := NewSimpleSpamPolicy("delegation", netparams.SpamProtectionMinTokensForDelegation, netparams.SpamProtectionMaxDelegations, log, accounting) 98 transferPolicy := NewSimpleSpamPolicy("transfer", "", netparams.TransferMaxCommandsPerEpoch, log, accounting) 99 issuesSignaturesPolicy := NewSimpleSpamPolicy("issueSignature", netparams.SpamProtectionMinMultisigUpdates, netparams.SpamProtectionMaxMultisigUpdates, log, accounting) 100 101 createReferralSetPolicy := NewSimpleSpamPolicy("createReferralSet", netparams.ReferralProgramMinStakedVegaTokens, netparams.SpamProtectionMaxCreateReferralSet, log, accounting) 102 updateReferralSetPolicy := NewSimpleSpamPolicy("updateReferralSet", netparams.ReferralProgramMinStakedVegaTokens, netparams.SpamProtectionMaxUpdateReferralSet, log, accounting) 103 applyReferralCodePolicy := NewSimpleSpamPolicy("applyReferralCode", "", netparams.SpamProtectionMaxApplyReferralCode, log, accounting) 104 updatePartyProfilePolicy := NewSimpleSpamPolicy("updatePartyProfile", "", netparams.SpamProtectionMaxUpdatePartyProfile, log, accounting) 105 106 // complex policies 107 votePolicy := NewVoteSpamPolicy(netparams.SpamProtectionMinTokensForVoting, netparams.SpamProtectionMaxVotes, log, accounting) 108 109 voteKey := (&types.PayloadVoteSpamPolicy{}).Key() 110 e.policyNameToPolicy = map[string]Policy{ 111 proposalPolicy.policyName: proposalPolicy, 112 valJoinPolicy.policyName: valJoinPolicy, 113 delegationPolicy.policyName: delegationPolicy, 114 transferPolicy.policyName: transferPolicy, 115 issuesSignaturesPolicy.policyName: issuesSignaturesPolicy, 116 voteKey: votePolicy, 117 createReferralSetPolicy.policyName: createReferralSetPolicy, 118 updateReferralSetPolicy.policyName: updateReferralSetPolicy, 119 applyReferralCodePolicy.policyName: applyReferralCodePolicy, 120 updatePartyProfilePolicy.policyName: updatePartyProfilePolicy, 121 } 122 e.hashKeys = []string{ 123 proposalPolicy.policyName, 124 valJoinPolicy.policyName, 125 delegationPolicy.policyName, 126 transferPolicy.policyName, 127 issuesSignaturesPolicy.policyName, 128 createReferralSetPolicy.policyName, 129 updateReferralSetPolicy.policyName, 130 applyReferralCodePolicy.policyName, 131 updatePartyProfilePolicy.policyName, 132 voteKey, 133 } 134 135 e.transactionTypeToPolicy[txn.ProposeCommand] = proposalPolicy 136 e.transactionTypeToPolicy[txn.BatchProposeCommand] = proposalPolicy 137 e.transactionTypeToPolicy[txn.AnnounceNodeCommand] = valJoinPolicy 138 e.transactionTypeToPolicy[txn.DelegateCommand] = delegationPolicy 139 e.transactionTypeToPolicy[txn.UndelegateCommand] = delegationPolicy 140 e.transactionTypeToPolicy[txn.TransferFundsCommand] = transferPolicy 141 e.transactionTypeToPolicy[txn.CancelTransferFundsCommand] = transferPolicy 142 e.transactionTypeToPolicy[txn.IssueSignatures] = issuesSignaturesPolicy 143 e.transactionTypeToPolicy[txn.VoteCommand] = votePolicy 144 e.transactionTypeToPolicy[txn.CreateReferralSetCommand] = createReferralSetPolicy 145 e.transactionTypeToPolicy[txn.UpdateReferralSetCommand] = updateReferralSetPolicy 146 e.transactionTypeToPolicy[txn.ApplyReferralCodeCommand] = applyReferralCodePolicy 147 e.transactionTypeToPolicy[txn.UpdatePartyProfileCommand] = updatePartyProfilePolicy 148 149 // register for epoch end notifications 150 epochEngine.NotifyOnEpoch(e.OnEpochEvent, e.OnEpochRestore) 151 e.log.Info("Spam protection started") 152 153 return e 154 } 155 156 func (e *Engine) DisableSpamProtection() { 157 e.log.Infof("Disabling spam protection for the Spam Engine") 158 e.noSpamProtection = true 159 } 160 161 // OnCreateReferralSet is called when the net param for max create referral set per epoch has changed. 162 func (e *Engine) OnMaxCreateReferralSet(ctx context.Context, max int64) error { 163 return e.transactionTypeToPolicy[txn.CreateReferralSetCommand].UpdateIntParam(netparams.SpamProtectionMaxCreateReferralSet, max) 164 } 165 166 // OnMaxPartyProfileUpdate is called when the net param for max update party profile per epoch has changed. 167 func (e *Engine) OnMaxPartyProfile(ctx context.Context, max int64) error { 168 return e.transactionTypeToPolicy[txn.UpdatePartyProfileCommand].UpdateIntParam(netparams.SpamProtectionMaxUpdatePartyProfile, max) 169 } 170 171 // OnMaxUpdateReferralSet is called when the net param for max update referral set per epoch has changed. 172 func (e *Engine) OnMaxUpdateReferralSet(ctx context.Context, max int64) error { 173 return e.transactionTypeToPolicy[txn.UpdateReferralSetCommand].UpdateIntParam(netparams.SpamProtectionMaxUpdateReferralSet, max) 174 } 175 176 // OnMaxApplyReferralCode is called when the net param for max update referral set per epoch has changed. 177 func (e *Engine) OnMaxApplyReferralCode(ctx context.Context, max int64) error { 178 return e.transactionTypeToPolicy[txn.ApplyReferralCodeCommand].UpdateIntParam(netparams.SpamProtectionMaxApplyReferralCode, max) 179 } 180 181 // OnMinTokensForReferral is called when the net param for min staked tokens requirement for referral set create/update has changed. 182 func (e *Engine) OnMinTokensForReferral(ctx context.Context, minTokens *num.Uint) error { 183 err := e.transactionTypeToPolicy[txn.CreateReferralSetCommand].UpdateUintParam(netparams.ReferralProgramMinStakedVegaTokens, minTokens) 184 if err != nil { 185 return err 186 } 187 return e.transactionTypeToPolicy[txn.UpdateReferralSetCommand].UpdateUintParam(netparams.ReferralProgramMinStakedVegaTokens, minTokens) 188 } 189 190 // OnMaxDelegationsChanged is called when the net param for max delegations per epoch has changed. 191 func (e *Engine) OnMaxDelegationsChanged(ctx context.Context, maxDelegations int64) error { 192 return e.transactionTypeToPolicy[txn.DelegateCommand].UpdateIntParam(netparams.SpamProtectionMaxDelegations, maxDelegations) 193 } 194 195 // OnMinTokensForDelegationChanged is called when the net param for min tokens requirement for voting has changed. 196 func (e *Engine) OnMinTokensForDelegationChanged(ctx context.Context, minTokens num.Decimal) error { 197 minTokensFoDelegation, _ := num.UintFromDecimal(minTokens) 198 return e.transactionTypeToPolicy[txn.DelegateCommand].UpdateUintParam(netparams.SpamProtectionMinTokensForDelegation, minTokensFoDelegation) 199 } 200 201 // OnMaxVotesChanged is called when the net param for max votes per epoch has changed. 202 func (e *Engine) OnMaxVotesChanged(ctx context.Context, maxVotes int64) error { 203 return e.transactionTypeToPolicy[txn.VoteCommand].UpdateIntParam(netparams.SpamProtectionMaxVotes, maxVotes) 204 } 205 206 // OnMinTokensForVotingChanged is called when the net param for min tokens requirement for voting has changed. 207 func (e *Engine) OnMinTokensForVotingChanged(ctx context.Context, minTokens num.Decimal) error { 208 minTokensForVoting, _ := num.UintFromDecimal(minTokens) 209 return e.transactionTypeToPolicy[txn.VoteCommand].UpdateUintParam(netparams.SpamProtectionMinTokensForVoting, minTokensForVoting) 210 } 211 212 // OnMaxProposalsChanged is called when the net param for max proposals per epoch has changed. 213 func (e *Engine) OnMaxProposalsChanged(ctx context.Context, maxProposals int64) error { 214 return e.transactionTypeToPolicy[txn.ProposeCommand].UpdateIntParam(netparams.SpamProtectionMaxProposals, maxProposals) 215 } 216 217 // OnMinTokensForProposalChanged is called when the net param for min tokens requirement for submitting a proposal has changed. 218 func (e *Engine) OnMinTokensForProposalChanged(ctx context.Context, minTokens num.Decimal) error { 219 minTokensForProposal, _ := num.UintFromDecimal(minTokens) 220 return e.transactionTypeToPolicy[txn.ProposeCommand].UpdateUintParam(netparams.SpamProtectionMinTokensForProposal, minTokensForProposal) 221 } 222 223 // OnMaxTransfersChanged is called when the net param for max transfers per epoch changes. 224 func (e *Engine) OnMaxTransfersChanged(_ context.Context, maxTransfers int64) error { 225 return e.transactionTypeToPolicy[txn.TransferFundsCommand].UpdateIntParam(netparams.TransferMaxCommandsPerEpoch, maxTransfers) 226 } 227 228 // OnMinValidatorTokensChanged is called when the net param for min tokens for joining validator changes. 229 func (e *Engine) OnMinValidatorTokensChanged(_ context.Context, minTokens num.Decimal) error { 230 minTokensForJoiningValidator, _ := num.UintFromDecimal(minTokens) 231 return e.transactionTypeToPolicy[txn.AnnounceNodeCommand].UpdateUintParam(netparams.StakingAndDelegationRewardMinimumValidatorStake, minTokensForJoiningValidator) 232 } 233 234 // OnMinTokensForProposalChanged is called when the net param for min tokens requirement for submitting a proposal has changed. 235 func (e *Engine) OnMinTokensForMultisigUpdatesChanged(_ context.Context, minTokens num.Decimal) error { 236 minTokensForMultisigUpdates, _ := num.UintFromDecimal(minTokens) 237 return e.transactionTypeToPolicy[txn.IssueSignatures].UpdateUintParam(netparams.SpamProtectionMinMultisigUpdates, minTokensForMultisigUpdates) 238 } 239 240 // OnMinTokensForProposalChanged is called when the net param for min tokens requirement for submitting a proposal has changed. 241 func (e *Engine) OnMaxMultisigUpdatesChanged(_ context.Context, maxUpdates int64) error { 242 return e.transactionTypeToPolicy[txn.IssueSignatures].UpdateIntParam(netparams.SpamProtectionMaxMultisigUpdates, maxUpdates) 243 } 244 245 // OnEpochEvent is a callback for epoch events. 246 func (e *Engine) OnEpochEvent(ctx context.Context, epoch types.Epoch) { 247 e.log.Info("Spam protection OnEpochEvent called", logging.Uint64("epoch", epoch.Seq)) 248 249 if e.noSpamProtection { 250 e.log.Info("Spam protection OnEpochEvent disabled", logging.Uint64("epoch", epoch.Seq)) 251 return 252 } 253 254 if e.currentEpoch == nil || e.currentEpoch.Seq != epoch.Seq { 255 if e.log.GetLevel() <= logging.DebugLevel { 256 e.log.Debug("Spam protection new epoch started", logging.Uint64("epochSeq", epoch.Seq)) 257 } 258 e.currentEpoch = &epoch 259 260 for _, policy := range e.transactionTypeToPolicy { 261 policy.Reset(epoch) 262 } 263 } 264 } 265 266 func (e *Engine) BeginBlock(txs []abci.Tx) { 267 for _, tx := range txs { 268 if _, ok := e.transactionTypeToPolicy[tx.Command()]; !ok { 269 continue 270 } 271 e.transactionTypeToPolicy[tx.Command()].UpdateTx(tx) 272 } 273 } 274 275 func (e *Engine) EndPrepareProposal() { 276 for _, policy := range e.transactionTypeToPolicy { 277 policy.RollbackProposal() 278 } 279 } 280 281 // PreBlockAccept is called from onCheckTx before a tx is added to mempool 282 // returns false is rejected by spam engine with a corresponding error. 283 func (e *Engine) PreBlockAccept(tx abci.Tx) error { 284 command := tx.Command() 285 if _, ok := e.transactionTypeToPolicy[command]; !ok { 286 return nil 287 } 288 if e.log.GetLevel() <= logging.DebugLevel { 289 e.log.Debug("Spam protection PreBlockAccept called for policy", logging.String("txHash", hex.EncodeToString(tx.Hash())), logging.String("command", command.String())) 290 } 291 if e.noSpamProtection { 292 e.log.Debug("Spam protection PreBlockAccept disabled for policy", logging.String("txHash", hex.EncodeToString(tx.Hash())), logging.String("command", command.String())) 293 return nil 294 } 295 return e.transactionTypeToPolicy[command].PreBlockAccept(tx) 296 } 297 298 func (e *Engine) ProcessProposal(txs []abci.Tx) bool { 299 success := true 300 for _, tx := range txs { 301 command := tx.Command() 302 if _, ok := e.transactionTypeToPolicy[command]; !ok { 303 continue 304 } 305 if e.noSpamProtection { 306 e.log.Debug("Spam protection PreBlockAccept disabled for policy", logging.String("txHash", hex.EncodeToString(tx.Hash())), logging.String("command", command.String())) 307 continue 308 } 309 310 if err := e.transactionTypeToPolicy[command].CheckBlockTx(tx); err != nil { 311 success = false 312 } 313 } 314 for _, p := range e.transactionTypeToPolicy { 315 p.RollbackProposal() 316 } 317 return success 318 } 319 320 // PostBlockAccept is called from onDeliverTx before the block is processed 321 // returns false is rejected by spam engine with a corresponding error. 322 func (e *Engine) CheckBlockTx(tx abci.Tx) error { 323 command := tx.Command() 324 if _, ok := e.transactionTypeToPolicy[command]; !ok { 325 return nil 326 } 327 if e.log.GetLevel() <= logging.DebugLevel { 328 e.log.Debug("Spam protection PostBlockAccept called for policy", logging.String("txHash", hex.EncodeToString(tx.Hash())), logging.String("command", command.String())) 329 } 330 331 if e.noSpamProtection { 332 e.log.Debug("Spam protection PreBlockAccept disabled for policy", logging.String("txHash", hex.EncodeToString(tx.Hash())), logging.String("command", command.String())) 333 return nil 334 } 335 return e.transactionTypeToPolicy[command].CheckBlockTx(tx) 336 } 337 338 func (e *Engine) GetSpamStatistics(partyID string) *protoapi.SpamStatistics { 339 stats := &protoapi.SpamStatistics{} 340 341 for txType, policy := range e.transactionTypeToPolicy { 342 switch txType { 343 case txn.ProposeCommand, txn.BatchProposeCommand: 344 stats.Proposals = policy.GetSpamStats(partyID) 345 case txn.DelegateCommand: 346 stats.Delegations = policy.GetSpamStats(partyID) 347 case txn.TransferFundsCommand: 348 stats.Transfers = policy.GetSpamStats(partyID) 349 case txn.AnnounceNodeCommand: 350 stats.NodeAnnouncements = policy.GetSpamStats(partyID) 351 case txn.IssueSignatures: 352 stats.IssueSignatures = policy.GetSpamStats(partyID) 353 case txn.VoteCommand: 354 stats.Votes = policy.GetVoteSpamStats(partyID) 355 case txn.CreateReferralSetCommand: 356 stats.CreateReferralSet = policy.GetSpamStats(partyID) 357 case txn.UpdateReferralSetCommand: 358 stats.UpdateReferralSet = policy.GetSpamStats(partyID) 359 case txn.ApplyReferralCodeCommand: 360 stats.ApplyReferralCode = policy.GetSpamStats(partyID) 361 default: 362 continue 363 } 364 } 365 366 return stats 367 }