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 }