code.vegaprotocol.io/vega@v0.79.0/wallet/api/spam/pow_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 walletapi "code.vegaprotocol.io/vega/wallet/api" 23 nodetypes "code.vegaprotocol.io/vega/wallet/api/node/types" 24 "code.vegaprotocol.io/vega/wallet/api/spam" 25 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestProofOfWorkGeneration(t *testing.T) { 30 t.Run("Basic generation", testBasicGeneration) 31 t.Run("Too many transactions without increasing difficulty", testTooManyTransactionsWithoutIncreasingDifficulty) 32 t.Run("Too many transactions with increasing difficulty", testTooManyTransactionsWithIncreasingDifficulty) 33 t.Run("Number of allowed past blocks changes", testNumberOfPastBlocksChanges) 34 t.Run("Different chains", testDifferentChains) 35 t.Run("Statistics vs own count", testStatsVsOwnCount) 36 } 37 38 func testBasicGeneration(t *testing.T) { 39 p := spam.NewHandler() 40 41 pubkey := vgcrypto.RandomHash() 42 res, err := p.GenerateProofOfWork(pubkey, defaultSpamStats(t)) 43 require.NoError(t, err) 44 require.NotEmpty(t, res.Tid) 45 } 46 47 func testTooManyTransactionsWithoutIncreasingDifficulty(t *testing.T) { 48 p := spam.NewHandler() 49 50 pubkey1 := vgcrypto.RandomHash() 51 pubkey2 := vgcrypto.RandomHash() 52 53 st := defaultSpamStats(t) 54 st.PoW.PowBlockStates[0].IncreasingDifficulty = false 55 st.PoW.PowBlockStates[0].TxPerBlock = 5 56 57 // when pubkey1 submits too many txn per block 58 for i := 0; i < 5; i++ { 59 _, err := p.GenerateProofOfWork(pubkey1, st) 60 require.NoError(t, err) 61 } 62 63 // then pubkey1 is blocked and pubkey2 isn't 64 _, err := p.GenerateProofOfWork(pubkey1, st) 65 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 66 67 _, err = p.GenerateProofOfWork(pubkey2, st) 68 require.NoError(t, err) 69 70 // when we start a new block and our counters reset 71 st.PoW.PowBlockStates[0].BlockHeight = 101 72 73 // then pubkey1 can generate pow again 74 _, err = p.GenerateProofOfWork(pubkey1, st) 75 require.NoError(t, err) 76 } 77 78 func testTooManyTransactionsWithIncreasingDifficulty(t *testing.T) { 79 p := spam.NewHandler() 80 81 pubkey1 := vgcrypto.RandomHash() 82 83 st := defaultSpamStats(t) 84 st.PoW.PowBlockStates[0].IncreasingDifficulty = true 85 st.PoW.PowBlockStates[0].TxPerBlock = 1 86 87 // when pubkey1 submits 5 transactions with difficulty 1 88 for i := 0; i < 5; i++ { 89 _, err := p.GenerateProofOfWork(pubkey1, st) 90 require.NoError(t, err) 91 } 92 93 // then the next 10 should increase in difficulty by one 94 bs := st.PoW.PowBlockStates[0] 95 for i := 2; i < 12; i++ { 96 r, err := p.GenerateProofOfWork(pubkey1, st) 97 require.NoError(t, err) 98 99 ok, d := vgcrypto.Verify(bs.BlockHash, r.Tid, r.Nonce, bs.HashFunction, 1) 100 require.True(t, ok) 101 require.GreaterOrEqual(t, uint(d), uint(i)) 102 } 103 } 104 105 func testNumberOfPastBlocksChanges(t *testing.T) { 106 p := spam.NewHandler() 107 108 pubkey := vgcrypto.RandomHash() 109 st := defaultSpamStats(t) 110 111 // start with a buffer size of 5 112 st.PoW.PowBlockStates[0].IncreasingDifficulty = false 113 st.PoW.PastBlocks = 5 114 st.PoW.PowBlockStates[0].TxPerBlock = 2 115 116 // send stuff in 117 for i := uint64(0); i < 10; i++ { 118 st.PoW.PowBlockStates[0].BlockHeight = i + 1 119 _, err := p.GenerateProofOfWork(pubkey, st) 120 require.NoError(t, err) 121 } 122 123 // reach limit on block 10 124 _, err := p.GenerateProofOfWork(pubkey, st) 125 require.NoError(t, err) 126 _, err = p.GenerateProofOfWork(pubkey, st) 127 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 128 129 // check we're still blocked after resize 130 st.PoW.PastBlocks = 2 131 _, err = p.GenerateProofOfWork(pubkey, st) 132 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 133 134 // now ask to create a pow for a block that is now too historic 135 st.PoW.PowBlockStates[0].BlockHeight = 8 136 _, err = p.GenerateProofOfWork(pubkey, st) 137 require.ErrorIs(t, err, walletapi.ErrBlockHeightTooHistoric) 138 139 // now increase and it should be fine 140 st.PoW.PastBlocks = 10 141 _, err = p.GenerateProofOfWork(pubkey, st) 142 require.NoError(t, err) 143 144 // check we are still blocked on the higher block 145 st.PoW.PowBlockStates[0].BlockHeight = 10 146 _, err = p.GenerateProofOfWork(pubkey, st) 147 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 148 } 149 150 func testDifferentChains(t *testing.T) { 151 p := spam.NewHandler() 152 153 pubkey1 := vgcrypto.RandomHash() 154 155 st := defaultSpamStats(t) 156 st.PoW.PowBlockStates[0].IncreasingDifficulty = false 157 st.PoW.PowBlockStates[0].TxPerBlock = 5 158 159 // when pubkey1 submits too many txn per block 160 for i := 0; i < 5; i++ { 161 _, err := p.GenerateProofOfWork(pubkey1, st) 162 require.NoError(t, err) 163 } 164 165 // then pubkey1 is blocked and pubkey2 isn't 166 _, err := p.GenerateProofOfWork(pubkey1, st) 167 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 168 169 st.ChainID = "different-chain" 170 // then pubkey1 is not blocked on a different chain 171 _, err = p.GenerateProofOfWork(pubkey1, st) 172 require.NoError(t, err) 173 } 174 175 func testStatsVsOwnCount(t *testing.T) { 176 p := spam.NewHandler() 177 178 pubkey1 := vgcrypto.RandomHash() 179 180 st := defaultSpamStats(t) 181 st.PoW.PowBlockStates[0].IncreasingDifficulty = false 182 st.PoW.PowBlockStates[0].TxPerBlock = 2 183 st.PoW.PowBlockStates[0].TransactionsSeen = 2 184 185 // we have a fresh state and stats tells us we're already at the limit 186 _, err := p.GenerateProofOfWork(pubkey1, st) 187 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 188 189 // we now move onto the next block and generate some proof-of-work 190 st.PoW.PowBlockStates[0].BlockHeight = 150 191 st.PoW.PowBlockStates[0].TransactionsSeen = 0 192 _, err = p.GenerateProofOfWork(pubkey1, st) 193 require.NoError(t, err) 194 _, err = p.GenerateProofOfWork(pubkey1, st) 195 require.NoError(t, err) 196 197 // next generation should fail because we've reached the limit, but the stats 198 // haven't caught up and will tell us there is space, we ignore it 199 st.PoW.PowBlockStates[0].TransactionsSeen = 0 200 _, err = p.GenerateProofOfWork(pubkey1, st) 201 require.ErrorIs(t, err, walletapi.ErrTransactionsPerBlockLimitReached) 202 } 203 204 func defaultSpamStats(t *testing.T) *nodetypes.SpamStatistics { 205 t.Helper() 206 return &nodetypes.SpamStatistics{ 207 ChainID: "default-chain-id", 208 LastBlockHeight: 10, 209 Delegations: &nodetypes.SpamStatistic{}, 210 Proposals: &nodetypes.SpamStatistic{}, 211 Transfers: &nodetypes.SpamStatistic{}, 212 NodeAnnouncements: &nodetypes.SpamStatistic{}, 213 IssuesSignatures: &nodetypes.SpamStatistic{}, 214 Votes: &nodetypes.VoteSpamStatistics{ 215 Proposals: map[string]uint64{}, 216 }, 217 PoW: &nodetypes.PoWStatistics{ 218 PowBlockStates: []nodetypes.PoWBlockState{ 219 { 220 BlockHash: vgcrypto.RandomHash(), 221 BlockHeight: 10, 222 TxPerBlock: 100, 223 IncreasingDifficulty: true, 224 HashFunction: vgcrypto.Sha3, 225 }, 226 }, 227 PastBlocks: 100, 228 }, 229 } 230 }