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  }