diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 50408a91..cf63702b 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -18,7 +18,7 @@ jobs: timeout-minutes: 30 env: DO_NOT_TRACK: true - ROLLKIT_VERSION: "v1.0.0-beta.1" + ROLLKIT_VERSION: "v0.14.2-0.20250718231032-db45c7baea8b" IGNITE_VERSION: "v29.2.0" outputs: carol_mnemonic: ${{ steps.save_mnemonic.outputs.carol_mnemonic }} @@ -41,7 +41,7 @@ jobs: - name: Start Local DA run: | - go install github.com/rollkit/rollkit/da/cmd/local-da@$ROLLKIT_VERSION + go install github.com/rollkit/rollkit/da/cmd/local-da@v1.0.0-beta.1 # start the local da in the background local-da & # capture the background process PID @@ -224,7 +224,7 @@ jobs: GMD_HOME: ${{ needs.liveness.outputs.gmd_home }} HERMES_VERSION: "v1.13.1" CEL_VERSION: "v4.0.3-arabica" - ROLLKIT_VERSION: "v1.0.0-beta.1" + ROLLKIT_VERSION: "v0.14.2-0.20250718231032-db45c7baea8b" steps: - name: Set up Go @@ -265,7 +265,7 @@ jobs: - name: Start Local DA run: | - go install github.com/rollkit/rollkit/da/cmd/local-da@$ROLLKIT_VERSION + go install github.com/rollkit/rollkit/da/cmd/local-da@v1.0.0-beta.1 # start the local da in the background local-da & # capture the background process PID diff --git a/.github/workflows/migration_test.yml b/.github/workflows/migration_test.yml index 93744c70..eaf2cb7c 100644 --- a/.github/workflows/migration_test.yml +++ b/.github/workflows/migration_test.yml @@ -14,7 +14,7 @@ jobs: timeout-minutes: 45 env: DO_NOT_TRACK: true - ROLLKIT_VERSION: "v1.0.0-beta.1" + ROLLKIT_VERSION: "v0.14.2-0.20250718231032-db45c7baea8b" IGNITE_VERSION: "v29.2.0" steps: - uses: actions/checkout@v4 @@ -200,7 +200,7 @@ jobs: - name: Start Local DA for Rollkit run: | - go install github.com/rollkit/rollkit/da/cmd/local-da@$ROLLKIT_VERSION + go install github.com/rollkit/rollkit/da/cmd/local-da@v1.0.0-beta.1 # start the local da in the background local-da & # capture the background process PID diff --git a/go.mod b/go.mod index 5010ebe3..87a169ac 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.24.1 replace ( github.com/btcsuite/btcd => github.com/btcsuite/btcd v0.22.0-beta github.com/cosmos/cosmos-sdk => github.com/cosmos/cosmos-sdk v0.50.14 + github.com/rollkit/rollkit => github.com/rollkit/rollkit v0.14.2-0.20250718231032-db45c7baea8b ) exclude github.com/btcsuite/btcd/chaincfg/chainhash v1.0.2 diff --git a/go.sum b/go.sum index 0c43b1e1..65324778 100644 --- a/go.sum +++ b/go.sum @@ -987,8 +987,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/rollkit/rollkit v1.0.0-beta.1 h1:d9Wkb5XgMtIdbsYm+hOoFdc+AcX0fI7ram0X3iREOUc= -github.com/rollkit/rollkit v1.0.0-beta.1/go.mod h1:sjDcpuc+djvPmhaDf0+CFJZ5mCuselDnh21H+e7gv6o= +github.com/rollkit/rollkit v0.14.2-0.20250718231032-db45c7baea8b h1:3f5B1b8qfBDpnLi4VkmARXKGA+flZYuR7geiuQM9D6I= +github.com/rollkit/rollkit v0.14.2-0.20250718231032-db45c7baea8b/go.mod h1:A2anu7qflYMlF83WXel+fs2ETpP2VsVIjarBLXWFcb4= github.com/rollkit/rollkit/core v1.0.0-beta.1 h1:lAcFWOTUaNGDWEDIjVv07FWizJswBsF3JWi9jEScV7k= github.com/rollkit/rollkit/core v1.0.0-beta.1/go.mod h1:0RhbqC8Is970KRhr6zPUQOZkmKt6/WqPRDQWfd2P7P0= github.com/rollkit/rollkit/da v1.0.0-beta.1 h1:MjN845kIsuRpisgnPJ2d3zuF6aN/uzOaiy/FCtJL7i0= diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go index e682aaa2..2a8951e6 100644 --- a/pkg/adapter/adapter.go +++ b/pkg/adapter/adapter.go @@ -12,7 +12,7 @@ import ( "cosmossdk.io/log" abci "github.com/cometbft/cometbft/abci/types" cmtcfg "github.com/cometbft/cometbft/config" - "github.com/cometbft/cometbft/libs/bytes" + cmtbytes "github.com/cometbft/cometbft/libs/bytes" "github.com/cometbft/cometbft/mempool" corep2p "github.com/cometbft/cometbft/p2p" cmtprototypes "github.com/cometbft/cometbft/proto/tendermint/types" @@ -326,13 +326,13 @@ func (a *Adapter) ExecuteTxs( return nil, 0, fmt.Errorf("get last commit: %w", err) } - emptyBlock, err := cometcompat.ToABCIBlock(header, &types.Data{}, lastCommit) + abciHeader, err := cometcompat.ToABCIHeader(&header.Header, lastCommit) if err != nil { return nil, 0, fmt.Errorf("compute header hash: %w", err) } ppResp, err := a.App.ProcessProposal(&abci.RequestProcessProposal{ - Hash: emptyBlock.Header.Hash(), + Hash: abciHeader.Hash(), Height: int64(blockHeight), Time: timestamp, Txs: txs, @@ -350,21 +350,23 @@ func (a *Adapter) ExecuteTxs( } fbResp, err := a.App.FinalizeBlock(&abci.RequestFinalizeBlock{ - Hash: emptyBlock.Header.Hash(), + Hash: abciHeader.Hash(), NextValidatorsHash: s.NextValidators.Hash(), ProposerAddress: s.Validators.Proposer.Address, Height: int64(blockHeight), Time: timestamp, - DecidedLastCommit: abci.CommitInfo{ - Round: 0, - Votes: nil, - }, - Txs: txs, + DecidedLastCommit: cometCommitToABCICommitInfo(lastCommit), + Txs: txs, }) if err != nil { return nil, 0, err } + // save commit to store to avoid recomputing it later + if err := a.Store.SaveLastCommit(ctx, blockHeight, lastCommit); err != nil { + return nil, 0, fmt.Errorf("save commit: %w", err) + } + for i, tx := range txs { sum256 := sha256.Sum256(tx) a.Logger.Debug("Processed TX", "hash", strings.ToUpper(hex.EncodeToString(sum256[:])), "result", fbResp.TxResults[i].Code, "log", fbResp.TxResults[i].Log, "height", blockHeight) @@ -461,7 +463,7 @@ func (a *Adapter) ExecuteTxs( { BlockIDFlag: cmttypes.BlockIDFlagCommit, ValidatorAddress: s.Validators.Proposer.Address, - Timestamp: time.Now().UTC(), + Timestamp: timestamp, Signature: []byte{}, }, } @@ -470,8 +472,11 @@ func (a *Adapter) ExecuteTxs( block := s.MakeBlock(int64(blockHeight), cmtTxs, lastCommit, nil, s.Validators.Proposer.Address) currentBlockID := cmttypes.BlockID{ - Hash: block.Hash(), - PartSetHeader: cmttypes.PartSetHeader{Total: 1, Hash: block.DataHash}, + Hash: block.Hash(), + PartSetHeader: cmttypes.PartSetHeader{ + Total: 1, + Hash: block.Hash(), + }, } if a.blockFilter.IsPublishable(ctx, int64(header.Height())) { @@ -479,6 +484,7 @@ func (a *Adapter) ExecuteTxs( if err := a.Store.SaveBlockResponse(ctx, blockHeight, fbResp); err != nil { return nil, 0, fmt.Errorf("save block response: %w", err) } + if err := fireEvents(a.EventBus, block, currentBlockID, fbResp, validatorUpdates); err != nil { return nil, 0, fmt.Errorf("fire events: %w", err) } @@ -556,6 +562,7 @@ func fireEvents( return nil } +// getLastCommit retrieves the last commit for the given block height. func (a *Adapter) getLastCommit(ctx context.Context, blockHeight uint64) (*cmttypes.Commit, error) { if blockHeight > 1 { header, data, err := a.RollkitStore.GetBlockData(ctx, blockHeight-1) @@ -564,9 +571,15 @@ func (a *Adapter) getLastCommit(ctx context.Context, blockHeight uint64) (*cmtty } commitForPrevBlock := &cmttypes.Commit{ - Height: int64(header.Height()), - Round: 0, - BlockID: cmttypes.BlockID{Hash: bytes.HexBytes(header.Hash()), PartSetHeader: cmttypes.PartSetHeader{Total: 1, Hash: bytes.HexBytes(data.Hash())}}, + Height: int64(header.Height()), + Round: 0, + BlockID: cmttypes.BlockID{ + Hash: cmtbytes.HexBytes(data.Hash()), + PartSetHeader: cmttypes.PartSetHeader{ + Total: 1, + Hash: cmtbytes.HexBytes(data.Hash()), + }, + }, Signatures: []cmttypes.CommitSig{ { BlockIDFlag: cmttypes.BlockIDFlagCommit, @@ -601,7 +614,7 @@ func cometCommitToABCICommitInfo(commit *cmttypes.Commit) abci.CommitInfo { votes[i] = abci.VoteInfo{ Validator: abci.Validator{ Address: sig.ValidatorAddress, - Power: 0, + Power: 1, }, BlockIdFlag: cmtprototypes.BlockIDFlag(sig.BlockIDFlag), } diff --git a/pkg/adapter/adapter_test.go b/pkg/adapter/adapter_test.go index 741e6221..1b846b0e 100644 --- a/pkg/adapter/adapter_test.go +++ b/pkg/adapter/adapter_test.go @@ -103,7 +103,7 @@ func TestExecuteFiresEvents(t *testing.T) { AppHash: []byte("apphash1"), } - headerBz, err := cometcompat.PayloadProvider()(&header) + headerBz, err := cometcompat.SignaturePayloadProvider()(&header, new(types.Data)) require.NoError(t, err) sig, err := privKey.Sign(headerBz) diff --git a/pkg/adapter/store.go b/pkg/adapter/store.go index 02736293..1aeb3d02 100644 --- a/pkg/adapter/store.go +++ b/pkg/adapter/store.go @@ -7,7 +7,9 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cmtstateproto "github.com/cometbft/cometbft/proto/tendermint/state" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmtstate "github.com/cometbft/cometbft/state" + cmttypes "github.com/cometbft/cometbft/types" proto "github.com/cosmos/gogoproto/proto" ds "github.com/ipfs/go-datastore" kt "github.com/ipfs/go-datastore/keytransform" @@ -20,6 +22,8 @@ const ( stateKey = "s" // blockResponseKey is the key used for storing block responses blockResponseKey = "br" + // commitKey is the key used for storing commits + commitKey = "c" ) // Store wraps a datastore with ABCI-specific functionality @@ -72,6 +76,42 @@ func (s *Store) SaveState(ctx context.Context, state *cmtstate.State) error { return s.prefixedStore.Put(ctx, ds.NewKey(stateKey), data) } +// SaveLastCommit saves the commit to disk per height. +// This is used to store the last commit for the block execution +func (s *Store) SaveLastCommit(ctx context.Context, height uint64, commit *cmttypes.Commit) error { + data, err := proto.Marshal(commit.ToProto()) + if err != nil { + return fmt.Errorf("failed to marshal last commit: %w", err) + } + + key := ds.NewKey(commitKey).ChildString(strconv.FormatUint(height, 10)) + return s.prefixedStore.Put(ctx, key, data) +} + +func (s *Store) GetLastCommit(ctx context.Context, height uint64) (*cmttypes.Commit, error) { + key := ds.NewKey(commitKey).ChildString(strconv.FormatUint(height, 10)) + data, err := s.prefixedStore.Get(ctx, key) + if err != nil { + return nil, fmt.Errorf("failed to get last commit: %w", err) + } + + if data == nil { + return nil, fmt.Errorf("last commit not found for height %d", height) + } + + protoCommit := &cmtproto.Commit{} + if err := proto.Unmarshal(data, protoCommit); err != nil { + return nil, fmt.Errorf("failed to unmarshal state: %w", err) + } + + commit, err := cmttypes.CommitFromProto(protoCommit) + if err != nil { + return nil, fmt.Errorf("failed to convert commit from proto: %w", err) + } + + return commit, nil +} + // SaveBlockResponse saves the block response to disk per height // This is used to store the results of the block execution // so that they can be retrieved later, e.g., for querying transaction results. diff --git a/pkg/cometcompat/convert.go b/pkg/cometcompat/convert.go index 47e22725..488c45a4 100644 --- a/pkg/cometcompat/convert.go +++ b/pkg/cometcompat/convert.go @@ -2,7 +2,6 @@ package cometcompat import ( "errors" - "fmt" cmbytes "github.com/cometbft/cometbft/libs/bytes" cmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" @@ -12,88 +11,63 @@ import ( rlktypes "github.com/rollkit/rollkit/types" ) -// ToABCIBlock converts Rolkit block into block format defined by ABCI. -func ToABCIBlock(header *rlktypes.SignedHeader, data *rlktypes.Data, lastCommit *cmttypes.Commit) (*cmttypes.Block, error) { - abciHeader, err := ToABCIHeader(&header.Header) - if err != nil { - return nil, err - } - - // validate have one validator +// ToABCIHeader converts rollkit header format defined by ABCI. +func ToABCIHeader(header *rlktypes.Header, lastCommit *cmttypes.Commit) (cmttypes.Header, error) { if len(header.ProposerAddress) == 0 { - return nil, errors.New("proposer address is not set") + return cmttypes.Header{}, errors.New("proposer address is not set") } - // set commit hash - abciHeader.LastCommitHash = lastCommit.Hash() - - // set validator hash - if header.Signer.Address != nil { - validatorHash, err := validatorHasher(header.ProposerAddress, header.Signer.PubKey) - if err != nil { - return nil, fmt.Errorf("failed to compute validator hash: %w", err) - } - abciHeader.ValidatorsHash = cmbytes.HexBytes(validatorHash) - abciHeader.NextValidatorsHash = cmbytes.HexBytes(validatorHash) - } + return cmttypes.Header{ + Version: cmprotoversion.Consensus{ + Block: cmtversion.BlockProtocol, + App: header.Version.App, + }, + Height: int64(header.Height()), //nolint:gosec + Time: header.Time(), + LastBlockID: lastCommit.BlockID, + LastCommitHash: lastCommit.Hash(), + DataHash: cmbytes.HexBytes(header.DataHash), + ConsensusHash: cmbytes.HexBytes(header.ConsensusHash), + AppHash: cmbytes.HexBytes(header.AppHash), + LastResultsHash: cmbytes.HexBytes(header.LastResultsHash), + EvidenceHash: new(cmttypes.EvidenceData).Hash(), + ProposerAddress: header.ProposerAddress, + ChainID: header.ChainID(), + ValidatorsHash: cmbytes.HexBytes(header.ValidatorHash), + NextValidatorsHash: cmbytes.HexBytes(header.ValidatorHash), + }, nil +} +// ToABCIBlock converts rollit block into block format defined by ABCI. +func ToABCIBlock(header cmttypes.Header, lastCommit *cmttypes.Commit, data *rlktypes.Data) (*cmttypes.Block, error) { abciBlock := cmttypes.Block{ - Header: abciHeader, + Header: header, Evidence: cmttypes.EvidenceData{ Evidence: nil, }, LastCommit: lastCommit, } + abciBlock.Txs = make([]cmttypes.Tx, len(data.Txs)) for i := range data.Txs { abciBlock.Txs[i] = cmttypes.Tx(data.Txs[i]) } - abciBlock.DataHash = cmbytes.HexBytes(header.DataHash) return &abciBlock, nil } -// ToABCIBlockMeta converts Rollkit block into BlockMeta format defined by ABCI -func ToABCIBlockMeta(header *rlktypes.SignedHeader, data *rlktypes.Data, lastCommit *cmttypes.Commit) (*cmttypes.BlockMeta, error) { - cmblock, err := ToABCIBlock(header, data, lastCommit) - if err != nil { - return nil, err - } - blockID := cmttypes.BlockID{Hash: cmblock.Hash()} - +// ToABCIBlockMeta converts an ABCI block into a BlockMeta format. +func ToABCIBlockMeta(abciBlock *cmttypes.Block) (*cmttypes.BlockMeta, error) { return &cmttypes.BlockMeta{ - BlockID: blockID, - BlockSize: cmblock.Size(), - Header: cmblock.Header, - NumTxs: len(cmblock.Txs), - }, nil -} - -// ToABCIHeader converts Rollkit header to Header format defined in ABCI. -func ToABCIHeader(header *rlktypes.Header) (cmttypes.Header, error) { - return cmttypes.Header{ - Version: cmprotoversion.Consensus{ - Block: cmtversion.BlockProtocol, - App: header.Version.App, - }, - Height: int64(header.Height()), //nolint:gosec - Time: header.Time(), - LastBlockID: cmttypes.BlockID{ - Hash: cmbytes.HexBytes(header.LastHeaderHash), + BlockID: cmttypes.BlockID{ + Hash: abciBlock.Hash(), PartSetHeader: cmttypes.PartSetHeader{ - Total: 0, - Hash: nil, + Total: 1, + Hash: abciBlock.Hash(), }, }, - LastCommitHash: cmbytes.HexBytes(header.LastCommitHash), - DataHash: cmbytes.HexBytes(header.DataHash), - ConsensusHash: cmbytes.HexBytes(header.ConsensusHash), - AppHash: cmbytes.HexBytes(header.AppHash), - LastResultsHash: cmbytes.HexBytes(header.LastResultsHash), - EvidenceHash: new(cmttypes.EvidenceData).Hash(), - ProposerAddress: header.ProposerAddress, - ChainID: header.ChainID(), - // validator hash and next validator hash are not set here - // they are set later (in ToABCIBlock) + BlockSize: abciBlock.Size(), + Header: abciBlock.Header, + NumTxs: len(abciBlock.Txs), }, nil } diff --git a/pkg/cometcompat/signer.go b/pkg/cometcompat/signer.go index 1f03f6a8..bcc7b09f 100644 --- a/pkg/cometcompat/signer.go +++ b/pkg/cometcompat/signer.go @@ -1,30 +1,31 @@ package cometcompat import ( + cmtbytes "github.com/cometbft/cometbft/libs/bytes" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" cmttypes "github.com/cometbft/cometbft/types" "github.com/rollkit/rollkit/types" ) -func PayloadProvider() types.SignaturePayloadProvider { - return func(header *types.Header) ([]byte, error) { - abciHeaderForSigning, err := ToABCIHeader(header) - if err != nil { - return nil, err - } +func SignaturePayloadProvider() types.SignaturePayloadProvider { + return func(header *types.Header, data *types.Data) ([]byte, error) { vote := cmtproto.Vote{ Type: cmtproto.PrecommitType, Height: int64(header.Height()), //nolint:gosec Round: 0, BlockID: cmtproto.BlockID{ - Hash: abciHeaderForSigning.Hash(), - PartSetHeader: cmtproto.PartSetHeader{}, + Hash: cmtbytes.HexBytes(data.Hash()), + PartSetHeader: cmtproto.PartSetHeader{ + Total: 1, + Hash: cmtbytes.HexBytes(data.Hash()), + }, }, Timestamp: header.Time(), ValidatorAddress: header.ProposerAddress, ValidatorIndex: 0, } + chainID := header.ChainID() consensusVoteBytes := cmttypes.VoteSignBytes(chainID, &vote) diff --git a/pkg/cometcompat/validator_hasher.go b/pkg/cometcompat/validator_hasher.go index 12dbb4c3..26d7935f 100644 --- a/pkg/cometcompat/validator_hasher.go +++ b/pkg/cometcompat/validator_hasher.go @@ -13,42 +13,44 @@ import ( rollkittypes "github.com/rollkit/rollkit/types" ) -// validatorHasher returns a function that calculates the ValidatorHash +// ValidatorHasher returns a function that calculates the ValidatorHash // compatible with CometBFT. This function is intended to be injected into Rollkit's Manager. -func validatorHasher(proposerAddress []byte, pubKey crypto.PubKey) (rollkittypes.Hash, error) { - var calculatedHash rollkittypes.Hash - - var cometBftPubKey tmcryptoed25519.PubKey - if pubKey.Type() == crypto.Ed25519 { - rawKey, err := pubKey.Raw() - if err != nil { - return calculatedHash, fmt.Errorf("failed to get raw bytes from libp2p public key: %w", err) +func ValidatorHasherProvider() func(proposerAddress []byte, pubKey crypto.PubKey) (rollkittypes.Hash, error) { + return func(proposerAddress []byte, pubKey crypto.PubKey) (rollkittypes.Hash, error) { + var calculatedHash rollkittypes.Hash + + var cometBftPubKey tmcryptoed25519.PubKey + if pubKey.Type() == crypto.Ed25519 { + rawKey, err := pubKey.Raw() + if err != nil { + return calculatedHash, fmt.Errorf("failed to get raw bytes from libp2p public key: %w", err) + } + if len(rawKey) != tmcryptoed25519.PubKeySize { + return calculatedHash, fmt.Errorf("libp2p public key size (%d) does not match CometBFT Ed25519 PubKeySize (%d)", len(rawKey), tmcryptoed25519.PubKeySize) + } + cometBftPubKey = rawKey + } else { + return calculatedHash, fmt.Errorf("unsupported public key type '%s', expected Ed25519 for CometBFT compatibility", pubKey.Type()) } - if len(rawKey) != tmcryptoed25519.PubKeySize { - return calculatedHash, fmt.Errorf("libp2p public key size (%d) does not match CometBFT Ed25519 PubKeySize (%d)", len(rawKey), tmcryptoed25519.PubKeySize) - } - cometBftPubKey = rawKey - } else { - return calculatedHash, fmt.Errorf("unsupported public key type '%s', expected Ed25519 for CometBFT compatibility", pubKey.Type()) - } - votingPower := int64(1) - sequencerValidator := tmtypes.NewValidator(cometBftPubKey, votingPower) + votingPower := int64(1) + sequencerValidator := tmtypes.NewValidator(cometBftPubKey, votingPower) - derivedAddress := sequencerValidator.Address.Bytes() - if !bytes.Equal(derivedAddress, proposerAddress) { - return calculatedHash, fmt.Errorf("CRITICAL MISMATCH - derived validator address (%s) does not match expected proposer address (%s). PubKey used for derivation: %s", - hex.EncodeToString(derivedAddress), - hex.EncodeToString(proposerAddress), - hex.EncodeToString(cometBftPubKey.Bytes())) - } + derivedAddress := sequencerValidator.Address.Bytes() + if !bytes.Equal(derivedAddress, proposerAddress) { + return calculatedHash, fmt.Errorf("CRITICAL MISMATCH - derived validator address (%s) does not match expected proposer address (%s). PubKey used for derivation: %s", + hex.EncodeToString(derivedAddress), + hex.EncodeToString(proposerAddress), + hex.EncodeToString(cometBftPubKey.Bytes())) + } - sequencerValidatorSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{sequencerValidator}) + sequencerValidatorSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{sequencerValidator}) - hashSumBytes := sequencerValidatorSet.Hash() + hashSumBytes := sequencerValidatorSet.Hash() - calculatedHash = make(rollkittypes.Hash, stdsha256.Size) - copy(calculatedHash, hashSumBytes) + calculatedHash = make(rollkittypes.Hash, stdsha256.Size) + copy(calculatedHash, hashSumBytes) - return calculatedHash, nil + return calculatedHash, nil + } } diff --git a/pkg/rpc/core/blocks.go b/pkg/rpc/core/blocks.go index c422eac9..a41b4b24 100644 --- a/pkg/rpc/core/blocks.go +++ b/pkg/rpc/core/blocks.go @@ -8,7 +8,6 @@ import ( cmbytes "github.com/cometbft/cometbft/libs/bytes" cmquery "github.com/cometbft/cometbft/libs/pubsub/query" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" ctypes "github.com/cometbft/cometbft/rpc/core/types" rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" cmttypes "github.com/cometbft/cometbft/types" @@ -73,20 +72,29 @@ func BlockSearch( return nil, err } - lastCommit, err := getLastCommit(wrappedCtx, uint64(results[i])) + lastCommit, err := env.Adapter.Store.GetLastCommit(wrappedCtx, uint64(results[i])) if err != nil { return nil, fmt.Errorf("failed to get last commit for block %d: %w", results[i], err) } - block, err := cometcompat.ToABCIBlock(header, data, lastCommit) + abciHeader, err := cometcompat.ToABCIHeader(&header.Header, lastCommit) + if err != nil { + return nil, fmt.Errorf("failed to convert header to ABCI format: %w", err) + } + + abciBlock, err := cometcompat.ToABCIBlock(abciHeader, lastCommit, data) if err != nil { return nil, err } blocks = append(blocks, &ctypes.ResultBlock{ - Block: block, + Block: abciBlock, BlockID: cmttypes.BlockID{ - Hash: block.Hash(), + Hash: abciBlock.Hash(), + PartSetHeader: cmttypes.PartSetHeader{ + Total: 1, + Hash: abciBlock.Hash(), + }, }, }) } @@ -125,54 +133,17 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) } } - header, data, err := env.Adapter.RollkitStore.GetBlockData(ctx.Context(), heightValue) - if err != nil { - return nil, err - } - - lastCommit, err := getLastCommit(ctx.Context(), heightValue) - if err != nil { - return nil, fmt.Errorf("failed to get last commit for block %d: %w", heightValue, err) - } - - // First apply ToABCIBlock to get the final header with all transformations - abciBlock, err := cometcompat.ToABCIBlock(header, data, lastCommit) - if err != nil { - return nil, err - } - - // Then re-sign the final ABCI header if we have a signer - if env.Signer != nil { - // Create a vote for the final ABCI header - vote := cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: int64(header.Height()), //nolint:gosec - Round: 0, - BlockID: cmtproto.BlockID{ - Hash: abciBlock.Header.Hash(), - PartSetHeader: cmtproto.PartSetHeader{}, - }, - Timestamp: abciBlock.Time, - ValidatorAddress: header.ProposerAddress, - ValidatorIndex: 0, - } - chainID := header.ChainID() - finalSignBytes := cmttypes.VoteSignBytes(chainID, &vote) - - newSignature, err := env.Signer.Sign(finalSignBytes) - if err != nil { - return nil, fmt.Errorf("failed to sign final ABCI header: %w", err) - } - - // Update the signature in the block - if len(abciBlock.LastCommit.Signatures) > 0 { - abciBlock.LastCommit.Signatures[0].Signature = newSignature - } + blockMeta, block := getBlockMeta(ctx.Context(), heightValue) + if blockMeta == nil { + return &ctypes.ResultBlock{ + BlockID: cmttypes.BlockID{}, + Block: block, + }, nil } return &ctypes.ResultBlock{ - BlockID: cmttypes.BlockID{Hash: abciBlock.Hash()}, - Block: abciBlock, + BlockID: blockMeta.BlockID, + Block: block, }, nil } @@ -184,12 +155,17 @@ func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error return nil, err } - lastCommit, err := getLastCommit(ctx.Context(), header.Height()) + lastCommit, err := env.Adapter.Store.GetLastCommit(ctx.Context(), header.Height()) if err != nil { return nil, fmt.Errorf("failed to get last commit for block %d: %w", header.Height(), err) } - abciBlock, err := cometcompat.ToABCIBlock(header, data, lastCommit) + abciHeader, err := cometcompat.ToABCIHeader(&header.Header, lastCommit) + if err != nil { + return nil, fmt.Errorf("failed to convert header to ABCI format: %w", err) + } + + abciBlock, err := cometcompat.ToABCIBlock(abciHeader, lastCommit, data) if err != nil { return nil, err } @@ -198,8 +174,8 @@ func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error BlockID: cmttypes.BlockID{ Hash: cmbytes.HexBytes(hash), PartSetHeader: cmttypes.PartSetHeader{ - Total: 0, - Hash: nil, + Total: 1, + Hash: cmbytes.HexBytes(hash), }, }, Block: abciBlock, @@ -215,70 +191,45 @@ func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, erro return nil, err } - header, rollkitData, err := env.Adapter.RollkitStore.GetBlockData(ctx.Context(), height) - if err != nil { - return nil, err - } - - // Create a proper commit that will be used for ToABCIBlock - abciCommit := &cmttypes.Commit{ - Height: int64(header.Height()), //nolint:gosec - Round: 0, - BlockID: cmttypes.BlockID{ - Hash: cmbytes.HexBytes(header.Hash()), // This will be updated after ToABCIBlock - PartSetHeader: cmttypes.PartSetHeader{}, - }, - Signatures: []cmttypes.CommitSig{{ - BlockIDFlag: cmttypes.BlockIDFlagCommit, - Signature: header.Signature, // This will be updated if we have a signer - ValidatorAddress: header.ProposerAddress, - Timestamp: header.Time(), - }}, + blockMeta, _ := getBlockMeta(ctx.Context(), height) + if blockMeta == nil { + return nil, nil } + abciHeader := blockMeta.Header - // First apply ToABCIBlock to get the final header with all transformations - abciBlock, err := cometcompat.ToABCIBlock(header, rollkitData, abciCommit) + currentHeight, err := env.Adapter.RollkitStore.Height(ctx.Context()) if err != nil { - return nil, err + return nil, fmt.Errorf("failed to get current height: %w", err) } - // Then re-sign the final ABCI header if we have a signer - if env.Signer != nil { - // Create a vote for the final ABCI header - vote := cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: int64(header.Height()), //nolint:gosec - Round: 0, - BlockID: cmtproto.BlockID{ - Hash: abciBlock.Header.Hash(), - PartSetHeader: cmtproto.PartSetHeader{}, - }, - Timestamp: abciBlock.Time, - ValidatorAddress: header.ProposerAddress, - ValidatorIndex: 0, + // non canonical commits do not have signatures + if height == currentHeight { + header, _, err := env.Adapter.RollkitStore.GetBlockData(ctx.Context(), uint64(currentHeight)) + if err != nil { + return nil, err } - chainID := header.ChainID() - finalSignBytes := cmttypes.VoteSignBytes(chainID, &vote) - newSignature, err := env.Signer.Sign(finalSignBytes) - if err != nil { - return nil, fmt.Errorf("failed to sign final ABCI header: %w", err) + commit := &cmttypes.Commit{ + Height: int64(currentHeight), //nolint:gosec + Round: 0, + BlockID: blockMeta.BlockID, + Signatures: []cmttypes.CommitSig{{ + BlockIDFlag: cmttypes.BlockIDFlagCommit, + Signature: header.Signature, + ValidatorAddress: header.ProposerAddress, + Timestamp: header.Time(), + }}, } - // Update the commit with the new signature - abciBlock.LastCommit.Signatures[0].Signature = newSignature + return ctypes.NewResultCommit(&abciHeader, commit, false), nil } - // Update the commit's BlockID to match the final ABCI block hash - abciBlock.LastCommit.BlockID.Hash = abciBlock.Header.Hash() + commit, err := env.Adapter.Store.GetLastCommit(ctx.Context(), height+1) + if err != nil { + return nil, fmt.Errorf("failed to get last commit for height %d: %w", height, err) + } - return &ctypes.ResultCommit{ - SignedHeader: cmttypes.SignedHeader{ - Header: &abciBlock.Header, - Commit: abciBlock.LastCommit, - }, - CanonicalCommit: true, - }, nil + return ctypes.NewResultCommit(&abciHeader, commit, true), nil } // BlockResults gets block results at a given height. @@ -313,10 +264,11 @@ func Header(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultHeader, erro return nil, err } - blockMeta := getBlockMeta(ctx.Context(), height) + blockMeta, _ := getBlockMeta(ctx.Context(), height) if blockMeta == nil { return nil, fmt.Errorf("block at height %d not found", height) } + return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil } @@ -327,26 +279,22 @@ func HeaderByHash(ctx *rpctypes.Context, hash cmbytes.HexBytes) (*ctypes.ResultH // decoding logic in the HTTP service will correctly translate from JSON. // See https://github.com/cometbft/cometbft/issues/6802 for context. - header, data, err := env.Adapter.RollkitStore.GetBlockByHash(ctx.Context(), rlktypes.Hash(hash)) + header, _, err := env.Adapter.RollkitStore.GetBlockByHash(ctx.Context(), rlktypes.Hash(hash)) if err != nil { return nil, err } - lastCommit, err := getLastCommit(ctx.Context(), header.Height()) + lastCommit, err := env.Adapter.Store.GetLastCommit(ctx.Context(), header.Height()) if err != nil { return nil, fmt.Errorf("failed to get last commit for block %d: %w", header.Height(), err) } - blockMeta, err := cometcompat.ToABCIBlockMeta(header, data, lastCommit) + abciHeader, err := cometcompat.ToABCIHeader(&header.Header, lastCommit) if err != nil { - return nil, err - } - - if blockMeta == nil { - return &ctypes.ResultHeader{}, nil + return nil, fmt.Errorf("failed to convert header to ABCI format: %w", err) } - return &ctypes.ResultHeader{Header: &blockMeta.Header}, nil + return &ctypes.ResultHeader{Header: &abciHeader}, nil } // BlockchainInfo gets block headers for minHeight <= height <= maxHeight. @@ -360,7 +308,7 @@ func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes. return nil, err } - // Currently blocks are not pruned and are synced linearly so the base height is 0 + // Currently blocks are not pruned and are synced linearly so the base height is 0. minHeight, maxHeight, err = filterMinMax( 0, int64(height), //nolint:gosec @@ -372,24 +320,14 @@ func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes. } env.Logger.Debug("BlockchainInfo", "maxHeight", maxHeight, "minHeight", minHeight) - blocks := make([]*cmttypes.BlockMeta, 0, maxHeight-minHeight+1) - for _, block := range BlockIterator(ctx.Context(), maxHeight, minHeight) { - if block.header != nil && block.data != nil { - lastCommit, err := getLastCommit(ctx.Context(), block.header.Height()) - if err != nil { - return nil, fmt.Errorf("failed to get last commit for block %d: %w", block.header.Height(), err) - } - - cmblockmeta, err := cometcompat.ToABCIBlockMeta(block.header, block.data, lastCommit) - if err != nil { - return nil, err - } - blocks = append(blocks, cmblockmeta) - } + blockMetas := []*cmttypes.BlockMeta{} + for height := maxHeight; height >= minHeight; height-- { + blockMeta, _ := getBlockMeta(ctx.Context(), uint64(height)) + blockMetas = append(blockMetas, blockMeta) } return &ctypes.ResultBlockchainInfo{ LastHeight: int64(height), //nolint:gosec - BlockMetas: blocks, + BlockMetas: blockMetas, }, nil } diff --git a/pkg/rpc/core/blocks_test.go b/pkg/rpc/core/blocks_test.go index ff99d047..1ce98f14 100644 --- a/pkg/rpc/core/blocks_test.go +++ b/pkg/rpc/core/blocks_test.go @@ -1,6 +1,7 @@ package core import ( + "context" "testing" "time" @@ -8,12 +9,11 @@ import ( cmtlog "github.com/cometbft/cometbft/libs/log" "github.com/cometbft/cometbft/libs/math" "github.com/cometbft/cometbft/light" - cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" ctypes "github.com/cometbft/cometbft/rpc/core/types" rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" cmttypes "github.com/cometbft/cometbft/types" + ds "github.com/ipfs/go-datastore" "github.com/libp2p/go-libp2p/core/crypto" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -31,10 +31,15 @@ func TestBlockSearch_Success(t *testing.T) { mockApp := new(MockApp) mockBlockIndexer := new(MockBlockIndexer) + // Create a real adapter store for the test + dsStore := ds.NewMapDatastore() + adapterStore := adapter.NewExecABCIStore(dsStore) + env = &Environment{ Adapter: &adapter.Adapter{ RollkitStore: mockRollkitStore, App: mockApp, + Store: adapterStore, }, TxIndexer: mockTxIndexer, BlockIndexer: mockBlockIndexer, @@ -53,29 +58,6 @@ func TestBlockSearch_Success(t *testing.T) { now := time.Now() chainID := "test-chain" - // Block 1 (needed for getLastCommit for block 2) - header0 := &types.SignedHeader{ - Header: types.Header{ - BaseHeader: types.BaseHeader{ - Height: 1, - Time: uint64(now.UnixNano()), - ChainID: chainID, - }, - ProposerAddress: []byte("proposer0"), - AppHash: []byte("apphash0"), - DataHash: make([]byte, 32), - }, - Signature: types.Signature(make([]byte, 64)), - } - data0 := &types.Data{ - Metadata: &types.Metadata{ - ChainID: chainID, - Height: 1, - Time: uint64(now.UnixNano()), - }, - Txs: make(types.Txs, 0), - } - header2 := &types.SignedHeader{ Header: types.Header{ BaseHeader: types.BaseHeader{ @@ -83,7 +65,7 @@ func TestBlockSearch_Success(t *testing.T) { Time: uint64(now.UnixNano() + int64(time.Second)), ChainID: chainID, }, - ProposerAddress: []byte("proposer2"), + ProposerAddress: make([]byte, 20), AppHash: []byte("apphash2"), DataHash: make([]byte, 32), }, @@ -105,7 +87,7 @@ func TestBlockSearch_Success(t *testing.T) { Time: uint64(now.UnixNano() + 2*int64(time.Second)), ChainID: chainID, }, - ProposerAddress: []byte("proposer3"), + ProposerAddress: make([]byte, 20), AppHash: []byte("apphash3"), DataHash: make([]byte, 32), }, @@ -120,29 +102,66 @@ func TestBlockSearch_Success(t *testing.T) { Txs: make(types.Txs, 0), } - // Mock GetBlockData calls (including the ones needed for getLastCommit) - mockRollkitStore.On("GetBlockData", mock.Anything, uint64(1)).Return(header0, data0, nil) // For getLastCommit of block 2 - mockRollkitStore.On("GetBlockData", mock.Anything, uint64(2)).Return(header2, data2, nil) // For block 2 and getLastCommit of block 3 + // Mock GetBlockData calls + mockRollkitStore.On("GetBlockData", mock.Anything, uint64(2)).Return(header2, data2, nil) // For block 2 mockRollkitStore.On("GetBlockData", mock.Anything, uint64(3)).Return(header3, data3, nil) // For block 3 + saveCtx := context.Background() + + // Create commit for block 2 (using block 1 data) + commit2 := &cmttypes.Commit{ + Height: 2, + BlockID: cmttypes.BlockID{ + Hash: []byte(header2.Hash()), + }, + Signatures: []cmttypes.CommitSig{ + { + BlockIDFlag: cmttypes.BlockIDFlagCommit, + ValidatorAddress: header2.ProposerAddress, + Timestamp: header2.Time(), + Signature: make([]byte, 64), + }, + }, + } + err := adapterStore.SaveLastCommit(saveCtx, 2, commit2) + require.NoError(t, err) + + // Create commit for block 3 (using block 2 data) + commit3 := &cmttypes.Commit{ + Height: 3, + BlockID: cmttypes.BlockID{ + Hash: []byte(header3.Hash()), + }, + Signatures: []cmttypes.CommitSig{ + { + BlockIDFlag: cmttypes.BlockIDFlagCommit, + ValidatorAddress: header3.ProposerAddress, + Timestamp: header3.Time(), + Signature: make([]byte, 64), + }, + }, + } + err = adapterStore.SaveLastCommit(saveCtx, 3, commit3) + require.NoError(t, err) + // Execute the test result, err := BlockSearch(ctx, query, &page, &perPage, orderBy) // Verify results require.NoError(t, err) require.NotNil(t, result) - assert.Len(t, result.Blocks, 2) - assert.Equal(t, 2, result.TotalCount) + require.Len(t, result.Blocks, 2) + require.Equal(t, 2, result.TotalCount) // Verify first block (height 2) - assert.Equal(t, header2.BaseHeader.Height, uint64(result.Blocks[0].Block.Height)) - assert.Equal(t, []byte(header2.AppHash), []byte(result.Blocks[0].Block.AppHash)) - assert.Equal(t, []byte(header2.ProposerAddress), []byte(result.Blocks[0].Block.ProposerAddress)) + require.Equal(t, header2.BaseHeader.Height, uint64(result.Blocks[0].Block.Height)) + require.Equal(t, []byte(header2.AppHash), []byte(result.Blocks[0].Block.AppHash)) + require.Equal(t, []byte(header2.ProposerAddress), []byte(result.Blocks[0].Block.ProposerAddress)) // Verify second block (height 3) - assert.Equal(t, header3.BaseHeader.Height, uint64(result.Blocks[1].Block.Height)) - assert.Equal(t, []byte(header3.AppHash), []byte(result.Blocks[1].Block.AppHash)) - assert.Equal(t, []byte(header3.ProposerAddress), []byte(result.Blocks[1].Block.ProposerAddress)) + require.Equal(t, header3.BaseHeader.Height, uint64(result.Blocks[1].Block.Height)) + require.Equal(t, []byte(header3.AppHash), []byte(result.Blocks[1].Block.AppHash)) + require.Equal(t, []byte(header3.ProposerAddress), []byte(result.Blocks[1].Block.ProposerAddress)) // Verify all mocks were called as expected mockTxIndexer.AssertExpectations(t) @@ -176,6 +195,10 @@ func TestCommit_VerifyCometBFTLightClientCompatibility_MultipleBlocks(t *testing chainID := "test-chain" now := time.Now() + // use the validator hasher helper from cometcompat + validatorHash, err := cometcompat.ValidatorHasherProvider()(validatorAddress, aggregatorPubKey) + require.NoError(err) + fixedValSet := &cmttypes.ValidatorSet{ Validators: []*cmttypes.Validator{cmttypes.NewValidator(cmtPubKey, 1)}, Proposer: cmttypes.NewValidator(cmtPubKey, 1), @@ -189,8 +212,10 @@ func TestCommit_VerifyCometBFTLightClientCompatibility_MultipleBlocks(t *testing blockHeight := uint64(i) // Create and sign block - blockData, rollkitHeader := createTestBlock(blockHeight, chainID, now, validatorAddress, i) - realSignature := signBlock(t, rollkitHeader, blockData, aggregatorPrivKey, chainID, validatorAddress) + blockData, rollkitHeader := createTestBlock(blockHeight, chainID, now, validatorAddress, validatorHash, i) + + // Create the signature for the rollkit block + realSignature := signBlock(t, rollkitHeader, blockData, aggregatorPrivKey) // Mock the store to return our signed block mockBlock(blockHeight, rollkitHeader, blockData, realSignature, aggregatorPubKey, validatorAddress) @@ -207,13 +232,14 @@ func TestCommit_VerifyCometBFTLightClientCompatibility_MultipleBlocks(t *testing verifyFirstBlock(t, fixedValSet, chainID, trustedHeader) isFirstBlock = false } else { - verifySubsequentBlock(t, fixedValSet, &trustedHeader, &commitResult.SignedHeader, rollkitHeader, realSignature) + t.Logf("Verifying block %d against trusted header %d", blockHeight, trustedHeader.Height) + verifySubsequentBlock(t, fixedValSet, &trustedHeader, &commitResult.SignedHeader, rollkitHeader) trustedHeader = commitResult.SignedHeader } } } -func createTestBlock(height uint64, chainID string, baseTime time.Time, validatorAddress []byte, offset int) (*types.Data, types.Header) { +func createTestBlock(height uint64, chainID string, baseTime time.Time, validatorAddress []byte, validatorHash []byte, offset int) (*types.Data, types.Header) { blockTime := uint64(baseTime.UnixNano() + int64(offset-1)*int64(time.Second)) blockData := &types.Data{ @@ -235,40 +261,16 @@ func createTestBlock(height uint64, chainID string, baseTime time.Time, validato DataHash: blockData.DACommitment(), AppHash: make([]byte, 32), ProposerAddress: validatorAddress, + ValidatorHash: validatorHash, } return blockData, rollkitHeader } -func signBlock(t *testing.T, header types.Header, data *types.Data, privKey crypto.PrivKey, chainID string, validatorAddress []byte) []byte { - tempCommit := &cmttypes.Commit{ - Height: int64(header.Height() - 1), - BlockID: cmttypes.BlockID{Hash: make([]byte, 32)}, - Signatures: []cmttypes.CommitSig{{ - BlockIDFlag: cmttypes.BlockIDFlagCommit, - ValidatorAddress: validatorAddress, - Timestamp: time.Now(), - Signature: make([]byte, 64), - }}, - } - - tempSignedHeader := &types.SignedHeader{ - Header: header, - Signature: types.Signature(make([]byte, 64)), - } - - abciBlock, err := cometcompat.ToABCIBlock(tempSignedHeader, data, tempCommit) +func signBlock(t *testing.T, header types.Header, data *types.Data, privKey crypto.PrivKey) []byte { + signBytes, err := cometcompat.SignaturePayloadProvider()(&header, data) require.NoError(t, err) - vote := cmtproto.Vote{ - Type: cmtproto.PrecommitType, - Height: int64(header.Height()), - BlockID: cmtproto.BlockID{Hash: abciBlock.Hash()}, - Timestamp: abciBlock.Time, - ValidatorAddress: validatorAddress, - } - - signBytes := cmttypes.VoteSignBytes(chainID, &vote) signature, err := privKey.Sign(signBytes) require.NoError(t, err) @@ -285,6 +287,8 @@ func mockBlock(height uint64, header types.Header, data *types.Data, signature [ }, } + env.Adapter.RollkitStore.(*rollkitmocks.MockStore).On("Height", mock.Anything).Return(header.Height(), nil).Once() + env.Adapter.RollkitStore.(*rollkitmocks.MockStore).On("GetBlockData", mock.Anything, height).Return(signedHeader, data, nil).Once() } @@ -297,31 +301,38 @@ func callCommitRPC(t *testing.T, height uint64) *ctypes.ResultCommit { } func verifyCommitResultBasic(t *testing.T, result *ctypes.ResultCommit, height uint64, header types.Header) { - assert := assert.New(t) + require := require.New(t) - assert.Equal(int64(height), result.Height) - assert.EqualValues(header.AppHash, result.AppHash.Bytes()) - assert.NotEqual(make([]byte, 64), result.Commit.Signatures[0].Signature, "Signature should not be zeros") + require.Equal(int64(height), result.Height) + require.EqualValues(header.AppHash, result.AppHash.Bytes()) + require.NotEqual(make([]byte, 64), result.Commit.Signatures[0].Signature, "Signature should not be zeros") } func verifyFirstBlock(t *testing.T, valSet *cmttypes.ValidatorSet, chainID string, header cmttypes.SignedHeader) { err := valSet.VerifyCommitLight(chainID, header.Commit.BlockID, header.Height, header.Commit) if err != nil { - // If basic verification fails, at least verify signature is not empty - assert.NotEqual(t, make([]byte, 64), header.Commit.Signatures[0].Signature, "First block signature should not be zeros") + t.Logf("First block verification failed: %v, doing basic checks", err) + require.NotEqual(t, make([]byte, 64), header.Commit.Signatures[0].Signature, "First block signature should not be zeros") } } -func verifySubsequentBlock(t *testing.T, valSet *cmttypes.ValidatorSet, trustedHeader, newHeader *cmttypes.SignedHeader, rollkitHeader types.Header, realSignature []byte) { +func verifySubsequentBlock(t *testing.T, valSet *cmttypes.ValidatorSet, trustedHeader, newHeader *cmttypes.SignedHeader, rollkitHeader types.Header) { require := require.New(t) trustingPeriod := 3 * time.Hour trustLevel := math.Fraction{Numerator: 1, Denominator: 1} maxClockDrift := 10 * time.Second - err := light.Verify(trustedHeader, valSet, newHeader, valSet, - trustingPeriod, time.Unix(0, int64(rollkitHeader.BaseHeader.Time)), maxClockDrift, trustLevel) - + err := light.Verify( + trustedHeader, + valSet, + newHeader, + valSet, + trustingPeriod, + time.Unix(0, int64(rollkitHeader.BaseHeader.Time)), + maxClockDrift, + trustLevel, + ) require.NoError(err, "Light client verification must pass") } @@ -355,10 +366,15 @@ func setupTestEnvironmentWithSigner(signer *MockSigner) *Environment { mockApp := new(MockApp) mockRollkitStore := new(rollkitmocks.MockStore) + // Create a real adapter store for the test + dsStore := ds.NewMapDatastore() + adapterStore := adapter.NewExecABCIStore(dsStore) + return &Environment{ Adapter: &adapter.Adapter{ RollkitStore: mockRollkitStore, App: mockApp, + Store: adapterStore, }, TxIndexer: mockTxIndexer, BlockIndexer: mockBlockIndexer, diff --git a/pkg/rpc/core/consensus.go b/pkg/rpc/core/consensus.go index a6750d6d..785380ec 100644 --- a/pkg/rpc/core/consensus.go +++ b/pkg/rpc/core/consensus.go @@ -22,17 +22,17 @@ func Validators(ctx *rpctypes.Context, heightPtr *int64, pagePtr, perPagePtr *in } genesisValidators := env.Adapter.AppGenesis.Consensus.Validators - if len(genesisValidators) != 1 { return nil, fmt.Errorf("there should be exactly one validator in genesis") } + // Since it's a centralized sequencer // changed behavior to get this from genesis genesisValidator := genesisValidators[0] validator := cmttypes.Validator{ Address: genesisValidator.Address, PubKey: genesisValidator.PubKey, - VotingPower: int64(1), + VotingPower: genesisValidator.Power, ProposerPriority: int64(1), } diff --git a/pkg/rpc/core/mocks_test.go b/pkg/rpc/core/mocks_test.go index 92def2a8..dd3f6b2b 100644 --- a/pkg/rpc/core/mocks_test.go +++ b/pkg/rpc/core/mocks_test.go @@ -6,6 +6,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" cmtlog "github.com/cometbft/cometbft/libs/log" cmquery "github.com/cometbft/cometbft/libs/pubsub/query" + cmtstate "github.com/cometbft/cometbft/state" "github.com/cometbft/cometbft/state/indexer" "github.com/cometbft/cometbft/state/txindex" cmttypes "github.com/cometbft/cometbft/types" @@ -279,3 +280,47 @@ func (m *MockP2PClient) Addrs() []ma.Multiaddr { func (m *MockP2PClient) Peers() []rlkp2p.PeerConnection { return []rlkp2p.PeerConnection{} // Stub implementation } + +// MockStore is a mock for adapter.Store +type MockStore struct { + mock.Mock +} + +func (m *MockStore) GetLastCommit(ctx context.Context, height uint64) (*cmttypes.Commit, error) { + args := m.Called(ctx, height) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*cmttypes.Commit), args.Error(1) +} + +func (m *MockStore) LoadState(ctx context.Context) (*cmtstate.State, error) { + args := m.Called(ctx) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*cmtstate.State), args.Error(1) +} + +func (m *MockStore) SaveState(ctx context.Context, state *cmtstate.State) error { + args := m.Called(ctx, state) + return args.Error(0) +} + +func (m *MockStore) SaveFinalizeBlockResponse(ctx context.Context, height uint64, response *abci.ResponseFinalizeBlock) error { + args := m.Called(ctx, height, response) + return args.Error(0) +} + +func (m *MockStore) GetFinalizeBlockResponse(ctx context.Context, height uint64) (*abci.ResponseFinalizeBlock, error) { + args := m.Called(ctx, height) + if args.Get(0) == nil { + return nil, args.Error(1) + } + return args.Get(0).(*abci.ResponseFinalizeBlock), args.Error(1) +} + +func (m *MockStore) SaveLastCommit(ctx context.Context, height uint64, commit *cmttypes.Commit) error { + args := m.Called(ctx, height, commit) + return args.Error(0) +} diff --git a/pkg/rpc/core/utils.go b/pkg/rpc/core/utils.go index 88821cca..7c82820f 100644 --- a/pkg/rpc/core/utils.go +++ b/pkg/rpc/core/utils.go @@ -5,14 +5,8 @@ import ( "encoding/hex" "errors" "fmt" - "sort" - "github.com/cometbft/cometbft/libs/bytes" cmttypes "github.com/cometbft/cometbft/types" - ds "github.com/ipfs/go-datastore" - dsq "github.com/ipfs/go-datastore/query" - - rlktypes "github.com/rollkit/rollkit/types" "github.com/rollkit/go-execution-abci/pkg/cometcompat" ) @@ -39,66 +33,43 @@ func normalizeHeight(ctx context.Context, height *int64) (uint64, error) { return heightValue, nil } -func getLastCommit(ctx context.Context, blockHeight uint64) (*cmttypes.Commit, error) { - if blockHeight > 1 { - header, data, err := env.Adapter.RollkitStore.GetBlockData(ctx, blockHeight-1) - if err != nil { - return nil, fmt.Errorf("failed to get previous block data: %w", err) - } - - commitForPrevBlock := &cmttypes.Commit{ - Height: int64(header.Height()), - Round: 0, - BlockID: cmttypes.BlockID{Hash: bytes.HexBytes(header.Hash()), PartSetHeader: cmttypes.PartSetHeader{Total: 1, Hash: bytes.HexBytes(data.Hash())}}, - Signatures: []cmttypes.CommitSig{ - { - BlockIDFlag: cmttypes.BlockIDFlagCommit, - ValidatorAddress: cmttypes.Address(header.ProposerAddress), - Timestamp: header.Time(), - Signature: header.Signature, - }, - }, - } - - return commitForPrevBlock, nil - } - - return &cmttypes.Commit{ - Height: int64(blockHeight), - Round: 0, - BlockID: cmttypes.BlockID{}, - Signatures: []cmttypes.CommitSig{}, - }, nil -} - -func getBlockMeta(ctx context.Context, n uint64) *cmttypes.BlockMeta { +func getBlockMeta(ctx context.Context, n uint64) (*cmttypes.BlockMeta, *cmttypes.Block) { header, data, err := env.Adapter.RollkitStore.GetBlockData(ctx, n) if err != nil { env.Logger.Error("Failed to get block data in getBlockMeta", "height", n, "err", err) - return nil + return nil, nil } if header == nil || data == nil { env.Logger.Error("Nil header or data returned from GetBlockData", "height", n) - return nil + return nil, nil + } + + lastCommit, err := env.Adapter.Store.GetLastCommit(ctx, header.Height()) + if err != nil { + env.Logger.Error("Failed to get last commit in getBlockMeta", "height", n, "err", err) + return nil, nil + } + + abciHeader, err := cometcompat.ToABCIHeader(&header.Header, lastCommit) + if err != nil { + env.Logger.Error("Failed to convert header to ABCI format", "height", n, "err", err) + return nil, nil } - // Create empty commit for ToABCIBlockMeta call - emptyCommit := &cmttypes.Commit{ - Height: int64(header.Height()), - Round: 0, - BlockID: cmttypes.BlockID{}, - Signatures: []cmttypes.CommitSig{}, + abciBlock, err := cometcompat.ToABCIBlock(abciHeader, lastCommit, data) + if err != nil { + env.Logger.Error("Failed to convert block to ABCI format", "height", n, "err", err) + return nil, nil } - // Assuming ToABCIBlockMeta is now in pkg/rpc/provider/provider_utils.go - bmeta, err := cometcompat.ToABCIBlockMeta(header, data, emptyCommit) // Removed rpc. prefix + abciBlockMeta, err := cometcompat.ToABCIBlockMeta(abciBlock) if err != nil { env.Logger.Error("Failed to convert block to ABCI block meta", "height", n, "err", err) - return nil + return nil, nil } - return bmeta + return abciBlockMeta, abciBlock } func filterMinMax(base, height, mini, maxi, limit int64) (int64, int64, error) { @@ -144,128 +115,3 @@ func TruncateNodeID(idStr string) (string, error) { } return hex.EncodeToString(idBytes[:NodeIDByteLength]), nil } - -func getHeightFromEntry(field string, value []byte) (uint64, error) { - switch field { - case "data": - data := new(rlktypes.Data) - if err := data.UnmarshalBinary(value); err != nil { - return 0, err - } - return data.Height(), nil - case "header": - header := new(rlktypes.SignedHeader) - if err := header.UnmarshalBinary(value); err != nil { - return 0, err - } - return header.Height(), nil - } - return 0, fmt.Errorf("unknown field: %s", field) -} - -type blockFilter struct { // needs this for the Filter interface - max int64 - min int64 - field string // need this field for differentiation between getting headers and getting data -} - -func (f *blockFilter) Filter(e dsq.Entry) bool { - height, err := getHeightFromEntry(f.field, e.Value) - if err != nil { - return false - } - return height >= uint64(f.min) && height <= uint64(f.max) -} - -// BlockIterator returns a slice of BlockResponse objects containing paired headers and data -// for blocks within the specified height range. It efficiently retrieves blocks by performing -// only two datastore queries (one for headers, one for data) rather than querying each block -// individually. -// Returns a slice of BlockResponse objects sorted by height in descending order. -func BlockIterator(ctx context.Context, max int64, min int64) []BlockResponse { - var blocks []BlockResponse - ds, ok := env.Adapter.RollkitStore.(ds.Batching) - if !ok { - return blocks - } - filterData := &blockFilter{max: max, min: min, field: "data"} - filterHeader := &blockFilter{max: max, min: min, field: "header"} - - // we need to do two queries, one for the block header and one for the block data - qHeader := dsq.Query{ - Prefix: "h", - } - qHeader.Filters = append(qHeader.Filters, filterHeader) - - qData := dsq.Query{ - Prefix: "d", - } - qData.Filters = append(qData.Filters, filterData) - - rHeader, err := ds.Query(ctx, qHeader) - if err != nil { - return blocks - } - rData, err := ds.Query(ctx, qData) - if err != nil { - return blocks - } - defer rHeader.Close() //nolint:errcheck - defer rData.Close() //nolint:errcheck - - // we need to match the data to the header using the height, for that we use a map - headerMap := make(map[uint64]*rlktypes.SignedHeader) - for res := range rHeader.Next() { - if res.Error != nil { - continue - } - header := new(rlktypes.SignedHeader) - if err := header.UnmarshalBinary(res.Value); err != nil { - continue - } - headerMap[header.Height()] = header - } - - dataMap := make(map[uint64]*rlktypes.Data) - for res := range rData.Next() { - if res.Error != nil { - continue - } - data := new(rlktypes.Data) - if err := data.UnmarshalBinary(res.Value); err != nil { - continue - } - dataMap[data.Height()] = data - } - - // maps the headers to the data - for height, header := range headerMap { - if data, ok := dataMap[height]; ok { - blocks = append(blocks, BlockResponse{header: header, data: data}) - } - } - - // sort blocks by height descending - sort.Slice(blocks, func(i, j int) bool { - return blocks[i].header.Height() > blocks[j].header.Height() - }) - - return blocks -} - -// BlockResponse represents a paired block header and data for efficient iteration. -// It's returned by BlockIterator to provide access to both components of a block. -type BlockResponse struct { - header *rlktypes.SignedHeader - data *rlktypes.Data -} - -// returns the block's signed header. -func (br BlockResponse) Header() *rlktypes.SignedHeader { - return br.header -} - -// returns the block's data. -func (br BlockResponse) Data() *rlktypes.Data { - return br.data -} diff --git a/server/migration_cmd.go b/server/migration_cmd.go index d13fbe4e..eab0a6f0 100644 --- a/server/migration_cmd.go +++ b/server/migration_cmd.go @@ -226,7 +226,7 @@ func cometBlockToRollkit(block *cmttypes.Block) (*rollkittypes.SignedHeader, *ro Block: block.Version.Block, App: block.Version.App, }, - LastHeaderHash: block.LastBlockID.Hash.Bytes(), + LastHeaderHash: block.Header.Hash().Bytes(), LastCommitHash: block.LastCommitHash.Bytes(), DataHash: block.DataHash.Bytes(), ConsensusHash: block.ConsensusHash.Bytes(), @@ -235,7 +235,7 @@ func cometBlockToRollkit(block *cmttypes.Block) (*rollkittypes.SignedHeader, *ro ValidatorHash: block.ValidatorsHash.Bytes(), ProposerAddress: block.ProposerAddress.Bytes(), }, - Signature: signature, // TODO: figure out this. + Signature: signature, } data = &rollkittypes.Data{ diff --git a/server/migration_cmd_test.go b/server/migration_cmd_test.go index fa131e22..5d7ecd4d 100644 --- a/server/migration_cmd_test.go +++ b/server/migration_cmd_test.go @@ -85,7 +85,7 @@ func TestCometBlockToRollkit(t *testing.T) { require.Equal(t, chainID, header.ChainID()) require.Equal(t, uint64(11), header.Version.Block) require.Equal(t, uint64(1), header.Version.App) - require.Equal(t, []byte("lastblockhash"), []byte(header.LastHeaderHash)) + require.Equal(t, block.Header.Hash().Bytes(), []byte(header.LastHeaderHash)) require.Equal(t, []byte("lastcommithash"), []byte(header.LastCommitHash)) require.Equal(t, []byte("datahash"), []byte(header.DataHash)) require.Equal(t, []byte("consensushash"), []byte(header.ConsensusHash)) diff --git a/server/start.go b/server/start.go index 719a7a82..a27d4656 100644 --- a/server/start.go +++ b/server/start.go @@ -36,6 +36,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + rollkitblock "github.com/rollkit/rollkit/block" "github.com/rollkit/rollkit/da/jsonrpc" "github.com/rollkit/rollkit/node" "github.com/rollkit/rollkit/pkg/config" @@ -472,7 +473,12 @@ func setupNodeAndExecutor( database, metrics, NewLogAdapter(logger), - cometcompat.PayloadProvider(), + node.NodeOptions{ + ManagerOptions: rollkitblock.ManagerOptions{ + SignaturePayloadProvider: cometcompat.SignaturePayloadProvider(), + ValidatorHasherProvider: cometcompat.ValidatorHasherProvider(), + }, + }, ) if err != nil { return nil, nil, cleanupFn, err