github.com/Finschia/ostracon@v1.1.5/rpc/core/mempool_test.go (about) 1 package core 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 ocabcicli "github.com/Finschia/ostracon/abci/client" 8 ocabci "github.com/Finschia/ostracon/abci/types" 9 "github.com/Finschia/ostracon/config" 10 "github.com/Finschia/ostracon/libs/log" 11 "github.com/Finschia/ostracon/mempool" 12 memv0 "github.com/Finschia/ostracon/mempool/v0" 13 "github.com/Finschia/ostracon/proxy/mocks" 14 ctypes "github.com/Finschia/ostracon/rpc/core/types" 15 rpctypes "github.com/Finschia/ostracon/rpc/jsonrpc/types" 16 "github.com/Finschia/ostracon/types" 17 "github.com/stretchr/testify/assert" 18 "github.com/stretchr/testify/mock" 19 abci "github.com/tendermint/tendermint/abci/types" 20 "net/http" 21 "sync" 22 "testing" 23 "time" 24 ) 25 26 type ErrorAssertionFunc func(t assert.TestingT, theError error, contains string, msgAndArgs ...interface{}) bool 27 28 func TestBroadcastTxAsync(t *testing.T) { 29 type args struct { 30 ctx *rpctypes.Context 31 tx types.Tx 32 } 33 tx := types.Tx{} 34 tests := []struct { 35 name string 36 args args 37 want *ctypes.ResultBroadcastTx 38 wantErr assert.ErrorAssertionFunc 39 err error 40 }{ 41 { 42 name: "success", 43 args: args{ 44 ctx: &rpctypes.Context{}, 45 tx: tx, 46 }, 47 want: &ctypes.ResultBroadcastTx{ 48 Code: abci.CodeTypeOK, 49 Data: nil, 50 Log: "", 51 Codespace: "", 52 MempoolError: "", 53 Hash: tx.Hash(), 54 }, 55 wantErr: assert.NoError, 56 err: nil, 57 }, 58 { 59 name: "failure: tx is same the one before", 60 args: args{ 61 ctx: &rpctypes.Context{}, 62 tx: tx, 63 }, 64 want: nil, 65 wantErr: assert.Error, 66 err: mempool.ErrTxInCache, 67 }, 68 } 69 env = &Environment{} 70 mockAppConnMempool := &mocks.AppConnMempool{} 71 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 72 mockAppConnMempool.On("CheckTxAsync", mock.Anything, mock.Anything).Return(&ocabcicli.ReqRes{}) 73 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 74 for _, tt := range tests { 75 t.Run(tt.name, func(t *testing.T) { 76 mockAppConnMempool.On("Error").Return(tt.err).Once() 77 got, err := BroadcastTxAsync(tt.args.ctx, tt.args.tx) 78 if !tt.wantErr(t, err, fmt.Sprintf("BroadcastTxAsync(%v, %v)", tt.args.ctx, tt.args.tx)) { 79 return 80 } 81 assert.Equal(t, tt.err, err) 82 assert.Equalf(t, tt.want, got, "BroadcastTxAsync(%v, %v)", tt.args.ctx, tt.args.tx) 83 }) 84 } 85 } 86 87 func TestBroadcastTxSync(t *testing.T) { 88 type args struct { 89 ctx *rpctypes.Context 90 tx types.Tx 91 } 92 tx := types.Tx{} 93 tests := []struct { 94 name string 95 args args 96 want *ctypes.ResultBroadcastTx 97 wantErr assert.ErrorAssertionFunc 98 err error 99 }{ 100 { 101 name: "success", 102 args: args{ 103 ctx: &rpctypes.Context{}, 104 tx: tx, 105 }, 106 want: &ctypes.ResultBroadcastTx{ 107 Code: ocabci.CodeTypeOK, 108 Data: nil, 109 Log: "", 110 Codespace: "", 111 MempoolError: "", 112 Hash: tx.Hash(), 113 }, 114 wantErr: assert.NoError, 115 err: nil, 116 }, 117 { 118 name: "failure: tx is same the one before", 119 args: args{ 120 ctx: &rpctypes.Context{}, 121 tx: tx, 122 }, 123 want: nil, 124 wantErr: assert.Error, 125 err: mempool.ErrTxInMap, 126 }, 127 } 128 env = &Environment{} 129 mockAppConnMempool := &mocks.AppConnMempool{} 130 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 131 mockAppConnMempool.On("CheckTxSync", mock.Anything).Return(&ocabci.ResponseCheckTx{}, nil) 132 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 133 for _, tt := range tests { 134 t.Run(tt.name, func(t *testing.T) { 135 mockAppConnMempool.On("Error").Return(tt.err).Once() 136 got, err := BroadcastTxSync(tt.args.ctx, tt.args.tx) 137 if !tt.wantErr(t, err, fmt.Sprintf("BroadcastTxSync(%v, %v)", tt.args.ctx, tt.args.tx)) { 138 return 139 } 140 assert.Equal(t, tt.err, err) 141 assert.Equalf(t, tt.want, got, "BroadcastTxSync(%v, %v)", tt.args.ctx, tt.args.tx) 142 }) 143 } 144 } 145 146 // TestBroadcastTxSyncWithCancelContextForCheckTxSync test in isolation from TestBroadcastTxSync since avoiding coexistence 147 func TestBroadcastTxSyncWithCancelContextForCheckTxSync(t *testing.T) { 148 type args struct { 149 ctx *rpctypes.Context 150 tx types.Tx 151 } 152 errContext, cancel := context.WithCancel(context.Background()) 153 defer cancel() // for safety to avoid memory leaks 154 req := &http.Request{} 155 req = req.WithContext(errContext) 156 errRpcContext := rpctypes.Context{HTTPReq: req} 157 tx := types.Tx{} 158 tests := []struct { 159 name string 160 args args 161 want *ctypes.ResultBroadcastTx 162 wantErr ErrorAssertionFunc 163 err error 164 }{ 165 { 166 name: "failure(non-deterministic test, retry please): interrupted by context", 167 args: args{ 168 ctx: &errRpcContext, 169 tx: tx, 170 }, 171 want: nil, 172 wantErr: assert.ErrorContains, 173 err: fmt.Errorf("broadcast confirmation not received: context canceled"), 174 }, 175 } 176 env = &Environment{} 177 mockAppConnMempool := &mocks.AppConnMempool{} 178 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 179 mockAppConnMempool.On("CheckTxSync", mock.Anything).Return( 180 &ocabci.ResponseCheckTx{Code: abci.CodeTypeOK}, nil).WaitUntil( 181 time.After(1000 * time.Millisecond)) // Wait calling the context cancel 182 mockAppConnMempool.On("Error").Return(nil) // Not to use tt.err 183 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 184 for _, tt := range tests { 185 t.Run(tt.name, func(t *testing.T) { 186 // cancel context for while doing `env.Mempool.CheckTxSync` 187 cancel() 188 wg := &sync.WaitGroup{} 189 wg.Add(1) 190 go func() { 191 got, err := BroadcastTxSync(tt.args.ctx, tt.args.tx) 192 if !tt.wantErr(t, err, tt.err.Error(), fmt.Sprintf("BroadcastTxSync(%v, %v)", tt.args.ctx, tt.args.tx)) { 193 wg.Done() 194 return 195 } 196 assert.Equalf(t, tt.want, got, "BroadcastTxSync(%v, %v)", tt.args.ctx, tt.args.tx) 197 wg.Done() 198 }() 199 wg.Wait() 200 }) 201 } 202 } 203 204 func TestBroadcastTxCommit(t *testing.T) { 205 type args struct { 206 ctx *rpctypes.Context 207 tx types.Tx 208 } 209 height := int64(1) 210 tx := types.Tx{} 211 tx1 := types.Tx{1} 212 tests := []struct { 213 name string 214 args args 215 want *ctypes.ResultBroadcastTxCommit 216 wantErr assert.ErrorAssertionFunc 217 }{ 218 { 219 name: "success", 220 args: args{ 221 ctx: &rpctypes.Context{}, 222 tx: tx, 223 }, 224 want: &ctypes.ResultBroadcastTxCommit{ 225 CheckTx: ocabci.ResponseCheckTx{ 226 Code: abci.CodeTypeOK, 227 }, 228 DeliverTx: abci.ResponseDeliverTx{ 229 Code: abci.CodeTypeOK, 230 Data: tx, 231 }, 232 Hash: tx.Hash(), 233 Height: height, 234 }, 235 wantErr: assert.NoError, 236 }, 237 { 238 name: "success but CheckTxResponse is not OK", 239 args: args{ 240 ctx: &rpctypes.Context{}, 241 tx: tx1, 242 }, 243 want: &ctypes.ResultBroadcastTxCommit{ 244 CheckTx: ocabci.ResponseCheckTx{ 245 Code: abci.CodeTypeOK + 1, // Not OK 246 }, 247 DeliverTx: abci.ResponseDeliverTx{}, // return empty response 248 Hash: tx1.Hash(), 249 Height: 0, // return empty height 250 }, 251 wantErr: assert.NoError, 252 }, 253 } 254 env = &Environment{} 255 env.Logger = log.TestingLogger() 256 env.Config = *config.TestConfig().RPC 257 env.EventBus = types.NewEventBus() 258 err := env.EventBus.OnStart() 259 defer env.EventBus.OnStop() 260 assert.NoError(t, err) 261 mockAppConnMempool := &mocks.AppConnMempool{} 262 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 263 mockAppConnMempool.On("Error").Return(nil) 264 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 265 for _, tt := range tests { 266 t.Run(tt.name, func(t *testing.T) { 267 mockAppConnMempool.On("CheckTxSync", mock.Anything).Return(&tt.want.CheckTx, nil).Once() 268 wg := &sync.WaitGroup{} 269 wg.Add(1) 270 go func() { 271 got, err := BroadcastTxCommit(tt.args.ctx, tt.args.tx) 272 if !tt.wantErr(t, err, fmt.Sprintf("BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx)) { 273 wg.Done() 274 return 275 } 276 assert.Equalf(t, tt.want, got, "BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx) 277 wg.Done() 278 }() 279 // Wait the time for `env.EventBus.Subscribe` in BroadcastTxCommit 280 time.Sleep(10 * time.Millisecond) 281 err := env.EventBus.PublishEventTx(types.EventDataTx{ 282 TxResult: abci.TxResult{ 283 Height: height, 284 Index: 0, 285 Tx: tt.args.tx, 286 Result: tt.want.DeliverTx, 287 }, 288 }) 289 assert.NoError(t, err) 290 wg.Wait() 291 }) 292 } 293 } 294 295 func TestBroadcastTxCommitWithCancelContextForCheckTxSync(t *testing.T) { 296 type args struct { 297 ctx *rpctypes.Context 298 tx types.Tx 299 } 300 errContext, cancel := context.WithCancel(context.Background()) 301 defer cancel() // for safety to avoid memory leaks 302 req := &http.Request{} 303 req = req.WithContext(errContext) 304 errRpcContext := rpctypes.Context{HTTPReq: req} 305 height := int64(1) 306 tx := types.Tx{} 307 resCheckTx := ocabci.ResponseCheckTx{ 308 Code: abci.CodeTypeOK, 309 } 310 resDeliverTx := abci.ResponseDeliverTx{ 311 Code: abci.CodeTypeOK, 312 Data: tx, 313 } 314 tests := []struct { 315 name string 316 args args 317 want *ctypes.ResultBroadcastTxCommit 318 wantErr ErrorAssertionFunc 319 err error 320 }{ 321 { 322 name: "failure(non-deterministic test, retry please): interrupted by context", 323 args: args{ 324 ctx: &errRpcContext, 325 tx: tx, 326 }, 327 want: nil, 328 wantErr: assert.ErrorContains, 329 err: fmt.Errorf("broadcast confirmation not received: context canceled"), 330 }, 331 } 332 env = &Environment{} 333 env.Logger = log.TestingLogger() 334 env.Config = *config.TestConfig().RPC 335 env.EventBus = types.NewEventBus() 336 err := env.EventBus.OnStart() 337 defer env.EventBus.OnStop() 338 assert.NoError(t, err) 339 mockAppConnMempool := &mocks.AppConnMempool{} 340 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 341 mockAppConnMempool.On("Error").Return(nil) 342 mockAppConnMempool.On("CheckTxSync", mock.Anything).Return(&resCheckTx, nil).WaitUntil( 343 time.After(1 * time.Second)) // Wait calling the context cancel 344 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 345 for _, tt := range tests { 346 t.Run(tt.name, func(t *testing.T) { 347 wg := &sync.WaitGroup{} 348 wg.Add(2) 349 go func() { 350 // Wait the time for `env.EventBus.Subscribe` in BroadcastTxCommit 351 time.Sleep(10 * time.Millisecond) 352 err := env.EventBus.PublishEventTx(types.EventDataTx{ 353 TxResult: abci.TxResult{ 354 Height: height, 355 Index: 0, 356 Tx: tx, 357 Result: resDeliverTx, 358 }, 359 }) 360 assert.NoError(t, err) 361 // cancel context for while doing `env.Mempool.CheckTxSync` 362 cancel() 363 wg.Done() 364 }() 365 go func() { 366 got, err := BroadcastTxCommit(tt.args.ctx, tt.args.tx) 367 if !tt.wantErr(t, err, tt.err.Error(), fmt.Sprintf("BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx)) { 368 wg.Done() 369 return 370 } 371 assert.Equalf(t, tt.want, got, "BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx) 372 wg.Done() 373 }() 374 wg.Wait() 375 }) 376 } 377 } 378 379 func TestBroadcastTxCommitTimeout(t *testing.T) { 380 type args struct { 381 ctx *rpctypes.Context 382 tx types.Tx 383 } 384 tx := types.Tx{} 385 tests := []struct { 386 name string 387 args args 388 want *ctypes.ResultBroadcastTxCommit 389 wantErr ErrorAssertionFunc 390 err error 391 }{ 392 { 393 name: "failure: timeout", 394 args: args{ 395 ctx: &rpctypes.Context{}, 396 tx: tx, 397 }, 398 want: &ctypes.ResultBroadcastTxCommit{ 399 CheckTx: ocabci.ResponseCheckTx{ 400 Code: abci.CodeTypeOK, 401 }, 402 DeliverTx: abci.ResponseDeliverTx{}, // return empty response 403 Hash: tx.Hash(), 404 Height: 0, // return empty height 405 }, 406 wantErr: assert.ErrorContains, 407 err: errors.New("timed out waiting for tx to be included in a block"), 408 }, 409 } 410 env = &Environment{} 411 env.Logger = log.TestingLogger() 412 env.Config = *config.TestConfig().RPC 413 env.Config.TimeoutBroadcastTxCommit = 1 // For test 414 env.EventBus = types.NewEventBus() 415 err := env.EventBus.OnStart() 416 defer env.EventBus.OnStop() 417 assert.NoError(t, err) 418 mockAppConnMempool := &mocks.AppConnMempool{} 419 mockAppConnMempool.On("SetGlobalCallback", mock.Anything) 420 mockAppConnMempool.On("Error").Return(nil) 421 env.Mempool = memv0.NewCListMempool(config.TestConfig().Mempool, mockAppConnMempool, 0) 422 for _, tt := range tests { 423 t.Run(tt.name, func(t *testing.T) { 424 mockAppConnMempool.On("CheckTxSync", mock.Anything).Return(&tt.want.CheckTx, nil).Once() 425 wg := &sync.WaitGroup{} 426 wg.Add(1) 427 go func() { 428 got, err := BroadcastTxCommit(tt.args.ctx, tt.args.tx) 429 if !tt.wantErr(t, err, tt.err.Error(), fmt.Sprintf("BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx)) { 430 wg.Done() 431 return 432 } 433 assert.Equalf(t, tt.want, got, "BroadcastTxCommit(%v, %v)", tt.args.ctx, tt.args.tx) 434 wg.Done() 435 }() 436 wg.Wait() 437 }) 438 } 439 }