code.vegaprotocol.io/vega@v0.79.0/wallet/api/spam/spam_test.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_test 17 18 import ( 19 "testing" 20 21 vgcrypto "code.vegaprotocol.io/vega/libs/crypto" 22 "code.vegaprotocol.io/vega/libs/ptr" 23 v1 "code.vegaprotocol.io/vega/protos/vega/commands/v1" 24 walletpb "code.vegaprotocol.io/vega/protos/vega/wallet/v1" 25 nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types" 26 "code.vegaprotocol.io/vega/wallet/api/spam" 27 28 "github.com/stretchr/testify/require" 29 ) 30 31 var ( 32 policies = []string{ 33 "proposals", 34 "delegations", 35 "announcements", 36 "transfers", 37 "votes", 38 "issueSignatures", 39 } 40 proposalID = "default-proposal-id" 41 ) 42 43 func TestSpamAwareness(t *testing.T) { 44 t.Run("spam policy has been banned", testSpamPolicyBannedUtil) 45 t.Run("spam policy will get banned", testSpamPolicyWillGetBanned) 46 t.Run("spam policy hits limit", testSpamPolicyHitsLimit) 47 t.Run("spam policy new epoch", testSpamPolicyNewEpoch) 48 t.Run("spam policy pow ban", testSpamPolicyPoWBan) 49 t.Run("spam policy votes separate proposals", testSpamVotesSeparateProposals) 50 t.Run("other transaction types not blocked", testOtherTransactionTypesNotBlock) 51 } 52 53 func testSpamPolicyBannedUtil(t *testing.T) { 54 p := spam.NewHandler() 55 56 pubKey := vgcrypto.RandomHash() 57 banned := nodetypes.SpamStatistic{ 58 BannedUntil: ptr.From("forever"), 59 MaxForEpoch: 100, 60 CountForEpoch: 100, 61 } 62 63 for _, f := range policies { 64 s, req := getSimplePolicyStats(t, pubKey, f, banned) 65 err := p.CheckSubmission(req, s) 66 require.Equal(t, err.Error(), "party is banned from submitting transactions of this type until forever") 67 } 68 } 69 70 func testSpamPolicyWillGetBanned(t *testing.T) { 71 p := spam.NewHandler() 72 73 pubKey := vgcrypto.RandomHash() 74 willBan := nodetypes.SpamStatistic{ 75 BannedUntil: nil, 76 MaxForEpoch: 100, 77 CountForEpoch: 100, 78 } 79 80 for _, f := range policies { 81 s, req := getSimplePolicyStats(t, pubKey, f, willBan) 82 err := p.CheckSubmission(req, s) 83 require.Equal(t, err.Error(), "party has already submitted the maximum number of transactions of this type per epoch (100)") 84 } 85 } 86 87 func testSpamPolicyHitsLimit(t *testing.T) { 88 p := spam.NewHandler() 89 pubKey := vgcrypto.RandomHash() 90 stat := nodetypes.SpamStatistic{ 91 BannedUntil: nil, 92 MaxForEpoch: 100, 93 CountForEpoch: 99, 94 } 95 96 for _, f := range policies { 97 s, req := getSimplePolicyStats(t, pubKey, f, stat) 98 99 // we are at 99 and this check pushes to 100 100 err := p.CheckSubmission(req, s) 101 require.NoError(t, err) 102 103 // so the next one will fail 104 err = p.CheckSubmission(req, s) 105 require.Error(t, err, spam.ErrPartyWillBeBanned) 106 } 107 } 108 109 func testSpamPolicyNewEpoch(t *testing.T) { 110 p := spam.NewHandler() 111 pubKey := vgcrypto.RandomHash() 112 for i, f := range policies { 113 stat := nodetypes.SpamStatistic{ 114 BannedUntil: nil, 115 MaxForEpoch: 100, 116 CountForEpoch: 88, 117 } 118 s, req := getSimplePolicyStats(t, pubKey, f, stat) 119 120 // we are at 88 and this check pushes to 100 121 err := p.CheckSubmission(req, s) 122 require.NoError(t, err) 123 124 // spam update is more than our count so we trust it 125 stat.CountForEpoch = 100 126 s, req = getSimplePolicyStats(t, pubKey, f, stat) 127 err = p.CheckSubmission(req, s) 128 require.Error(t, err, spam.ErrPartyWillBeBanned) 129 130 // dip into a new epoch so we take the spam stats lower value 131 stat.CountForEpoch = 0 132 s, req = getSimplePolicyStats(t, pubKey, f, stat) 133 s.EpochSeq += (1 + uint64(i)) 134 err = p.CheckSubmission(req, s) 135 require.NoError(t, err) 136 } 137 } 138 139 func testSpamPolicyPoWBan(t *testing.T) { 140 p := spam.NewHandler() 141 pubKey := vgcrypto.RandomHash() 142 for _, f := range policies { 143 stat := nodetypes.SpamStatistic{ 144 BannedUntil: nil, 145 MaxForEpoch: 100, 146 CountForEpoch: 88, 147 } 148 s, req := getSimplePolicyStats(t, pubKey, f, stat) 149 s.PoW.BannedUntil = ptr.From("forever") 150 151 // we are at 88 so find to submit, but banned by PoW 152 err := p.CheckSubmission(req, s) 153 require.Equal(t, err.Error(), "party is banned from submitting all transactions until forever") 154 } 155 } 156 157 func testSpamVotesSeparateProposals(t *testing.T) { 158 p := spam.NewHandler() 159 pubKey := vgcrypto.RandomHash() 160 161 stat := nodetypes.SpamStatistic{ 162 BannedUntil: nil, 163 MaxForEpoch: 100, 164 CountForEpoch: 100, 165 } 166 s, req := getSimplePolicyStats(t, pubKey, "votes", stat) 167 168 // we're at our max for the first proposal 169 err := p.CheckSubmission(req, s) 170 require.Equal(t, err.Error(), "party has already submitted the maximum number of transactions of this type per epoch (100)") 171 172 // but can still submit against a different proposal 173 req = &walletpb.SubmitTransactionRequest{ 174 PubKey: pubKey, 175 Command: &walletpb.SubmitTransactionRequest_VoteSubmission{ 176 VoteSubmission: &v1.VoteSubmission{ 177 ProposalId: vgcrypto.RandomHash(), 178 }, 179 }, 180 } 181 err = p.CheckSubmission(req, s) 182 require.NoError(t, err) 183 } 184 185 func testOtherTransactionTypesNotBlock(t *testing.T) { 186 p := spam.NewHandler() 187 pubKey := vgcrypto.RandomHash() 188 until := ptr.From("forever") 189 190 stat := nodetypes.SpamStatistic{ 191 BannedUntil: until, 192 } 193 194 // banned on everything except for proof-of-work 195 stats := &nodetypes.SpamStatistics{ 196 PoW: &nodetypes.PoWStatistics{}, 197 Proposals: &stat, 198 Delegations: &stat, 199 Transfers: &stat, 200 NodeAnnouncements: &stat, 201 IssuesSignatures: &stat, 202 Votes: &nodetypes.VoteSpamStatistics{ 203 BannedUntil: until, 204 }, 205 } 206 207 // but can still submit against a different proposal 208 req := &walletpb.SubmitTransactionRequest{ 209 PubKey: pubKey, 210 Command: &walletpb.SubmitTransactionRequest_OrderSubmission{}, 211 } 212 err := p.CheckSubmission(req, stats) 213 require.NoError(t, err) 214 215 // but pow ban still applies 216 stats.PoW.BannedUntil = until 217 err = p.CheckSubmission(req, stats) 218 require.Equal(t, err.Error(), "party is banned from submitting all transactions until forever") 219 } 220 221 func getSimplePolicyStats(t *testing.T, pubKey, policy string, st nodetypes.SpamStatistic) (*nodetypes.SpamStatistics, *walletpb.SubmitTransactionRequest) { 222 t.Helper() 223 spam := defaultSpamStats(t) 224 req := &walletpb.SubmitTransactionRequest{ 225 PubKey: pubKey, 226 } 227 228 switch policy { 229 case "proposals": 230 spam.Proposals = &st 231 req.Command = &walletpb.SubmitTransactionRequest_ProposalSubmission{} 232 case "delegations": 233 spam.Delegations = &st 234 req.Command = &walletpb.SubmitTransactionRequest_DelegateSubmission{} 235 case "transfers": 236 spam.Transfers = &st 237 req.Command = &walletpb.SubmitTransactionRequest_Transfer{} 238 case "announcements": 239 spam.NodeAnnouncements = &st 240 req.Command = &walletpb.SubmitTransactionRequest_AnnounceNode{} 241 case "issueSignatures": 242 spam.IssuesSignatures = &st 243 req.Command = &walletpb.SubmitTransactionRequest_IssueSignatures{} 244 case "votes": 245 spam.Votes.MaxForEpoch = st.MaxForEpoch 246 spam.Votes.BannedUntil = st.BannedUntil 247 spam.Votes.Proposals = map[string]uint64{proposalID: st.CountForEpoch} 248 req.Command = &walletpb.SubmitTransactionRequest_VoteSubmission{ 249 VoteSubmission: &v1.VoteSubmission{ 250 ProposalId: proposalID, 251 }, 252 } 253 default: 254 t.FailNow() 255 } 256 257 return spam, req 258 }