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 }