github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/stdmap/incorporated_result_seals_test.go (about)

     1  package stdmap
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/stretchr/testify/assert"
     7  	"github.com/stretchr/testify/require"
     8  
     9  	"pgregory.net/rapid"
    10  
    11  	"github.com/onflow/flow-go/model/flow"
    12  	"github.com/onflow/flow-go/utils/unittest"
    13  )
    14  
    15  // icrSealsMachine is a description of a state machine for testing IncorporatedresultSeals
    16  type icrSealsMachine struct {
    17  	icrs  *IncorporatedResultSeals       // icrSeals being tested
    18  	state []*flow.IncorporatedResultSeal // model of the icrSeals
    19  }
    20  
    21  // init is an action for initializing a icrSeals instance.
    22  func (m *icrSealsMachine) init(t *rapid.T) {
    23  	m.icrs = NewIncorporatedResultSeals(1000)
    24  }
    25  
    26  // Add is a conditional action which adds an item to the icrSeals.
    27  func (m *icrSealsMachine) Add(t *rapid.T) {
    28  	i := rapid.Uint64().Draw(t, "i")
    29  
    30  	seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) {
    31  		s.Header.Height = i
    32  	})
    33  
    34  	_, err := m.icrs.Add(seal)
    35  	require.NoError(t, err)
    36  
    37  	// we do not re-add already present seals
    38  	unmet := true
    39  	for _, v := range m.state {
    40  		if v.ID() == seal.ID() {
    41  			unmet = false
    42  		}
    43  	}
    44  
    45  	if unmet && seal.Header.Height >= m.icrs.lowestHeight {
    46  		m.state = append(m.state, seal)
    47  	}
    48  }
    49  
    50  // Prune is a Conditional action that removes elements of height strictly lower than its argument
    51  func (m *icrSealsMachine) PruneUpToHeight(t *rapid.T) {
    52  	h := rapid.Uint64().Draw(t, "h")
    53  	err := m.icrs.PruneUpToHeight(h)
    54  	if h >= m.icrs.lowestHeight {
    55  		require.NoError(t, err)
    56  		assert.Equal(t, m.icrs.lowestHeight, h)
    57  	}
    58  
    59  	filtered_state := make([]*flow.IncorporatedResultSeal, 0)
    60  	for _, v := range m.state {
    61  		if v.Header.Height >= h {
    62  			filtered_state = append(filtered_state, v)
    63  		}
    64  	}
    65  	m.state = filtered_state
    66  }
    67  
    68  // Get is an action that retrieves an element from the icrSeals
    69  func (m *icrSealsMachine) Get(t *rapid.T) {
    70  	n := len(m.state)
    71  	// skip if the store is empty
    72  	if n == 0 {
    73  		return
    74  	}
    75  	i := rapid.IntRange(0, n-1).Draw(t, "i")
    76  
    77  	s := m.state[i]
    78  	actual, ok := m.icrs.ByID(s.ID())
    79  	require.True(t, ok)
    80  	require.Equal(t, s, actual)
    81  
    82  }
    83  
    84  // GetUnknown is an action that removes an unknown element from the icrSeals
    85  // This mostly tests ByID has no insertion side-effects
    86  func (m *icrSealsMachine) GetUnknown(t *rapid.T) {
    87  	n := len(m.state)
    88  	// skip if the store is empty
    89  	if n == 0 {
    90  		return
    91  	}
    92  	i := rapid.IntRange(0, n-1).Draw(t, "i")
    93  	seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) {
    94  		s.Header.Height = uint64(i)
    95  	})
    96  
    97  	// check seal is unknown
    98  	unknown := true
    99  	for _, v := range m.state {
   100  		if v.ID() == seal.ID() {
   101  			unknown = false
   102  		}
   103  	}
   104  
   105  	if unknown {
   106  		_, found := m.icrs.ByID(seal.ID())
   107  		require.False(t, found)
   108  	}
   109  	// no modification of state
   110  
   111  }
   112  
   113  // Remove is a conditional action that removes a known element from the icrSeals
   114  func (m *icrSealsMachine) Remove(t *rapid.T) {
   115  	n := len(m.state)
   116  	// skip if the store is empty
   117  	if n == 0 {
   118  		return
   119  	}
   120  	i := rapid.IntRange(0, n-1).Draw(t, "i")
   121  
   122  	s := m.state[i]
   123  	ok := m.icrs.Remove(s.ID())
   124  	require.True(t, ok)
   125  
   126  	// remove m[i], we don't care about ordering here
   127  	m.state[n-1], m.state[i] = m.state[i], m.state[n-1]
   128  	m.state = m.state[:n-1]
   129  
   130  }
   131  
   132  // RemoveUnknown is an action that removes an unknown element from the icrSeals
   133  // This mostly tests Remove has no insertion side-effects
   134  func (m *icrSealsMachine) RemoveUnknown(t *rapid.T) {
   135  	n := len(m.state)
   136  	// skip if the store is empty
   137  	if n == 0 {
   138  		return
   139  	}
   140  	i := rapid.IntRange(0, n-1).Draw(t, "i")
   141  	seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) {
   142  		s.Header.Height = uint64(i)
   143  	})
   144  
   145  	// check seal is unknown
   146  	unknown := true
   147  	for _, v := range m.state {
   148  		if v.ID() == seal.ID() {
   149  			unknown = false
   150  		}
   151  	}
   152  
   153  	if unknown {
   154  		removed := m.icrs.Remove(seal.ID())
   155  		require.False(t, removed)
   156  	}
   157  	// no modification of state
   158  
   159  }
   160  
   161  // Check runs after every action and verifies that all required invariants hold.
   162  func (m *icrSealsMachine) Check(t *rapid.T) {
   163  	if int(m.icrs.Size()) != len(m.state) {
   164  		t.Fatalf("store size mismatch: %v vs expected %v", m.icrs.Size(), len(m.state))
   165  	}
   166  	assert.ElementsMatch(t, m.icrs.All(), m.state)
   167  }
   168  
   169  // Run the icrSeals state machine and test it against its model
   170  func TestIcrs(t *testing.T) {
   171  	rapid.Check(t, func(t *rapid.T) {
   172  		sm := new(icrSealsMachine)
   173  		sm.init(t)
   174  		t.Repeat(rapid.StateMachineActions(sm))
   175  	})
   176  }
   177  
   178  func TestIncorporatedResultSeals(t *testing.T) {
   179  	t.Parallel()
   180  
   181  	// after adding one receipt, should be able to query it back by previous result id
   182  	// after removing, should not be able to query it back.
   183  	t.Run("add remove get", func(t *testing.T) {
   184  		pool := NewIncorporatedResultSeals(1000)
   185  
   186  		seal := unittest.IncorporatedResultSeal.Fixture()
   187  
   188  		ok, err := pool.Add(seal)
   189  		require.NoError(t, err)
   190  		require.True(t, ok)
   191  
   192  		actual, ok := pool.ByID(seal.ID())
   193  		require.True(t, ok)
   194  		require.Equal(t, seal, actual)
   195  
   196  		deleted := pool.Remove(seal.ID())
   197  		require.True(t, deleted)
   198  
   199  		_, ok = pool.ByID(seal.ID())
   200  		require.False(t, ok)
   201  	})
   202  
   203  	t.Run("add 100 prune by height", func(t *testing.T) {
   204  		pool := NewIncorporatedResultSeals(1000)
   205  
   206  		seals := make([]*flow.IncorporatedResultSeal, 0, 100)
   207  		for i := 0; i < 100; i++ {
   208  			seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) {
   209  				s.Header.Height = uint64(i)
   210  			})
   211  
   212  			seals = append(seals, seal)
   213  		}
   214  
   215  		for _, seal := range seals {
   216  			ok, err := pool.Add(seal)
   217  			require.NoError(t, err)
   218  			require.True(t, ok, "seal at height %d was not added", seal.Header.Height)
   219  		}
   220  		verifyPresent(t, pool, seals...)
   221  
   222  		err := pool.PruneUpToHeight(5)
   223  		require.NoError(t, err)
   224  		verifyAbsent(t, pool, seals[:5]...)
   225  		verifyPresent(t, pool, seals[5:]...)
   226  
   227  		err = pool.PruneUpToHeight(10)
   228  		require.NoError(t, err)
   229  		verifyAbsent(t, pool, seals[:10]...)
   230  		verifyPresent(t, pool, seals[10:]...)
   231  	})
   232  
   233  	t.Run("prune with some nonexisting heights", func(t *testing.T) {
   234  		pool := NewIncorporatedResultSeals(1000)
   235  
   236  		// create seals, but NOT for heights 2 and 3
   237  		seals := make([]*flow.IncorporatedResultSeal, 0, 6)
   238  		for _, h := range []uint64{0, 1, 4, 5, 6, 7} {
   239  			seal := unittest.IncorporatedResultSeal.Fixture(func(s *flow.IncorporatedResultSeal) {
   240  				s.Header.Height = h
   241  			})
   242  			seals = append(seals, seal)
   243  			ok, err := pool.Add(seal)
   244  			require.NoError(t, err)
   245  			require.True(t, ok)
   246  		}
   247  
   248  		err := pool.PruneUpToHeight(5)
   249  		require.NoError(t, err)
   250  		verifyAbsent(t, pool, seals[:3]...)
   251  		verifyPresent(t, pool, seals[3:]...)
   252  	})
   253  }
   254  
   255  func verifyPresent(t *testing.T, pool *IncorporatedResultSeals, seals ...*flow.IncorporatedResultSeal) {
   256  	for _, seal := range seals {
   257  		_, ok := pool.ByID(seal.ID())
   258  		require.True(t, ok, "seal at height %d should be in mempool", seal.Header.Height)
   259  	}
   260  }
   261  
   262  func verifyAbsent(t *testing.T, pool *IncorporatedResultSeals, seals ...*flow.IncorporatedResultSeal) {
   263  	for _, seal := range seals {
   264  		_, ok := pool.ByID(seal.ID())
   265  		require.False(t, ok, "seal at height %d should not be in mempool", seal.Header.Height)
   266  	}
   267  }