github.com/vipernet-xyz/tm@v0.34.24/blockchain/v2/processor_test.go (about)

     1  package v2
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  
     8  	"github.com/vipernet-xyz/tm/p2p"
     9  	tmState "github.com/vipernet-xyz/tm/state"
    10  	"github.com/vipernet-xyz/tm/types"
    11  )
    12  
    13  // pcBlock is a test helper structure with simple types. Its purpose is to help with test readability.
    14  type pcBlock struct {
    15  	pid    string
    16  	height int64
    17  }
    18  
    19  // params is a test structure used to create processor state.
    20  type params struct {
    21  	height       int64
    22  	items        []pcBlock
    23  	blocksSynced int
    24  	verBL        []int64
    25  	appBL        []int64
    26  	draining     bool
    27  }
    28  
    29  // makePcBlock makes an empty block.
    30  func makePcBlock(height int64) *types.Block {
    31  	return &types.Block{Header: types.Header{Height: height}}
    32  }
    33  
    34  // makeState takes test parameters and creates a specific processor state.
    35  func makeState(p *params) *pcState {
    36  	var (
    37  		tmState = tmState.State{LastBlockHeight: p.height}
    38  		context = newMockProcessorContext(tmState, p.verBL, p.appBL)
    39  	)
    40  	state := newPcState(context)
    41  
    42  	for _, item := range p.items {
    43  		state.enqueue(p2p.ID(item.pid), makePcBlock(item.height), item.height)
    44  	}
    45  
    46  	state.blocksSynced = p.blocksSynced
    47  	state.draining = p.draining
    48  	return state
    49  }
    50  
    51  func mBlockResponse(peerID p2p.ID, height int64) scBlockReceived {
    52  	return scBlockReceived{
    53  		peerID: peerID,
    54  		block:  makePcBlock(height),
    55  	}
    56  }
    57  
    58  type pcFsmMakeStateValues struct {
    59  	currentState  *params
    60  	event         Event
    61  	wantState     *params
    62  	wantNextEvent Event
    63  	wantErr       error
    64  	wantPanic     bool
    65  }
    66  
    67  type testFields struct {
    68  	name  string
    69  	steps []pcFsmMakeStateValues
    70  }
    71  
    72  func executeProcessorTests(t *testing.T, tests []testFields) {
    73  	for _, tt := range tests {
    74  		tt := tt
    75  		t.Run(tt.name, func(t *testing.T) {
    76  			var state *pcState
    77  			for _, step := range tt.steps {
    78  				defer func() {
    79  					r := recover()
    80  					if (r != nil) != step.wantPanic {
    81  						t.Errorf("recover = %v, wantPanic = %v", r, step.wantPanic)
    82  					}
    83  				}()
    84  
    85  				// First step must always initialise the currentState as state.
    86  				if step.currentState != nil {
    87  					state = makeState(step.currentState)
    88  				}
    89  				if state == nil {
    90  					panic("Bad (initial?) step")
    91  				}
    92  
    93  				nextEvent, err := state.handle(step.event)
    94  				t.Log(state)
    95  				assert.Equal(t, step.wantErr, err)
    96  				assert.Equal(t, makeState(step.wantState), state)
    97  				assert.Equal(t, step.wantNextEvent, nextEvent)
    98  				// Next step may use the wantedState as their currentState.
    99  				state = makeState(step.wantState)
   100  			}
   101  		})
   102  	}
   103  }
   104  
   105  func TestRProcessPeerError(t *testing.T) {
   106  	tests := []testFields{
   107  		{
   108  			name: "error for existing peer",
   109  			steps: []pcFsmMakeStateValues{
   110  				{
   111  					currentState:  &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
   112  					event:         scPeerError{peerID: "P2"},
   113  					wantState:     &params{items: []pcBlock{{"P1", 1}}},
   114  					wantNextEvent: noOp,
   115  				},
   116  			},
   117  		},
   118  		{
   119  			name: "error for unknown peer",
   120  			steps: []pcFsmMakeStateValues{
   121  				{
   122  					currentState:  &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
   123  					event:         scPeerError{peerID: "P3"},
   124  					wantState:     &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}},
   125  					wantNextEvent: noOp,
   126  				},
   127  			},
   128  		},
   129  	}
   130  
   131  	executeProcessorTests(t, tests)
   132  }
   133  
   134  func TestPcBlockResponse(t *testing.T) {
   135  	tests := []testFields{
   136  		{
   137  			name: "add one block",
   138  			steps: []pcFsmMakeStateValues{
   139  				{
   140  					currentState: &params{}, event: mBlockResponse("P1", 1),
   141  					wantState: &params{items: []pcBlock{{"P1", 1}}}, wantNextEvent: noOp,
   142  				},
   143  			},
   144  		},
   145  
   146  		{
   147  			name: "add two blocks",
   148  			steps: []pcFsmMakeStateValues{
   149  				{
   150  					currentState: &params{}, event: mBlockResponse("P1", 3),
   151  					wantState: &params{items: []pcBlock{{"P1", 3}}}, wantNextEvent: noOp,
   152  				},
   153  				{ // use previous wantState as currentState,
   154  					event:     mBlockResponse("P1", 4),
   155  					wantState: &params{items: []pcBlock{{"P1", 3}, {"P1", 4}}}, wantNextEvent: noOp,
   156  				},
   157  			},
   158  		},
   159  	}
   160  
   161  	executeProcessorTests(t, tests)
   162  }
   163  
   164  func TestRProcessBlockSuccess(t *testing.T) {
   165  	tests := []testFields{
   166  		{
   167  			name: "noop - no blocks over current height",
   168  			steps: []pcFsmMakeStateValues{
   169  				{
   170  					currentState: &params{}, event: rProcessBlock{},
   171  					wantState: &params{}, wantNextEvent: noOp,
   172  				},
   173  			},
   174  		},
   175  		{
   176  			name: "noop - high new blocks",
   177  			steps: []pcFsmMakeStateValues{
   178  				{
   179  					currentState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, event: rProcessBlock{},
   180  					wantState: &params{height: 5, items: []pcBlock{{"P1", 30}, {"P2", 31}}}, wantNextEvent: noOp,
   181  				},
   182  			},
   183  		},
   184  		{
   185  			name: "blocks H+1 and H+2 present",
   186  			steps: []pcFsmMakeStateValues{
   187  				{
   188  					currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}}, event: rProcessBlock{},
   189  					wantState:     &params{height: 1, items: []pcBlock{{"P2", 2}}, blocksSynced: 1},
   190  					wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
   191  				},
   192  			},
   193  		},
   194  		{
   195  			name: "blocks H+1 and H+2 present after draining",
   196  			steps: []pcFsmMakeStateValues{
   197  				{ // some contiguous blocks - on stop check draining is set
   198  					currentState:  &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}},
   199  					event:         scFinishedEv{},
   200  					wantState:     &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P1", 4}}, draining: true},
   201  					wantNextEvent: noOp,
   202  				},
   203  				{
   204  					event:         rProcessBlock{},
   205  					wantState:     &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
   206  					wantNextEvent: pcBlockProcessed{height: 1, peerID: "P1"},
   207  				},
   208  				{ // finish when H+1 or/and H+2 are missing
   209  					event:         rProcessBlock{},
   210  					wantState:     &params{height: 1, items: []pcBlock{{"P2", 2}, {"P1", 4}}, blocksSynced: 1, draining: true},
   211  					wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 1}, blocksSynced: 1},
   212  				},
   213  			},
   214  		},
   215  	}
   216  
   217  	executeProcessorTests(t, tests)
   218  }
   219  
   220  func TestRProcessBlockFailures(t *testing.T) {
   221  	tests := []testFields{
   222  		{
   223  			name: "blocks H+1 and H+2 present from different peers - H+1 verification fails ",
   224  			steps: []pcFsmMakeStateValues{
   225  				{
   226  					currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, verBL: []int64{1}}, event: rProcessBlock{},
   227  					wantState:     &params{items: []pcBlock{}, verBL: []int64{1}},
   228  					wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P2"},
   229  				},
   230  			},
   231  		},
   232  		{
   233  			name: "blocks H+1 and H+2 present from same peer - H+1 applyBlock fails ",
   234  			steps: []pcFsmMakeStateValues{
   235  				{
   236  					currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}}, appBL: []int64{1}}, event: rProcessBlock{},
   237  					wantState: &params{items: []pcBlock{}, appBL: []int64{1}}, wantPanic: true,
   238  				},
   239  			},
   240  		},
   241  		{
   242  			name: "blocks H+1 and H+2 present from same peers - H+1 verification fails ",
   243  			steps: []pcFsmMakeStateValues{
   244  				{
   245  					currentState: &params{height: 0, items: []pcBlock{{"P1", 1}, {"P1", 2}, {"P2", 3}},
   246  						verBL: []int64{1}}, event: rProcessBlock{},
   247  					wantState:     &params{height: 0, items: []pcBlock{{"P2", 3}}, verBL: []int64{1}},
   248  					wantNextEvent: pcBlockVerificationFailure{height: 1, firstPeerID: "P1", secondPeerID: "P1"},
   249  				},
   250  			},
   251  		},
   252  		{
   253  			name: "blocks H+1 and H+2 present from different peers - H+1 applyBlock fails ",
   254  			steps: []pcFsmMakeStateValues{
   255  				{
   256  					currentState: &params{items: []pcBlock{{"P1", 1}, {"P2", 2}, {"P2", 3}}, appBL: []int64{1}},
   257  					event:        rProcessBlock{},
   258  					wantState:    &params{items: []pcBlock{{"P2", 3}}, appBL: []int64{1}}, wantPanic: true,
   259  				},
   260  			},
   261  		},
   262  	}
   263  
   264  	executeProcessorTests(t, tests)
   265  }
   266  
   267  func TestScFinishedEv(t *testing.T) {
   268  	tests := []testFields{
   269  		{
   270  			name: "no blocks",
   271  			steps: []pcFsmMakeStateValues{
   272  				{
   273  					currentState: &params{height: 100, items: []pcBlock{}, blocksSynced: 100}, event: scFinishedEv{},
   274  					wantState:     &params{height: 100, items: []pcBlock{}, blocksSynced: 100},
   275  					wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
   276  				},
   277  			},
   278  		},
   279  		{
   280  			name: "maxHeight+1 block present",
   281  			steps: []pcFsmMakeStateValues{
   282  				{
   283  					currentState: &params{height: 100, items: []pcBlock{
   284  						{"P1", 101}}, blocksSynced: 100}, event: scFinishedEv{},
   285  					wantState:     &params{height: 100, items: []pcBlock{{"P1", 101}}, blocksSynced: 100},
   286  					wantNextEvent: pcFinished{tmState: tmState.State{LastBlockHeight: 100}, blocksSynced: 100},
   287  				},
   288  			},
   289  		},
   290  		{
   291  			name: "more blocks present",
   292  			steps: []pcFsmMakeStateValues{
   293  				{
   294  					currentState: &params{height: 100, items: []pcBlock{
   295  						{"P1", 101}, {"P1", 102}}, blocksSynced: 100}, event: scFinishedEv{},
   296  					wantState: &params{height: 100, items: []pcBlock{
   297  						{"P1", 101}, {"P1", 102}}, blocksSynced: 100, draining: true},
   298  					wantNextEvent: noOp,
   299  					wantErr:       nil,
   300  				},
   301  			},
   302  		},
   303  	}
   304  
   305  	executeProcessorTests(t, tests)
   306  }