code.vegaprotocol.io/vega@v0.79.0/core/pow/engine_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 pow
    17  
    18  import (
    19  	"context"
    20  	"errors"
    21  	"math/rand"
    22  	"testing"
    23  
    24  	"code.vegaprotocol.io/vega/core/blockchain/abci"
    25  	"code.vegaprotocol.io/vega/core/pow/mocks"
    26  	"code.vegaprotocol.io/vega/core/txn"
    27  	"code.vegaprotocol.io/vega/libs/crypto"
    28  	"code.vegaprotocol.io/vega/libs/num"
    29  	"code.vegaprotocol.io/vega/logging"
    30  
    31  	"github.com/golang/mock/gomock"
    32  	"github.com/stretchr/testify/assert"
    33  	"github.com/stretchr/testify/require"
    34  )
    35  
    36  func TestSpamPoWNumberOfPastBlocks(t *testing.T) {
    37  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    38  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(200))
    39  	require.Equal(t, uint32(200), e.SpamPoWNumberOfPastBlocks())
    40  }
    41  
    42  func TestSpamPoWDifficulty(t *testing.T) {
    43  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    44  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
    45  	require.Equal(t, uint32(20), e.SpamPoWDifficulty())
    46  }
    47  
    48  func TestSpamPoWHashFunction(t *testing.T) {
    49  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    50  	e.UpdateSpamPoWHashFunction(context.Background(), "hash4")
    51  	require.Equal(t, "hash4", e.SpamPoWHashFunction())
    52  }
    53  
    54  func TestSpamPoWNumberOfTxPerBlock(t *testing.T) {
    55  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    56  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(2))
    57  	require.Equal(t, uint32(2), e.SpamPoWNumberOfPastBlocks())
    58  }
    59  
    60  func TestSpamPoWIncreasingDifficulty(t *testing.T) {
    61  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    62  	e.UpdateSpamPoWIncreasingDifficulty(context.Background(), num.NewUint(1))
    63  	require.Equal(t, true, e.SpamPoWIncreasingDifficulty())
    64  }
    65  
    66  func TestUpdateNumberOfBlocks(t *testing.T) {
    67  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    68  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(5))
    69  	require.Equal(t, uint32(5), e.SpamPoWNumberOfPastBlocks())
    70  }
    71  
    72  func TestCheckTx(t *testing.T) {
    73  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    74  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(5))
    75  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
    76  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
    77  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
    78  
    79  	e.currentBlock = 100
    80  	e.blockHeight[100] = 100
    81  	e.blockHash[100] = "113EB390CBEB921433BDBA832CCDFD81AC4C77C3748A41B1AF08C96BC6C7BCD9"
    82  	e.seenTid["49B0DF0954A8C048554B1C65F4F5883C38640D101A11959EB651AE2065A80BBB"] = struct{}{}
    83  	e.heightToTid[96] = []string{"49B0DF0954A8C048554B1C65F4F5883C38640D101A11959EB651AE2065A80BBB"}
    84  
    85  	// seen transction
    86  	require.Equal(t, errors.New("proof of work tid already used"), e.CheckTx(&testTx{blockHeight: 100, powTxID: "49B0DF0954A8C048554B1C65F4F5883C38640D101A11959EB651AE2065A80BBB"}))
    87  
    88  	// incorrect pow
    89  	require.Equal(t, errors.New("failed to verify proof of work"), e.CheckTx(&testTx{party: crypto.RandomHash(), blockHeight: 100, powTxID: "077723AB0705677EAA704130D403C21352F87A9AF0E9C4C8F85CC13245FEFED7", powNonce: 1}))
    90  
    91  	// all good
    92  	require.NoError(t, e.CheckTx(&testTx{party: crypto.RandomHash(), blockHeight: 100, powTxID: "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", powNonce: 596}))
    93  }
    94  
    95  func TestCheckBlockTx(t *testing.T) {
    96  	e := New(logging.NewTestLogger(), NewDefaultConfig())
    97  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(5))
    98  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
    99  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   100  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
   101  
   102  	e.currentBlock = 100
   103  	e.blockHeight[100] = 100
   104  	e.blockHash[100] = "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4"
   105  
   106  	require.Equal(t, 0, len(e.seenTid))
   107  	require.Equal(t, 0, len(e.heightToTid))
   108  	party := crypto.RandomHash()
   109  	tx1 := &testTx{party: party, blockHeight: 100, txID: "1", powTxID: "DFE522E234D67E6AE3F017859F898E576B3928EA57310B765398615A0D3FDE2F", powNonce: 424517} // 00000e31f8ac983354f5885d46b7631bc75f69ec82e8f6178bae53db0ab7e054 - 20
   110  	res1, d1 := e.CheckBlockTx(tx1)
   111  	require.Equal(t, ValidationResultSuccess, res1)
   112  	require.Equal(t, 1, len(e.seenTid))
   113  
   114  	// same transaction within the same proposal should fail verification
   115  	res2, d2 := e.CheckBlockTx(tx1)
   116  	require.Equal(t, ValidationResultVerificationPowError, res2)
   117  
   118  	// another transaction with invalid nonce should fail verification
   119  	tx2 := &testTx{party: party, blockHeight: 100, txID: "2", powTxID: "DFE522E234D67E6AE3F017859F898E576B3928EA57310B765398615A0D3FDE2F", powNonce: 1}
   120  	res3, d3 := e.CheckBlockTx(tx2)
   121  	require.Equal(t, ValidationResultVerificationPowError, res3)
   122  
   123  	// old transaction should fail verification
   124  	tx3 := &testTx{party: party, blockHeight: 50, txID: "3", powTxID: "5B0E1EB96CCAC120E6D824A5F4C4007EABC59573B861BD84B1EF09DFB376DC84", powNonce: 4031737}
   125  	res4, d4 := e.CheckBlockTx(tx3)
   126  	require.Equal(t, ValidationResultVerificationPowError, res4)
   127  
   128  	// add another transaction not increasing difficulty
   129  	tx4 := &testTx{party: party, blockHeight: 100, txID: "4", powTxID: "2A1319636230740888C968E4E7610D6DE820E644EEC3C08AA5322A0A022014BD", powNonce: 1421231} // 000009c5043c4e1dd7fe190ece8d3fd83d94c4e2a2b7800456ce5f5a653c9f75 - 20
   130  	res5, d5 := e.CheckBlockTx(tx4)
   131  	require.Equal(t, ValidationResultTooManyTx, res5)
   132  
   133  	entries := []ValidationEntry{
   134  		{ValResult: res1, Difficulty: d1, Tx: tx1},
   135  		{ValResult: res2, Difficulty: d2, Tx: tx1},
   136  		{ValResult: res3, Difficulty: d3, Tx: tx2},
   137  		{ValResult: res4, Difficulty: d4, Tx: tx3},
   138  		{ValResult: res5, Difficulty: d5, Tx: tx4},
   139  	}
   140  	e.rollback(entries)
   141  	require.Equal(t, 0, len(e.seenTid))
   142  	require.Equal(t, 0, len(e.heightToTid))
   143  
   144  	res, d := e.CheckBlockTx(tx1)
   145  	require.Equal(t, ValidationResultSuccess, res)
   146  	require.Equal(t, 1, len(e.seenTid))
   147  
   148  	valEntry := ValidationEntry{ValResult: res, Difficulty: d, Tx: tx1}
   149  	e.rollback([]ValidationEntry{valEntry})
   150  	e.BeginBlock(101, crypto.RandomHash(), []abci.Tx{tx1})
   151  	require.Equal(t, 1, len(e.heightToTid))
   152  	require.Equal(t, 1, len(e.seenTid))
   153  	require.Equal(t, "DFE522E234D67E6AE3F017859F898E576B3928EA57310B765398615A0D3FDE2F", e.heightToTid[100][0])
   154  }
   155  
   156  func TestMempoolTidRejection(t *testing.T) {
   157  	e := New(logging.NewTestLogger(), NewDefaultConfig())
   158  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(5))
   159  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
   160  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   161  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
   162  
   163  	party := crypto.RandomHash()
   164  	e.currentBlock = 100
   165  	e.blockHeight[100] = 100
   166  	e.blockHash[100] = "113EB390CBEB921433BDBA832CCDFD81AC4C77C3748A41B1AF08C96BC6C7BCD9"
   167  
   168  	tx1 := &testTx{party: party, blockHeight: 100, powTxID: "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", powNonce: 596}
   169  	require.NoError(t, e.CheckTx(tx1))
   170  	require.Equal(t, 1, len(e.mempoolSeenTid))
   171  
   172  	require.Error(t, e.CheckTx(tx1))
   173  	res, d := e.CheckBlockTx(tx1)
   174  	e.rollback([]ValidationEntry{{Tx: tx1, Difficulty: d, ValResult: res}})
   175  	e.BeginBlock(101, crypto.RandomHash(), []abci.Tx{tx1})
   176  	e.OnCommit()
   177  	require.Equal(t, 1, len(e.seenTid))
   178  	_, ok := e.seenTid["2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4"]
   179  	require.True(t, ok)
   180  
   181  	require.Equal(t, 0, len(e.mempoolSeenTid))
   182  	require.Error(t, e.CheckTx(tx1))
   183  }
   184  
   185  func TestDeliverTxDuplciateNonce(t *testing.T) {
   186  	e := New(logging.NewTestLogger(), NewDefaultConfig())
   187  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(5))
   188  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
   189  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   190  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
   191  
   192  	e.currentBlock = 100
   193  	e.blockHeight[100] = 100
   194  	e.blockHash[100] = "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4"
   195  
   196  	require.Equal(t, 0, len(e.seenTid))
   197  	require.Equal(t, 0, len(e.heightToTid))
   198  	require.Equal(t, 0, len(e.heightToNonceRef))
   199  	party := crypto.RandomHash()
   200  
   201  	tx := &testTx{
   202  		party:       party,
   203  		blockHeight: 100,
   204  		powTxID:     "94A9CB1532011081B013CCD8E6AAA832CAB1CBA603F0C5A093B14C4961E5E7F0",
   205  		powNonce:    431336,
   206  		nonce:       12,
   207  	}
   208  
   209  	require.Equal(t, true, e.ProcessProposal([]abci.Tx{tx}))
   210  	e.BeginBlock(101, crypto.RandomHash(), []abci.Tx{tx})
   211  
   212  	require.Equal(t, 1, len(e.seenTid))
   213  	require.Equal(t, 1, len(e.heightToTid))
   214  	require.Equal(t, 1, len(e.heightToNonceRef))
   215  	require.Equal(t, 1, len(e.heightToNonceRef[100]))
   216  
   217  	// now send it in again with a different pow
   218  	tx.powNonce = 100
   219  	tx.powTxID = "113EB390CBEB921433BDBA832CCDFD81AC4C77C3748A41B1AF08C96BC6C7BCD9"
   220  	tx.txID = crypto.RandomHash()
   221  
   222  	require.Equal(t, false, e.ProcessProposal([]abci.Tx{tx}))
   223  
   224  	// but a different party can used that nonce at that height
   225  	tx.powTxID = "DC911C0EA95545441F3E1182DD25D973764395A7E75CBDBC086F1C6F7075AED6"
   226  	tx.powNonce = 523162
   227  	tx.party = crypto.RandomHash()
   228  
   229  	require.Equal(t, true, e.ProcessProposal([]abci.Tx{tx}))
   230  	e.BeginBlock(101, crypto.RandomHash(), []abci.Tx{tx})
   231  	require.Equal(t, 1, len(e.heightToNonceRef))
   232  	require.Equal(t, 2, len(e.heightToNonceRef[100]))
   233  
   234  	// check the maps are purged when we leave scope
   235  	e.BeginBlock(105, crypto.RandomHash(), []abci.Tx{})
   236  	require.Equal(t, 0, len(e.seenTid))
   237  	require.Equal(t, 0, len(e.heightToTid))
   238  	require.Equal(t, 0, len(e.heightToNonceRef))
   239  	require.Equal(t, 0, len(e.seenNonceRef))
   240  }
   241  
   242  func TestExpectedDifficulty(t *testing.T) {
   243  	type args struct {
   244  		spamPowDifficulty         uint
   245  		spamPoWNumberOfTxPerBlock uint
   246  		seenTx                    uint
   247  	}
   248  
   249  	tests := []struct {
   250  		name           string
   251  		args           args
   252  		wantTotal      uint
   253  		wantDifficulty uint
   254  	}{
   255  		{
   256  			name: "3 transactions",
   257  			args: args{
   258  				spamPowDifficulty:         20,
   259  				spamPoWNumberOfTxPerBlock: 5,
   260  				seenTx:                    3,
   261  			},
   262  			wantTotal:      60, // 3 * 20
   263  			wantDifficulty: 20,
   264  		},
   265  		{
   266  			name: "5 transactions",
   267  			args: args{
   268  				spamPowDifficulty:         20,
   269  				spamPoWNumberOfTxPerBlock: 5,
   270  				seenTx:                    5,
   271  			},
   272  			wantTotal:      100, // 5 * 20
   273  			wantDifficulty: 21,
   274  		},
   275  		{
   276  			name: "6 transactions",
   277  			args: args{
   278  				spamPowDifficulty:         20,
   279  				spamPoWNumberOfTxPerBlock: 5,
   280  				seenTx:                    6,
   281  			},
   282  			wantTotal:      121, // 5 * 20 + 21
   283  			wantDifficulty: 21,
   284  		},
   285  		{
   286  			name: "9 transactions",
   287  			args: args{
   288  				spamPowDifficulty:         20,
   289  				spamPoWNumberOfTxPerBlock: 5,
   290  				seenTx:                    9,
   291  			},
   292  			wantTotal:      184, // 5 * 20 + 4 * 21
   293  			wantDifficulty: 21,
   294  		},
   295  		{
   296  			name: "10 transactions",
   297  			args: args{
   298  				spamPowDifficulty:         20,
   299  				spamPoWNumberOfTxPerBlock: 5,
   300  				seenTx:                    10,
   301  			},
   302  			wantTotal:      205, // 5 * 20 + 5 * 21
   303  			wantDifficulty: 22,
   304  		},
   305  		{
   306  			name: "20 transactions",
   307  			args: args{
   308  				spamPowDifficulty:         20,
   309  				spamPoWNumberOfTxPerBlock: 5,
   310  				seenTx:                    20,
   311  			},
   312  			wantTotal:      430, // 5 * 20 + 5 * 21 + 5 * 22 + 5 * 23
   313  			wantDifficulty: 24,
   314  		},
   315  		{
   316  			name: "22 transactions",
   317  			args: args{
   318  				spamPowDifficulty:         20,
   319  				spamPoWNumberOfTxPerBlock: 5,
   320  				seenTx:                    22,
   321  			},
   322  			wantTotal:      478, // 5 * 20 + 5 * 21 + 5 * 22 + 5 * 23 + 2 * 24
   323  			wantDifficulty: 24,
   324  		},
   325  	}
   326  
   327  	for _, tt := range tests {
   328  		t.Run(tt.name, func(t *testing.T) {
   329  			gotTotal, gotDifficulty := calculateExpectedDifficulty(tt.args.spamPowDifficulty, tt.args.spamPoWNumberOfTxPerBlock, tt.args.seenTx)
   330  			require.Equal(t, tt.wantTotal, gotTotal)
   331  			require.Equal(t, tt.wantDifficulty, gotDifficulty)
   332  		})
   333  	}
   334  }
   335  
   336  func TestBeginBlock(t *testing.T) {
   337  	e := New(logging.NewTestLogger(), NewDefaultConfig())
   338  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(3))
   339  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
   340  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   341  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
   342  
   343  	e.BeginBlock(100, "113EB390CBEB921433BDBA832CCDFD81AC4C77C3748A41B1AF08C96BC6C7BCD9", []abci.Tx{})
   344  	e.BeginBlock(101, "C692100485479CE9E1815B9E0A66D3596295A04DB42170CB4B61CFAE7332ADD8", []abci.Tx{})
   345  	e.BeginBlock(102, "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", []abci.Tx{})
   346  
   347  	require.Equal(t, uint64(102), e.currentBlock)
   348  	require.Equal(t, uint64(100), e.blockHeight[100])
   349  	require.Equal(t, uint64(101), e.blockHeight[101])
   350  	require.Equal(t, uint64(102), e.blockHeight[102])
   351  	require.Equal(t, "113EB390CBEB921433BDBA832CCDFD81AC4C77C3748A41B1AF08C96BC6C7BCD9", e.blockHash[100])
   352  	require.Equal(t, "C692100485479CE9E1815B9E0A66D3596295A04DB42170CB4B61CFAE7332ADD8", e.blockHash[101])
   353  	require.Equal(t, "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", e.blockHash[102])
   354  
   355  	// now add some transactions for block 102 before it goes off
   356  	tx1 := &testTx{txID: "1", party: crypto.RandomHash(), blockHeight: 102, powTxID: "94A9CB1532011081B013CCD8E6AAA832CAB1CBA603F0C5A093B14C4961E5E7F0", powNonce: 431336}
   357  	tx2 := &testTx{txID: "2", party: crypto.RandomHash(), blockHeight: 102, powTxID: "DC911C0EA95545441F3E1182DD25D973764395A7E75CBDBC086F1C6F7075AED6", powNonce: 523162}
   358  
   359  	res1, d1 := e.CheckBlockTx(tx1)
   360  	res2, d2 := e.CheckBlockTx(tx2)
   361  	require.Equal(t, ValidationResultSuccess, res1)
   362  	require.Equal(t, ValidationResultSuccess, res2)
   363  	e.rollback([]ValidationEntry{{Tx: tx1, ValResult: res1, Difficulty: d1}, {Tx: tx2, ValResult: res2, Difficulty: d2}})
   364  	require.Equal(t, 0, len(e.seenTid))
   365  	require.Equal(t, 0, len(e.heightToTid[100]))
   366  
   367  	e.BeginBlock(103, "2E289FB9CEF7234E2C08F34CCD66B330229067CE47E22F76EF0595B3ABA9968F", []abci.Tx{tx1, tx2})
   368  	require.Equal(t, uint64(103), e.currentBlock)
   369  	require.Equal(t, uint64(103), e.blockHeight[103])
   370  	require.Equal(t, uint64(101), e.blockHeight[101])
   371  	require.Equal(t, uint64(102), e.blockHeight[102])
   372  	require.Equal(t, "C692100485479CE9E1815B9E0A66D3596295A04DB42170CB4B61CFAE7332ADD8", e.blockHash[101])
   373  	require.Equal(t, "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", e.blockHash[102])
   374  	require.Equal(t, "2E289FB9CEF7234E2C08F34CCD66B330229067CE47E22F76EF0595B3ABA9968F", e.blockHash[103])
   375  	require.Equal(t, 2, len(e.seenTid))
   376  	require.Equal(t, 2, len(e.seenTx))
   377  	require.Equal(t, 2, len(e.heightToTid[102]))
   378  	require.Equal(t, *d1, e.activeStates[0].blockToPartyState[102][tx1.party].observedDifficulty)
   379  	require.Equal(t, *d2, e.activeStates[0].blockToPartyState[102][tx2.party].observedDifficulty)
   380  	require.Equal(t, uint(1), e.activeStates[0].blockToPartyState[102][tx1.party].seenCount)
   381  	require.Equal(t, uint(1), e.activeStates[0].blockToPartyState[102][tx2.party].seenCount)
   382  }
   383  
   384  func TestAllowTransactionsAcrossMultipleBlocks(t *testing.T) {
   385  	e := New(logging.NewTestLogger(), NewDefaultConfig())
   386  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(10))
   387  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
   388  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   389  	e.UpdateSpamPoWNumberOfTxPerBlock(context.Background(), num.NewUint(1))
   390  	e.UpdateSpamPoWIncreasingDifficulty(context.Background(), num.NewUint(1))
   391  	e.BeginBlock(100, "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", []abci.Tx{})
   392  
   393  	// test happy days first - 4 transactions with increasing difficulty results in no ban - regardless of the order they come in
   394  	party := crypto.RandomHash()
   395  	txs := []*testTx{
   396  		{txID: "9", blockHeight: 100, party: party, powTxID: "DFE522E234D67E6AE3F017859F898E576B3928EA57310B765398615A0D3FDE2F", powNonce: 424517},   // 00000e31f8ac983354f5885d46b7631bc75f69ec82e8f6178bae53db0ab7e054 - 20
   397  		{txID: "10", blockHeight: 100, party: party, powTxID: "5B0E1EB96CCAC120E6D824A5F4C4007EABC59573B861BD84B1EF09DFB376DC84", powNonce: 4031737}, // 000002a98320df372412d7179ca2645b13ff3ecbe660e4a9a743fb423d8aec1f - 22
   398  		{txID: "11", blockHeight: 100, party: party, powTxID: "94A9CB1532011081B013CCD8E6AAA832CAB1CBA603F0C5A093B14C4961E5E7F0", powNonce: 431336},  // 000001c297318619efd60b9197f89e36fea83ca8d7461cf7b7c78af84e0a3b51 - 23
   399  		{txID: "8", blockHeight: 100, party: party, powTxID: "2A1319636230740888C968E4E7610D6DE820E644EEC3C08AA5322A0A022014BD", powNonce: 1421231},  // 000009c5043c4e1dd7fe190ece8d3fd83d94c4e2a2b7800456ce5f5a653c9f75 - 20
   400  	}
   401  
   402  	// process the first transaction on block 101
   403  	e.BeginBlock(101, crypto.RandomHash(), []abci.Tx{})
   404  	res, d := e.CheckBlockTx(txs[0])
   405  	require.Equal(t, ValidationResultSuccess, res)
   406  	e.rollback([]ValidationEntry{{ValResult: res, Difficulty: d, Tx: txs[0]}})
   407  
   408  	// process the second transaction on block 102
   409  	e.BeginBlock(102, crypto.RandomHash(), []abci.Tx{txs[0]})
   410  	res, d = e.CheckBlockTx(txs[1])
   411  	require.Equal(t, ValidationResultSuccess, res)
   412  	e.rollback([]ValidationEntry{{ValResult: res, Difficulty: d, Tx: txs[1]}})
   413  
   414  	// process the third transaction on block 103
   415  	e.BeginBlock(103, crypto.RandomHash(), []abci.Tx{txs[1]})
   416  	res, d = e.CheckBlockTx(txs[2])
   417  	require.Equal(t, ValidationResultSuccess, res)
   418  	e.rollback([]ValidationEntry{{ValResult: res, Difficulty: d, Tx: txs[2]}})
   419  
   420  	// process the last transaction on block 104
   421  	e.BeginBlock(104, crypto.RandomHash(), []abci.Tx{txs[2]})
   422  	res, d = e.CheckBlockTx(txs[3])
   423  	require.Equal(t, ValidationResultTooManyTx, res)
   424  	e.rollback([]ValidationEntry{{ValResult: res, Difficulty: d, Tx: txs[3]}})
   425  }
   426  
   427  func TestEdgeCase1(t *testing.T) {
   428  	ts := mocks.NewMockTimeService(gomock.NewController(t))
   429  	e := New(logging.NewTestLogger(), NewDefaultConfig())
   430  	e.UpdateSpamPoWDifficulty(context.Background(), num.NewUint(20))
   431  	e.UpdateSpamPoWNumberOfPastBlocks(context.Background(), num.NewUint(100))
   432  	e.UpdateSpamPoWHashFunction(context.Background(), crypto.Sha3)
   433  	ts.EXPECT().GetTimeNow().AnyTimes()
   434  
   435  	e.BeginBlock(1, "9DF61AC8AD2178E2E2FD2D94E0F07A4B8AA141213179B03C184F8EAD898A9336", []abci.Tx{})
   436  	nonce, _, _ := crypto.PoW("9DF61AC8AD2178E2E2FD2D94E0F07A4B8AA141213179B03C184F8EAD898A9336", "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", 20, "sha3_24_rounds")
   437  
   438  	e.BeginBlock(50, "1D5839A6F7BF1CDB681590890E9D50ECFA222C41F57D1F05229ED3DED533F59A", []abci.Tx{&testTx{txID: "5CCCE01E56B9666F39F007BF577F10BB46987CFE1B1BE80AAC1DBBF51F9C45FE", party: "zohar", blockHeight: 1, powTxID: "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", powNonce: nonce}})
   439  
   440  	// come block 100
   441  	e.BeginBlock(100, "2D2E4EC3DA3584F3FD4AD1BD1C0700E3C8DFB7BB1C307312AB35F18940836FC4", []abci.Tx{})
   442  
   443  	// block 100 ended, block 101 is being prepared, at this point transactions from block 1 are not valid anymore.
   444  	// we've cleared the state of the 100th oldest block seen tx (aka block 1) - now with the modified check for distance in verify - checktx should not allow the transaction in
   445  	require.Error(t, e.CheckTx(&testTx{txID: "5CCCE01E56B9666F39F007BF577F10BB46987CFE1B1BE80AAC1DBBF51F9C45FE", party: "zohar", blockHeight: 2261296, powTxID: "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", powNonce: nonce}))
   446  	e.BeginBlock(101, "2D2E4EC3DA3584F3FD4AD1BD1C0700E3C8DFB7BB1C307312AB35F18940836FC4", []abci.Tx{})
   447  
   448  	// verify for fun that we can't get the transaction at this point either.
   449  	require.Error(t, e.CheckTx(&testTx{txID: "5CCCE01E56B9666F39F007BF577F10BB46987CFE1B1BE80AAC1DBBF51F9C45FE", party: "zohar", blockHeight: 2261296, powTxID: "2E7A16D9EF690F0D2BEED115FBA13BA2AAA16C8F971910AD88C72B9DB010C7D4", powNonce: nonce}))
   450  }
   451  
   452  type testTx struct {
   453  	party       string
   454  	blockHeight uint64
   455  	powNonce    uint64
   456  	powTxID     string
   457  	txID        string
   458  	nonce       uint64
   459  }
   460  
   461  func (tx *testTx) TTL() uint64                 { return 100 }
   462  func (tx *testTx) GetLength() int              { return 0 }
   463  func (tx *testTx) Unmarshal(interface{}) error { return nil }
   464  func (tx *testTx) GetPoWTID() string           { return tx.powTxID }
   465  func (tx *testTx) GetVersion() uint32          { return 2 }
   466  func (tx *testTx) GetPoWNonce() uint64         { return tx.powNonce }
   467  
   468  func (tx *testTx) Signature() []byte    { return []byte{} }
   469  func (tx *testTx) Payload() []byte      { return []byte{} }
   470  func (tx *testTx) PubKey() []byte       { return []byte{} }
   471  func (tx *testTx) PubKeyHex() string    { return "" }
   472  func (tx *testTx) Party() string        { return tx.party }
   473  func (tx *testTx) Hash() []byte         { return []byte(tx.txID) }
   474  func (tx *testTx) Command() txn.Command { return txn.AmendOrderCommand }
   475  func (tx *testTx) BlockHeight() uint64  { return tx.blockHeight }
   476  func (tx *testTx) GetCmd() interface{}  { return nil }
   477  func (tx *testTx) Validate() error      { return nil }
   478  func (tx *testTx) GetNonce() uint64 {
   479  	if tx.nonce != 0 {
   480  		return tx.nonce
   481  	}
   482  	return rand.Uint64()
   483  }
   484  
   485  func Test_ExpectedSpamDifficulty(t *testing.T) {
   486  	type args struct {
   487  		spamPowDifficulty         uint
   488  		spamPoWNumberOfTxPerBlock uint
   489  		seenTx                    uint
   490  		observedDifficulty        uint
   491  		increaseDifficulty        bool
   492  	}
   493  
   494  	tests := []struct {
   495  		name  string
   496  		args  args
   497  		isNil bool
   498  		want  uint64
   499  	}{
   500  		{
   501  			name: "Expected difficulty after 12 txs",
   502  			args: args{
   503  				spamPowDifficulty:         10,
   504  				spamPoWNumberOfTxPerBlock: 5,
   505  				seenTx:                    12,
   506  				observedDifficulty:        132,
   507  				increaseDifficulty:        true,
   508  			},
   509  			isNil: false,
   510  			want:  10,
   511  		},
   512  		{
   513  			name: "Expected difficulty after 13 txs",
   514  			args: args{
   515  				spamPowDifficulty:         10,
   516  				spamPoWNumberOfTxPerBlock: 5,
   517  				seenTx:                    13,
   518  				observedDifficulty:        142,
   519  				increaseDifficulty:        true,
   520  			},
   521  			isNil: false,
   522  			want:  11,
   523  		},
   524  		{
   525  			name: "Expected difficulty after 14 txs",
   526  			args: args{
   527  				spamPowDifficulty:         10,
   528  				spamPoWNumberOfTxPerBlock: 5,
   529  				seenTx:                    14,
   530  				observedDifficulty:        153,
   531  				increaseDifficulty:        true,
   532  			},
   533  			isNil: false,
   534  			want:  12,
   535  		},
   536  		{
   537  			name: "Expected difficulty after 15 txs",
   538  			args: args{
   539  				spamPowDifficulty:         10,
   540  				spamPoWNumberOfTxPerBlock: 5,
   541  				seenTx:                    15,
   542  				observedDifficulty:        166,
   543  				increaseDifficulty:        true,
   544  			},
   545  			isNil: false,
   546  			want:  12, // after 15txs, the difficulty is increased to 13, but we should have 1 extra in the credit from the previous block
   547  		},
   548  		{
   549  			name: "Expected difficulty after 16 txs",
   550  			args: args{
   551  				spamPowDifficulty:         10,
   552  				spamPoWNumberOfTxPerBlock: 5,
   553  				seenTx:                    16,
   554  				observedDifficulty:        178,
   555  				increaseDifficulty:        true,
   556  			},
   557  			isNil: false,
   558  			want:  13,
   559  		},
   560  		{
   561  			name: "Expected difficulty after 17 txs",
   562  			args: args{
   563  				spamPowDifficulty:         10,
   564  				spamPoWNumberOfTxPerBlock: 5,
   565  				seenTx:                    17,
   566  				observedDifficulty:        193,
   567  				increaseDifficulty:        true,
   568  			},
   569  			isNil: false,
   570  			want:  11,
   571  		},
   572  		{
   573  			name: "Expected difficulty when increaseDifficulty is false",
   574  			args: args{
   575  				spamPowDifficulty:         10,
   576  				spamPoWNumberOfTxPerBlock: 5,
   577  				seenTx:                    17,
   578  				observedDifficulty:        193,
   579  				increaseDifficulty:        false,
   580  			},
   581  			isNil: true,
   582  		},
   583  		{
   584  			name: "Expected difficulty when increaseDifficulty is false but fewer seen than allowed in block",
   585  			args: args{
   586  				spamPowDifficulty:         10,
   587  				spamPoWNumberOfTxPerBlock: 100,
   588  				seenTx:                    1,
   589  				observedDifficulty:        10,
   590  				increaseDifficulty:        false,
   591  			},
   592  			isNil: false,
   593  			want:  10,
   594  		},
   595  	}
   596  
   597  	for _, tt := range tests {
   598  		t.Run(tt.name, func(t *testing.T) {
   599  			got := getMinDifficultyForNextTx(
   600  				tt.args.spamPowDifficulty,
   601  				tt.args.spamPoWNumberOfTxPerBlock,
   602  				tt.args.seenTx,
   603  				tt.args.observedDifficulty,
   604  				tt.args.increaseDifficulty)
   605  			if tt.isNil {
   606  				assert.Nil(t, got)
   607  				return
   608  			}
   609  
   610  			assert.Equal(t, tt.want, *got, "getMinDifficultyForNextTx() = %v, want %v", *got, tt.want)
   611  		})
   612  	}
   613  }