github.com/iotexproject/iotex-core@v1.14.1-rc1/actpool/actqueue_test.go (about)

     1  // Copyright (c) 2019 IoTeX Foundation
     2  // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability
     3  // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed.
     4  // This source code is governed by Apache License 2.0 that can be found in the LICENSE file.
     5  package actpool
     6  
     7  import (
     8  	"container/heap"
     9  	"context"
    10  	"fmt"
    11  	"math/big"
    12  	"math/rand"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/facebookgo/clock"
    17  	"github.com/golang/mock/gomock"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	"github.com/iotexproject/iotex-core/action"
    21  	"github.com/iotexproject/iotex-core/action/protocol"
    22  	"github.com/iotexproject/iotex-core/blockchain/genesis"
    23  	"github.com/iotexproject/iotex-core/state"
    24  	"github.com/iotexproject/iotex-core/test/identityset"
    25  	"github.com/iotexproject/iotex-core/test/mock/mock_chainmanager"
    26  )
    27  
    28  const (
    29  	maxBalance = 1e7
    30  )
    31  
    32  func TestNoncePriorityQueue(t *testing.T) {
    33  	require := require.New(t)
    34  	pq := ascNoncePriorityQueue{}
    35  	// Push four dummy nonce to the queue
    36  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(1)})
    37  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(3)})
    38  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(2)})
    39  	// Test Pop implementation
    40  	i := uint64(1)
    41  	for pq.Len() > 0 {
    42  		nonce := heap.Pop(&pq).(*nonceWithTTL).nonce
    43  		require.Equal(i, nonce)
    44  		i++
    45  	}
    46  	// Repush the four dummy nonce back to the queue
    47  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(3)})
    48  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(2)})
    49  	heap.Push(&pq, &nonceWithTTL{nonce: uint64(1)})
    50  	// Test built-in Remove implementation
    51  	// Remove a random nonce from noncePriorityQueue
    52  	rand.Seed(time.Now().UnixNano())
    53  	heap.Remove(&pq, rand.Intn(pq.Len()))
    54  	t.Log("After randomly removing a dummy nonce, the remaining dummy nonces in the order of popped are as follows:")
    55  	for pq.Len() > 0 {
    56  		nonce := heap.Pop(&pq).(*nonceWithTTL).nonce
    57  		t.Log(nonce)
    58  		t.Log()
    59  	}
    60  }
    61  
    62  func TestActQueuePut(t *testing.T) {
    63  	require := require.New(t)
    64  	q := NewActQueue(nil, "", 1, big.NewInt(maxBalance)).(*actQueue)
    65  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 2, big.NewInt(100), nil, uint64(0), big.NewInt(1))
    66  	require.NoError(err)
    67  	require.NoError(q.Put(tsf1))
    68  	require.Equal(uint64(2), q.ascQueue[0].nonce)
    69  	require.NotNil(q.items[tsf1.Nonce()])
    70  	tsf2, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(100), nil, uint64(0), big.NewInt(1))
    71  	require.NoError(err)
    72  	require.NoError(q.Put(tsf2))
    73  	require.Equal(uint64(1), heap.Pop(&q.ascQueue).(*nonceWithTTL).nonce)
    74  	require.Equal(tsf2, q.items[uint64(1)])
    75  	require.Equal(uint64(2), heap.Pop(&q.ascQueue).(*nonceWithTTL).nonce)
    76  	require.Equal(tsf1, q.items[uint64(2)])
    77  	// tsf3 is a act which fails to cut in line
    78  	tsf3, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
    79  	require.NoError(err)
    80  	require.Error(q.Put(tsf3))
    81  	// tsf4 is a act which succeeds in cutting in line
    82  	tsf4, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(1000), nil, uint64(0), big.NewInt(2))
    83  	require.NoError(err)
    84  	require.NoError(q.Put(tsf4))
    85  }
    86  
    87  func TestActQueueFilterNonce(t *testing.T) {
    88  	require := require.New(t)
    89  	q := NewActQueue(nil, "", 1, big.NewInt(maxBalance)).(*actQueue)
    90  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(1), nil, uint64(0), big.NewInt(0))
    91  	require.NoError(err)
    92  	tsf2, err := action.SignedTransfer(_addr2, _priKey1, 2, big.NewInt(1), nil, uint64(0), big.NewInt(0))
    93  	require.NoError(err)
    94  	tsf3, err := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
    95  	require.NoError(err)
    96  	require.NoError(q.Put(tsf1))
    97  	require.NoError(q.Put(tsf2))
    98  	require.NoError(q.Put(tsf3))
    99  	q.UpdateAccountState(3, big.NewInt(maxBalance))
   100  	require.Equal(1, len(q.items))
   101  	require.Equal(uint64(3), q.ascQueue[0].nonce)
   102  	require.Equal(tsf3, q.items[q.ascQueue[0].nonce])
   103  }
   104  
   105  func TestActQueueUpdateNonce(t *testing.T) {
   106  	require := require.New(t)
   107  	q := NewActQueue(nil, "", 1, big.NewInt(1010)).(*actQueue)
   108  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(1), nil, uint64(0), big.NewInt(0))
   109  	require.NoError(err)
   110  	tsf2, err := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   111  	require.NoError(err)
   112  	tsf3, err := action.SignedTransfer(_addr2, _priKey1, 4, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   113  	require.NoError(err)
   114  	tsf4, err := action.SignedTransfer(_addr2, _priKey1, 6, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   115  	require.NoError(err)
   116  	tsf5, err := action.SignedTransfer(_addr2, _priKey1, 2, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   117  	require.NoError(err)
   118  	require.NoError(q.Put(tsf1))
   119  	require.NoError(q.Put(tsf2))
   120  	require.NoError(q.Put(tsf3))
   121  	require.NoError(q.Put(tsf4))
   122  	require.NoError(q.Put(tsf5))
   123  	require.Equal(uint64(3), q.pendingNonce)
   124  }
   125  
   126  func TestActQueuePendingActs(t *testing.T) {
   127  	ctrl := gomock.NewController(t)
   128  	require := require.New(t)
   129  
   130  	sf := mock_chainmanager.NewMockStateReader(ctrl)
   131  	sf.EXPECT().State(gomock.Any(), gomock.Any()).Do(func(accountState *state.Account, _ protocol.StateOption) {
   132  		require.NoError(accountState.SetPendingNonce(accountState.PendingNonce() + 1))
   133  		accountState.Balance = big.NewInt(maxBalance)
   134  	}).Return(uint64(0), nil).Times(1)
   135  	sf.EXPECT().Height().Return(uint64(1), nil).AnyTimes()
   136  	ctx := protocol.WithFeatureCtx(protocol.WithBlockCtx(
   137  		genesis.WithGenesisContext(context.Background(), genesis.Default), protocol.BlockCtx{
   138  			BlockHeight: 1,
   139  		}))
   140  	ap, err := NewActPool(genesis.Default, sf, DefaultConfig)
   141  	require.NoError(err)
   142  	q := NewActQueue(ap.(*actPool), identityset.Address(0).String(), 1, big.NewInt(maxBalance)).(*actQueue)
   143  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 2, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   144  	require.NoError(err)
   145  	tsf2, err := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   146  	require.NoError(err)
   147  	tsf3, err := action.SignedTransfer(_addr2, _priKey1, 5, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   148  	require.NoError(err)
   149  	tsf4, err := action.SignedTransfer(_addr2, _priKey1, 6, big.NewInt(10000), nil, uint64(0), big.NewInt(0))
   150  	require.NoError(err)
   151  	tsf5, err := action.SignedTransfer(_addr2, _priKey1, 7, big.NewInt(100000), nil, uint64(0), big.NewInt(0))
   152  	require.NoError(err)
   153  	require.NoError(q.Put(tsf1))
   154  	require.NoError(q.Put(tsf2))
   155  	require.NoError(q.Put(tsf3))
   156  	require.NoError(q.Put(tsf4))
   157  	require.NoError(q.Put(tsf5))
   158  	q.pendingNonce = 4
   159  	actions := q.PendingActs(ctx)
   160  	require.Equal([]*action.SealedEnvelope{tsf1, tsf2}, actions)
   161  }
   162  
   163  func TestActQueueAllActs(t *testing.T) {
   164  	require := require.New(t)
   165  	q := NewActQueue(nil, "", 1, big.NewInt(maxBalance)).(*actQueue)
   166  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   167  	require.NoError(err)
   168  	tsf3, err := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(1000), nil, uint64(0), big.NewInt(0))
   169  	require.NoError(err)
   170  	require.NoError(q.Put(tsf1))
   171  	require.NoError(q.Put(tsf3))
   172  	actions := q.AllActs()
   173  	require.Equal([]*action.SealedEnvelope{tsf1, tsf3}, actions)
   174  }
   175  
   176  func TestActQueueTimeOutAction(t *testing.T) {
   177  	c := clock.NewMock()
   178  	q := NewActQueue(nil, "", 1, big.NewInt(maxBalance), WithClock(c), WithTimeOut(3*time.Minute))
   179  	tsf1, err := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   180  	require.NoError(t, err)
   181  	tsf2, err := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   182  	require.NoError(t, err)
   183  
   184  	require.NoError(t, q.Put(tsf1))
   185  	c.Add(2 * time.Minute)
   186  
   187  	require.NoError(t, q.Put(tsf2))
   188  	q.(*actQueue).cleanTimeout()
   189  	require.Equal(t, 2, q.Len())
   190  	c.Add(2 * time.Minute)
   191  	q.(*actQueue).cleanTimeout()
   192  	require.Equal(t, 2, q.Len())
   193  	c.Add(2 * time.Minute)
   194  	q.(*actQueue).cleanTimeout()
   195  	require.Equal(t, 1, q.Len())
   196  }
   197  
   198  func TestActQueueCleanTimeout(t *testing.T) {
   199  	require := require.New(t)
   200  	q := NewActQueue(nil, "", 1, big.NewInt(1000)).(*actQueue)
   201  	mockClock := clock.NewMock()
   202  	q.clock = mockClock
   203  	q.ttl = 2 * time.Minute
   204  	tsf1, _ := action.SignedTransfer(_addr2, _priKey1, 1, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   205  	tsf2, _ := action.SignedTransfer(_addr2, _priKey1, 2, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   206  	tsf3, _ := action.SignedTransfer(_addr2, _priKey1, 3, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   207  	tsf5, _ := action.SignedTransfer(_addr2, _priKey1, 5, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   208  	tsf6, _ := action.SignedTransfer(_addr2, _priKey1, 6, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   209  	tsf7, _ := action.SignedTransfer(_addr2, _priKey1, 7, big.NewInt(100), nil, uint64(0), big.NewInt(0))
   210  	require.NoError(q.Put(tsf7))
   211  	mockClock.Add(10 * time.Minute)
   212  	require.NoError(q.Put(tsf1))
   213  	require.NoError(q.Put(tsf5))
   214  	mockClock.Add(1 * time.Minute)
   215  	require.NoError(q.Put(tsf2))
   216  	require.NoError(q.Put(tsf6))
   217  	require.NoError(q.Put(tsf3))
   218  
   219  	q.cleanTimeout()
   220  	require.Equal(5, len(q.ascQueue))
   221  	expectedHeap := []uint64{1, 2, 3, 5, 6}
   222  	for i := range expectedHeap {
   223  		require.Equal(expectedHeap[i], q.ascQueue[i].nonce)
   224  	}
   225  	mockClock.Add(2 * time.Minute)
   226  	ret := q.cleanTimeout()
   227  	require.Equal(1, len(ret))
   228  }
   229  
   230  // BenchmarkHeapInitAndRemove compare the heap re-establish performance between
   231  // using the heap.Init and the heap.Remove after remove some elements.
   232  // The bench result show that the performance of heap.Init is better than heap.Remove
   233  // in the most cases.
   234  // More detail to see the discusses in https://github.com/iotexproject/iotex-core/pull/3013
   235  func BenchmarkHeapInitAndRemove(b *testing.B) {
   236  	const batch = 20
   237  	testIndex := ascNoncePriorityQueue{}
   238  	index := ascNoncePriorityQueue{}
   239  	invalidTime := time.Now()
   240  	validTime := time.Now().Add(10 * time.Minute)
   241  	for k := uint64(1); k <= batch; k++ {
   242  		for j := uint64(0); j < batch; j++ {
   243  			if j < k {
   244  				heap.Push(&testIndex, &nonceWithTTL{nonce: j, deadline: invalidTime})
   245  			} else {
   246  				heap.Push(&testIndex, &nonceWithTTL{nonce: j, deadline: validTime})
   247  			}
   248  		}
   249  		b.ResetTimer()
   250  		b.Run(fmt.Sprintf("heap.Remove-(%d/%d)", k, batch), func(b *testing.B) {
   251  			for i := 0; i < b.N; i++ {
   252  				// init
   253  				index = index[:0]
   254  				for _, nonce := range testIndex {
   255  					nonce2 := *nonce
   256  					index = append(index, &nonce2)
   257  				}
   258  				// algo
   259  				removedNonceList := make([]*nonceWithTTL, 0, batch)
   260  				for _, nonce := range index {
   261  					if invalidTime.Equal(nonce.deadline) {
   262  						removedNonceList = append(removedNonceList, nonce)
   263  					}
   264  				}
   265  				for _, removedNonce := range removedNonceList {
   266  					heap.Remove(&index, removedNonce.ascIdx)
   267  				}
   268  			}
   269  		})
   270  		b.ResetTimer()
   271  		b.Run(fmt.Sprintf("heap.Init-(%d/%d)", k, batch), func(b *testing.B) {
   272  			for i := 0; i < b.N; i++ {
   273  				// init
   274  				index = index[:0]
   275  				for _, nonce := range testIndex {
   276  					nonce2 := *nonce
   277  					index = append(index, &nonce2)
   278  				}
   279  				// algo
   280  				size := index.Len()
   281  				for j := 0; j < size; {
   282  					if invalidTime.Equal(index[j].deadline) {
   283  						index[j] = index[size-1]
   284  						size--
   285  						continue
   286  					}
   287  					j++
   288  				}
   289  				index = index[:size]
   290  				heap.Init(&index)
   291  			}
   292  		})
   293  	}
   294  }