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  }