github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/rpc/core/blocks_test.go (about)

     1  package core
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	dbm "github.com/cometbft/cometbft-db"
    13  	"github.com/badrootd/celestia-core/crypto/merkle"
    14  	"github.com/badrootd/celestia-core/libs/pubsub/query"
    15  	cmtrand "github.com/badrootd/celestia-core/libs/rand"
    16  	"github.com/badrootd/celestia-core/types"
    17  
    18  	abci "github.com/badrootd/celestia-core/abci/types"
    19  	cmtstate "github.com/badrootd/celestia-core/proto/tendermint/state"
    20  	ctypes "github.com/badrootd/celestia-core/rpc/core/types"
    21  	rpctypes "github.com/badrootd/celestia-core/rpc/jsonrpc/types"
    22  	sm "github.com/badrootd/celestia-core/state"
    23  )
    24  
    25  func TestBlockchainInfo(t *testing.T) {
    26  	cases := []struct {
    27  		min, max     int64
    28  		base, height int64
    29  		limit        int64
    30  		resultLength int64
    31  		wantErr      bool
    32  	}{
    33  
    34  		// min > max
    35  		{0, 0, 0, 0, 10, 0, true},  // min set to 1
    36  		{0, 1, 0, 0, 10, 0, true},  // max set to height (0)
    37  		{0, 0, 0, 1, 10, 1, false}, // max set to height (1)
    38  		{2, 0, 0, 1, 10, 0, true},  // max set to height (1)
    39  		{2, 1, 0, 5, 10, 0, true},
    40  
    41  		// negative
    42  		{1, 10, 0, 14, 10, 10, false}, // control
    43  		{-1, 10, 0, 14, 10, 0, true},
    44  		{1, -10, 0, 14, 10, 0, true},
    45  		{-9223372036854775808, -9223372036854775788, 0, 100, 20, 0, true},
    46  
    47  		// check base
    48  		{1, 1, 1, 1, 1, 1, false},
    49  		{2, 5, 3, 5, 5, 3, false},
    50  
    51  		// check limit and height
    52  		{1, 1, 0, 1, 10, 1, false},
    53  		{1, 1, 0, 5, 10, 1, false},
    54  		{2, 2, 0, 5, 10, 1, false},
    55  		{1, 2, 0, 5, 10, 2, false},
    56  		{1, 5, 0, 1, 10, 1, false},
    57  		{1, 5, 0, 10, 10, 5, false},
    58  		{1, 15, 0, 10, 10, 10, false},
    59  		{1, 15, 0, 15, 10, 10, false},
    60  		{1, 15, 0, 15, 20, 15, false},
    61  		{1, 20, 0, 15, 20, 15, false},
    62  		{1, 20, 0, 20, 20, 20, false},
    63  	}
    64  
    65  	for i, c := range cases {
    66  		caseString := fmt.Sprintf("test %d failed", i)
    67  		min, max, err := filterMinMax(c.base, c.height, c.min, c.max, c.limit)
    68  		if c.wantErr {
    69  			require.Error(t, err, caseString)
    70  		} else {
    71  			require.NoError(t, err, caseString)
    72  			require.Equal(t, 1+max-min, c.resultLength, caseString)
    73  		}
    74  	}
    75  }
    76  
    77  func TestBlockResults(t *testing.T) {
    78  	results := &cmtstate.ABCIResponses{
    79  		DeliverTxs: []*abci.ResponseDeliverTx{
    80  			{Code: 0, Data: []byte{0x01}, Log: "ok"},
    81  			{Code: 0, Data: []byte{0x02}, Log: "ok"},
    82  			{Code: 1, Log: "not ok"},
    83  		},
    84  		EndBlock:   &abci.ResponseEndBlock{},
    85  		BeginBlock: &abci.ResponseBeginBlock{},
    86  	}
    87  
    88  	globalEnv = &Environment{}
    89  	globalEnv.StateStore = sm.NewStore(dbm.NewMemDB(), sm.StoreOptions{
    90  		DiscardABCIResponses: false,
    91  	})
    92  	err := globalEnv.StateStore.SaveABCIResponses(100, results)
    93  	require.NoError(t, err)
    94  	globalEnv.BlockStore = mockBlockStore{height: 100}
    95  	SetEnvironment(globalEnv)
    96  
    97  	testCases := []struct {
    98  		height  int64
    99  		wantErr bool
   100  		wantRes *ctypes.ResultBlockResults
   101  	}{
   102  		{-1, true, nil},
   103  		{0, true, nil},
   104  		{101, true, nil},
   105  		{100, false, &ctypes.ResultBlockResults{
   106  			Height:                100,
   107  			TxsResults:            results.DeliverTxs,
   108  			BeginBlockEvents:      results.BeginBlock.Events,
   109  			EndBlockEvents:        results.EndBlock.Events,
   110  			ValidatorUpdates:      results.EndBlock.ValidatorUpdates,
   111  			ConsensusParamUpdates: results.EndBlock.ConsensusParamUpdates,
   112  		}},
   113  	}
   114  
   115  	for _, tc := range testCases {
   116  		res, err := BlockResults(&rpctypes.Context{}, &tc.height)
   117  		if tc.wantErr {
   118  			assert.Error(t, err)
   119  		} else {
   120  			assert.NoError(t, err)
   121  			assert.Equal(t, tc.wantRes, res)
   122  		}
   123  	}
   124  }
   125  
   126  func TestEncodeDataRootTuple(t *testing.T) {
   127  	height := uint64(2)
   128  	dataRoot, err := hex.DecodeString("82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013")
   129  	require.NoError(t, err)
   130  
   131  	expectedEncoding, err := hex.DecodeString(
   132  		// hex representation of height padded to 32 bytes
   133  		"0000000000000000000000000000000000000000000000000000000000000002" +
   134  			// data root
   135  			"82dc1607d84557d3579ce602a45f5872e821c36dbda7ec926dfa17ebc8d5c013",
   136  	)
   137  	require.NoError(t, err)
   138  	require.NotNil(t, expectedEncoding)
   139  
   140  	actualEncoding, err := EncodeDataRootTuple(height, *(*[32]byte)(dataRoot))
   141  	require.NoError(t, err)
   142  	require.NotNil(t, actualEncoding)
   143  
   144  	// Check that the length of packed data is correct
   145  	assert.Equal(t, len(actualEncoding), 64)
   146  	assert.Equal(t, expectedEncoding, actualEncoding)
   147  }
   148  
   149  func TestDataCommitmentResults(t *testing.T) {
   150  	env := &Environment{}
   151  	height := int64(2826)
   152  
   153  	blocks := randomBlocks(height)
   154  	blockStore := mockBlockStore{
   155  		height: height,
   156  		blocks: blocks,
   157  	}
   158  	env.BlockStore = blockStore
   159  
   160  	testCases := []struct {
   161  		beginQuery int
   162  		endQuery   int
   163  		expectPass bool
   164  	}{
   165  		{10, 15, true},
   166  		{2727, 2828, false},
   167  		{10, 9, false},
   168  		{0, 1000, false},
   169  		{0, 10, false},
   170  		{10, 8, false},
   171  		// to test the end exclusive support for ranges.
   172  		// the end block could be equal to (height+1), but the data commitment would only
   173  		// take up to height. So we should be able to send request having end block equal
   174  		// to (height+1).
   175  		{int(env.BlockStore.Height()) - 100, int(env.BlockStore.Height()) + 1, true},
   176  	}
   177  
   178  	for i, tc := range testCases {
   179  		env.BlockIndexer = mockBlockIndexer{
   180  			height:          height,
   181  			beginQueryBlock: tc.beginQuery,
   182  			endQueryBlock:   tc.endQuery,
   183  		}
   184  		SetEnvironment(env)
   185  
   186  		actualCommitment, err := DataCommitment(&rpctypes.Context{}, uint64(tc.beginQuery), uint64(tc.endQuery))
   187  		if tc.expectPass {
   188  			require.Nil(t, err, "should generate the needed data commitment.")
   189  
   190  			size := tc.endQuery - tc.beginQuery
   191  			dataRootEncodedTuples := make([][]byte, size)
   192  			for i := 0; i < size; i++ {
   193  				encodedTuple, err := EncodeDataRootTuple(
   194  					uint64(blocks[tc.beginQuery+i].Height),
   195  					*(*[32]byte)(blocks[tc.beginQuery+i].DataHash),
   196  				)
   197  				require.NoError(t, err)
   198  				dataRootEncodedTuples[i] = encodedTuple
   199  			}
   200  			expectedCommitment := merkle.HashFromByteSlices(dataRootEncodedTuples)
   201  
   202  			assert.Equal(
   203  				t,
   204  				expectedCommitment,
   205  				actualCommitment.DataCommitment.Bytes(),
   206  				i,
   207  			)
   208  		} else {
   209  			require.NotNil(t, err, "couldn't generate the needed data commitment.")
   210  		}
   211  	}
   212  }
   213  
   214  func TestDataRootInclusionProofResults(t *testing.T) {
   215  	env := &Environment{}
   216  	env.StateStore = sm.NewStore(
   217  		dbm.NewMemDB(), sm.StoreOptions{
   218  			DiscardABCIResponses: false,
   219  		},
   220  	)
   221  
   222  	height := int64(2826)
   223  	env.BlockStore = mockBlockStore{height: height}
   224  	SetEnvironment(env)
   225  
   226  	blocks := randomBlocks(height)
   227  	blockStore := mockBlockStore{
   228  		height: height,
   229  		blocks: blocks,
   230  	}
   231  	env.BlockStore = blockStore
   232  
   233  	testCases := []struct {
   234  		height     int
   235  		firstQuery int
   236  		lastQuery  int
   237  		expectPass bool
   238  	}{
   239  		{8, 10, 15, false},
   240  		{10, 0, 15, false},
   241  		{10, 10, 15, true},
   242  		{13, 10, 15, true},
   243  		{14, 10, 15, true},
   244  		{15, 10, 15, false},
   245  		{17, 10, 15, false},
   246  	}
   247  
   248  	for i, tc := range testCases {
   249  		env.BlockIndexer = mockBlockIndexer{
   250  			height:          height,
   251  			beginQueryBlock: tc.firstQuery,
   252  			endQueryBlock:   tc.lastQuery,
   253  		}
   254  
   255  		proof, err := DataRootInclusionProof(
   256  			&rpctypes.Context{},
   257  			int64(tc.height),
   258  			uint64(tc.firstQuery),
   259  			uint64(tc.lastQuery),
   260  		)
   261  		if tc.expectPass {
   262  			require.Nil(t, err, "should generate block height data root inclusion proof.", i)
   263  
   264  			size := tc.lastQuery - tc.firstQuery
   265  			dataRootEncodedTuples := make([][]byte, size)
   266  			for i := 0; i < size; i++ {
   267  				encodedTuple, err := EncodeDataRootTuple(
   268  					uint64(blocks[tc.firstQuery+i].Height),
   269  					*(*[32]byte)(blocks[tc.firstQuery+i].DataHash),
   270  				)
   271  				require.NoError(t, err)
   272  				dataRootEncodedTuples[i] = encodedTuple
   273  			}
   274  			commitment := merkle.HashFromByteSlices(dataRootEncodedTuples)
   275  
   276  			err = proof.Proof.Verify(commitment, dataRootEncodedTuples[tc.height-tc.firstQuery])
   277  			require.NoError(t, err)
   278  		} else {
   279  			require.NotNil(t, err, "shouldn't be able to generate proof.")
   280  		}
   281  	}
   282  }
   283  
   284  type mockBlockStore struct {
   285  	height int64
   286  	blocks []*types.Block
   287  }
   288  
   289  func (mockBlockStore) Base() int64                                       { return 1 }
   290  func (store mockBlockStore) Height() int64                               { return store.height }
   291  func (store mockBlockStore) Size() int64                                 { return store.height }
   292  func (mockBlockStore) LoadBaseMeta() *types.BlockMeta                    { return nil }
   293  func (mockBlockStore) LoadBlockByHash(hash []byte) *types.Block          { return nil }
   294  func (mockBlockStore) LoadBlockPart(height int64, index int) *types.Part { return nil }
   295  func (mockBlockStore) LoadBlockMetaByHash(hash []byte) *types.BlockMeta  { return nil }
   296  func (mockBlockStore) LoadBlockCommit(height int64) *types.Commit        { return nil }
   297  func (mockBlockStore) LoadSeenCommit(height int64) *types.Commit         { return nil }
   298  func (mockBlockStore) PruneBlocks(height int64) (uint64, error)          { return 0, nil }
   299  func (mockBlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenCommit *types.Commit) {
   300  }
   301  func (mockBlockStore) DeleteLatestBlock() error { return nil }
   302  
   303  func (store mockBlockStore) LoadBlockMeta(height int64) *types.BlockMeta {
   304  	if height > store.height {
   305  		return nil
   306  	}
   307  	block := store.blocks[height]
   308  	return &types.BlockMeta{
   309  		BlockID: types.BlockID{Hash: block.Hash(), PartSetHeader: block.MakePartSet(types.BlockPartSizeBytes).Header()},
   310  		Header:  block.Header,
   311  	}
   312  }
   313  
   314  func (store mockBlockStore) LoadBlock(height int64) *types.Block {
   315  	if height > store.height {
   316  		return nil
   317  	}
   318  	return store.blocks[height]
   319  }
   320  
   321  // mockBlockIndexer used to mock the set of indexed blocks and return a predefined one.
   322  type mockBlockIndexer struct {
   323  	height          int64
   324  	beginQueryBlock int // used not to have to parse any query
   325  	endQueryBlock   int // used not to have to parse any query
   326  }
   327  
   328  func (indexer mockBlockIndexer) Has(height int64) (bool, error)            { return true, nil }
   329  func (indexer mockBlockIndexer) Index(types.EventDataNewBlockHeader) error { return nil }
   330  
   331  // Search returns a list of block heights corresponding to the values of `indexer.endQueryBlock`
   332  // and `indexer.beginQueryBlock`.
   333  // Doesn't use the query parameter for anything.
   334  func (indexer mockBlockIndexer) Search(ctx context.Context, _ *query.Query) ([]int64, error) {
   335  	size := indexer.endQueryBlock - indexer.beginQueryBlock + 1
   336  	results := make([]int64, size)
   337  	for i := 0; i < size; i++ {
   338  		results[i] = int64(indexer.beginQueryBlock + i)
   339  	}
   340  	return results, nil
   341  }
   342  
   343  // randomBlocks generates a set of random blocks up to (and including) the provided height.
   344  func randomBlocks(height int64) []*types.Block {
   345  	blocks := make([]*types.Block, height+1)
   346  	for i := int64(0); i <= height; i++ {
   347  		blocks[i] = randomBlock(i)
   348  	}
   349  	return blocks
   350  }
   351  
   352  // randomBlock generates a Block with a certain height and random data hash.
   353  func randomBlock(height int64) *types.Block {
   354  	return &types.Block{
   355  		Header: types.Header{
   356  			Height:   height,
   357  			DataHash: cmtrand.Bytes(32),
   358  		},
   359  	}
   360  }