diff --git a/go.mod b/go.mod index 85f78bf..c3dbbde 100644 --- a/go.mod +++ b/go.mod @@ -27,10 +27,10 @@ require ( github.com/libp2p/go-libp2p-pubsub v0.14.0 github.com/multiformats/go-multiaddr v0.15.0 github.com/prometheus/client_golang v1.22.0 - github.com/rollkit/rollkit v0.14.2-0.20250602221640-d561a3a1706d - github.com/rollkit/rollkit/core v0.0.0-20250602221640-d561a3a1706d - github.com/rollkit/rollkit/da v0.0.0-20250602221640-d561a3a1706d - github.com/rollkit/rollkit/sequencers/single v0.0.0-20250602221640-d561a3a1706d + github.com/rollkit/rollkit v0.14.2-0.20250603093058-6a441b7b3018 + github.com/rollkit/rollkit/core v0.0.0-20250529164851-d4b4a1e88558 + github.com/rollkit/rollkit/da v0.0.0-20250526094218-4a5686055970 + github.com/rollkit/rollkit/sequencers/single v0.0.0-20250526094218-4a5686055970 github.com/rs/cors v1.11.1 github.com/spf13/cobra v1.9.1 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 4e16d03..fde1167 100644 --- a/go.sum +++ b/go.sum @@ -1004,14 +1004,14 @@ 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 v0.14.2-0.20250602221640-d561a3a1706d h1:URLaPLUMtrm1UEq6yXH6gcUyr84MmprZVQjm0JOpHys= -github.com/rollkit/rollkit v0.14.2-0.20250602221640-d561a3a1706d/go.mod h1:gq4o6SyzrLKry9DwjtwM90d1LsFW7JpyxvnfNTTp1Cw= -github.com/rollkit/rollkit/core v0.0.0-20250602221640-d561a3a1706d h1:12LFAx7Uzcqam2tOhnW46qRnexio1qpHWqNQfeKnge8= -github.com/rollkit/rollkit/core v0.0.0-20250602221640-d561a3a1706d/go.mod h1:0RhbqC8Is970KRhr6zPUQOZkmKt6/WqPRDQWfd2P7P0= -github.com/rollkit/rollkit/da v0.0.0-20250602221640-d561a3a1706d h1:dGoDZ/h3mNTjZKRc7UDfqk6FT6SOevkTjMOdFC9kTPs= -github.com/rollkit/rollkit/da v0.0.0-20250602221640-d561a3a1706d/go.mod h1:MqbHTMhjb1PGbGaZ7bVddCm5OJg1+GMVEPpERdsO058= -github.com/rollkit/rollkit/sequencers/single v0.0.0-20250602221640-d561a3a1706d h1:Aus59RwnvK9INKi7wZEJbTrzv5N5xpHwq5zvJPBbE5k= -github.com/rollkit/rollkit/sequencers/single v0.0.0-20250602221640-d561a3a1706d/go.mod h1:kmCVN/v3/esPZW/ImaZq6mF+I2UsqjUPh7bODFDUOag= +github.com/rollkit/rollkit v0.14.2-0.20250603093058-6a441b7b3018 h1:RjotQTsOsy2nah1IkMUmZyfNE3b6NlJ+hiXSVpVPJS8= +github.com/rollkit/rollkit v0.14.2-0.20250603093058-6a441b7b3018/go.mod h1:Gw1i+hGmPp9Y7b05SXlqGj+tnXhzaiW/xs4wboXsEL0= +github.com/rollkit/rollkit/core v0.0.0-20250529164851-d4b4a1e88558 h1:0jWzjfP/DQSdsRYnIMPNAD7itd8Oq7roV3cu9MddTEA= +github.com/rollkit/rollkit/core v0.0.0-20250529164851-d4b4a1e88558/go.mod h1:0RhbqC8Is970KRhr6zPUQOZkmKt6/WqPRDQWfd2P7P0= +github.com/rollkit/rollkit/da v0.0.0-20250526094218-4a5686055970 h1:PnnQLIkI119NALSqI91peceGsqnBVyBBeyunk6auTpM= +github.com/rollkit/rollkit/da v0.0.0-20250526094218-4a5686055970/go.mod h1:MqbHTMhjb1PGbGaZ7bVddCm5OJg1+GMVEPpERdsO058= +github.com/rollkit/rollkit/sequencers/single v0.0.0-20250526094218-4a5686055970 h1:vQVPtGxKquSxE74CtA/kv2OmtyIDKivKYT8AcRjFNak= +github.com/rollkit/rollkit/sequencers/single v0.0.0-20250526094218-4a5686055970/go.mod h1:KVjWXmDUlMWSg/n+6QhJMueM9nigrDpydyzyAYky/A4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= diff --git a/pkg/adapter/adapter.go b/pkg/adapter/adapter.go index bffbe77..5f19b97 100644 --- a/pkg/adapter/adapter.go +++ b/pkg/adapter/adapter.go @@ -9,8 +9,10 @@ import ( "cosmossdk.io/log" abci "github.com/cometbft/cometbft/abci/types" "github.com/cometbft/cometbft/config" + "github.com/cometbft/cometbft/libs/bytes" "github.com/cometbft/cometbft/mempool" corep2p "github.com/cometbft/cometbft/p2p" + types1 "github.com/cometbft/cometbft/proto/tendermint/types" cmtstate "github.com/cometbft/cometbft/state" cmttypes "github.com/cometbft/cometbft/types" servertypes "github.com/cosmos/cosmos-sdk/server/types" @@ -25,6 +27,7 @@ import ( rollnode "github.com/rollkit/rollkit/node" rollkitp2p "github.com/rollkit/rollkit/pkg/p2p" rstore "github.com/rollkit/rollkit/pkg/store" + "github.com/rollkit/rollkit/types" "github.com/rollkit/go-execution-abci/pkg/p2p" ) @@ -244,6 +247,7 @@ func (a *Adapter) InitChain(ctx context.Context, genesisTime time.Time, initialH } else { s.ConsensusParams = cmttypes.ConsensusParamsFromProto(consensusParams) } + s.ChainID = chainID vals, err := cmttypes.PB2TM.ValidatorUpdates(res.Validators) if err != nil { @@ -279,6 +283,7 @@ func (a *Adapter) ExecuteTxs( blockHeight uint64, timestamp time.Time, prevStateRoot []byte, + metadata map[string]interface{}, ) ([]byte, uint64, error) { execStart := time.Now() defer func() { @@ -287,19 +292,53 @@ func (a *Adapter) ExecuteTxs( a.Logger.Info("Executing block", "height", blockHeight, "num_txs", len(txs), "timestamp", timestamp) a.Metrics.TxsExecutedPerBlock.Observe(float64(len(txs))) + var headerHash types.Hash + if h, ok := metadata[types.HeaderHashKey]; ok { + headerHash = h.(types.Hash) + } else { + a.Logger.Info("header hash not found in metadata, running genesis block") + } + s, err := a.Store.LoadState(ctx) if err != nil { return nil, 0, fmt.Errorf("failed to load state: %w", err) } + var proposedLastCommit abci.CommitInfo + if blockHeight > 1 { + header, data, err := a.RollkitStore.GetBlockData(ctx, blockHeight-1) + if err != nil { + return nil, 0, 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, + }, + }, + } + proposedLastCommit = cometCommitToABCICommitInfo(commitForPrevBlock) + } else { + // For the first block, ProposedLastCommit is empty + proposedLastCommit = abci.CommitInfo{Round: 0, Votes: []abci.VoteInfo{}} + } + ppResp, err := a.App.ProcessProposal(&abci.RequestProcessProposal{ - Txs: txs, - ProposedLastCommit: abci.CommitInfo{}, - Hash: prevStateRoot, + Hash: headerHash, Height: int64(blockHeight), Time: timestamp, - NextValidatorsHash: s.NextValidators.Hash(), + Txs: txs, + ProposedLastCommit: proposedLastCommit, + Misbehavior: []abci.Misbehavior{}, ProposerAddress: s.Validators.Proposer.Address, + NextValidatorsHash: s.NextValidators.Hash(), }) if err != nil { return nil, 0, err @@ -310,17 +349,24 @@ func (a *Adapter) ExecuteTxs( } fbResp, err := a.App.FinalizeBlock(&abci.RequestFinalizeBlock{ - Txs: txs, - Hash: prevStateRoot, - Height: int64(blockHeight), - Time: timestamp, + Hash: headerHash, NextValidatorsHash: s.NextValidators.Hash(), ProposerAddress: s.Validators.Proposer.Address, + Height: int64(blockHeight), + Time: timestamp, + DecidedLastCommit: abci.CommitInfo{ + Round: 0, + Votes: nil, + }, + Txs: txs, }) if err != nil { return nil, 0, err } + s.AppHash = fbResp.AppHash + s.LastBlockHeight = int64(blockHeight) + nValSet := s.NextValidators.Copy() validatorUpdates, err := cmttypes.PB2TM.ValidatorUpdates(fbResp.ValidatorUpdates) @@ -404,8 +450,46 @@ func (a *Adapter) ExecuteTxs( for i := range txs { cmtTxs[i] = txs[i] } - block := s.MakeBlock(int64(blockHeight), cmtTxs, &cmttypes.Commit{Height: int64(blockHeight)}, nil, s.Validators.Proposer.Address) - fireEvents(a.Logger, a.EventBus, block, cmttypes.BlockID{}, fbResp, validatorUpdates) + + var commit = &cmttypes.Commit{ + Height: int64(blockHeight), + Round: 0, + Signatures: []cmttypes.CommitSig{ + { + BlockIDFlag: cmttypes.BlockIDFlagCommit, + ValidatorAddress: s.Validators.Proposer.Address, + Timestamp: time.Now(), + Signature: []byte{}, + }, + }, + } + + if blockHeight > 1 { + header, data, err := a.RollkitStore.GetBlockData(ctx, blockHeight-1) + if err != nil { + return nil, 0, fmt.Errorf("failed to get previous block data: %w", err) + } + + commit = &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, + }, + }, + } + } + + block := s.MakeBlock(int64(blockHeight), cmtTxs, commit, nil, s.Validators.Proposer.Address) + + currentBlockID := cmttypes.BlockID{Hash: block.Hash(), PartSetHeader: cmttypes.PartSetHeader{Total: 1, Hash: block.DataHash}} + + fireEvents(a.Logger, a.EventBus, block, currentBlockID, fbResp, validatorUpdates) a.Logger.Info("block executed successfully", "height", blockHeight, "appHash", fmt.Sprintf("%X", fbResp.AppHash)) return fbResp.AppHash, uint64(s.ConsensusParams.Block.MaxBytes), nil @@ -521,3 +605,34 @@ func (a *Adapter) GetTxs(ctx context.Context) ([][]byte, error) { func (a *Adapter) SetFinal(ctx context.Context, blockHeight uint64) error { return nil } + +func cometCommitToABCICommitInfo(commit *cmttypes.Commit) abci.CommitInfo { + if commit == nil { + return abci.CommitInfo{ + Round: 0, + Votes: []abci.VoteInfo{}, + } + } + + if len(commit.Signatures) == 0 { + return abci.CommitInfo{ + Round: commit.Round, + Votes: []abci.VoteInfo{}, + } + } + + votes := make([]abci.VoteInfo, len(commit.Signatures)) + for i, sig := range commit.Signatures { + votes[i] = abci.VoteInfo{ + Validator: abci.Validator{ + Address: sig.ValidatorAddress, + Power: 0, + }, + BlockIdFlag: types1.BlockIDFlag(sig.BlockIDFlag), + } + } + return abci.CommitInfo{ + Round: commit.Round, + Votes: votes, + } +} diff --git a/pkg/adapter/adapter_test.go b/pkg/adapter/adapter_test.go index ecb9ed9..b5b7ddb 100644 --- a/pkg/adapter/adapter_test.go +++ b/pkg/adapter/adapter_test.go @@ -87,7 +87,7 @@ func TestExecuteFiresEvents(t *testing.T) { require.NoError(t, adapter.Store.SaveState(ctx, stateFixture())) // when - _, _, err := adapter.ExecuteTxs(ctx, spec.txs, 1, timestamp, bytes.Repeat([]byte{1}, 32)) + _, _, err := adapter.ExecuteTxs(ctx, spec.txs, 1, timestamp, bytes.Repeat([]byte{1}, 32), nil) if spec.expErr { require.Error(t, err) blockMx.RLock() diff --git a/pkg/common/convert.go b/pkg/common/convert.go index 0f71045..7a45238 100644 --- a/pkg/common/convert.go +++ b/pkg/common/convert.go @@ -5,8 +5,9 @@ import ( "time" cmbytes "github.com/cometbft/cometbft/libs/bytes" - cmversion "github.com/cometbft/cometbft/proto/tendermint/version" + cmprotoversion "github.com/cometbft/cometbft/proto/tendermint/version" cmttypes "github.com/cometbft/cometbft/types" + cmtversion "github.com/cometbft/cometbft/version" rlktypes "github.com/rollkit/rollkit/types" ) @@ -15,14 +16,14 @@ import ( // Caller should fill all the fields that are not available in Rollkit header (like ChainID). func ToABCIHeader(header *rlktypes.Header) (cmttypes.Header, error) { return cmttypes.Header{ - Version: cmversion.Consensus{ - Block: header.Version.Block, + 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[:]), + Hash: cmbytes.HexBytes(header.LastHeaderHash), PartSetHeader: cmttypes.PartSetHeader{ Total: 0, Hash: nil, @@ -94,7 +95,7 @@ func ToABCIBlockMeta(header *rlktypes.SignedHeader, data *rlktypes.Data) (*cmtty // ToABCICommit returns a commit format defined by ABCI. // Other fields (especially ValidatorAddress and Timestamp of Signature) have to be filled by caller. -func ToABCICommit(height uint64, hash rlktypes.Hash, val cmttypes.Address, time time.Time, signature rlktypes.Signature) *cmttypes.Commit { +func ToABCICommit(height uint64, hash []byte, val cmttypes.Address, time time.Time, signature rlktypes.Signature) *cmttypes.Commit { return &cmttypes.Commit{ Height: int64(height), //nolint:gosec Round: 0, diff --git a/pkg/rollkit_adapter/commit_hasher.go b/pkg/rollkit_adapter/commit_hasher.go new file mode 100644 index 0000000..993e7ef --- /dev/null +++ b/pkg/rollkit_adapter/commit_hasher.go @@ -0,0 +1,16 @@ +package rollkitadapter + +import ( + "github.com/rollkit/rollkit/types" + + "github.com/rollkit/go-execution-abci/pkg/rpc" +) + +func CreateCometBFTCommitHasher() types.CommitHashProvider { + return func(signature *types.Signature, header *types.Header, proposerAddress []byte) (types.Hash, error) { + abciCommit := rpc.GetABCICommit(header.Height(), header.Hash(), proposerAddress, header.Time(), *signature) + abciCommit.Signatures[0].ValidatorAddress = proposerAddress + abciCommit.Signatures[0].Timestamp = header.Time() + return types.Hash(abciCommit.Hash()), nil + } +} diff --git a/pkg/rollkit_adapter/header_hasher.go b/pkg/rollkit_adapter/header_hasher.go new file mode 100644 index 0000000..e2dffca --- /dev/null +++ b/pkg/rollkit_adapter/header_hasher.go @@ -0,0 +1,23 @@ +package rollkitadapter + +import ( + rollkittypes "github.com/rollkit/rollkit/types" + + "github.com/rollkit/go-execution-abci/pkg/common" +) + +// init automatically sets the CometBFT header hasher as the default when this package is imported +func init() { + rollkittypes.SetHeaderHasher(CreateCometBFTHeaderHasher()) +} + +func CreateCometBFTHeaderHasher() rollkittypes.HeaderHasher { + return func(header *rollkittypes.Header) (rollkittypes.Hash, error) { + abciHeader, err := common.ToABCIHeader(header) + if err != nil { + return nil, err + } + + return rollkittypes.Hash(abciHeader.Hash()), nil + } +} diff --git a/pkg/rollkit_adapter/signer.go b/pkg/rollkit_adapter/signer.go new file mode 100644 index 0000000..43c9052 --- /dev/null +++ b/pkg/rollkit_adapter/signer.go @@ -0,0 +1,35 @@ +package rollkitadapter + +import ( + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + cmtypes "github.com/cometbft/cometbft/types" + + "github.com/rollkit/rollkit/types" + + "github.com/rollkit/go-execution-abci/pkg/rpc" +) + +func CreateCometBFTPayloadProvider() types.SignaturePayloadProvider { + return func(header *types.Header, data *types.Data) ([]byte, error) { + abciHeaderForSigning, err := rpc.ToABCIHeader(header) + if err != nil { + return nil, err + } + vote := cmtproto.Vote{ + Type: cmtproto.PrecommitType, + Height: int64(header.Height()), //nolint:gosec + Round: 0, + BlockID: cmtproto.BlockID{ + Hash: abciHeaderForSigning.Hash(), + PartSetHeader: cmtproto.PartSetHeader{}, + }, + Timestamp: header.Time(), + ValidatorAddress: header.ProposerAddress, + ValidatorIndex: 0, + } + chainID := header.ChainID() + consensusVoteBytes := cmtypes.VoteSignBytes(chainID, &vote) + + return consensusVoteBytes, nil + } +} diff --git a/pkg/rollkit_adapter/validator_hasher.go b/pkg/rollkit_adapter/validator_hasher.go new file mode 100644 index 0000000..6558229 --- /dev/null +++ b/pkg/rollkit_adapter/validator_hasher.go @@ -0,0 +1,65 @@ +package rollkitadapter + +import ( + "bytes" + stdsha256 "crypto/sha256" + "encoding/hex" + "fmt" + + "cosmossdk.io/log" + tmcryptoed25519 "github.com/cometbft/cometbft/crypto/ed25519" + tmtypes "github.com/cometbft/cometbft/types" + "github.com/libp2p/go-libp2p/core/crypto" + + rollkittypes "github.com/rollkit/rollkit/types" +) + +// CreateCometBFTValidatorHasher returns a function that calculates the ValidatorHash +// compatible with CometBFT. This function is intended to be injected into Rollkit's Manager. +func CreateCometBFTValidatorHasher(logger log.Logger) rollkittypes.ValidatorHasher { + 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 { + logger.Error("ValidatorHasher: failed to get raw bytes from libp2p public key", "error", err) + return calculatedHash, fmt.Errorf("ValidatorHasher: failed to get raw bytes from libp2p public key: %w", err) + } + if len(rawKey) != tmcryptoed25519.PubKeySize { + errMsg := fmt.Sprintf("ValidatorHasher: libp2p public key size (%d) does not match CometBFT Ed25519 PubKeySize (%d)", len(rawKey), tmcryptoed25519.PubKeySize) + logger.Error(errMsg) + return calculatedHash, fmt.Errorf("%s", errMsg) + } + cometBftPubKey = rawKey + } else { + errMsg := fmt.Sprintf("ValidatorHasher: unsupported public key type '%s', expected Ed25519 for CometBFT compatibility", pubKey.Type()) + logger.Error(errMsg) + return calculatedHash, fmt.Errorf("%s", errMsg) + } + + votingPower := int64(1) + sequencerValidator := tmtypes.NewValidator(cometBftPubKey, votingPower) + + derivedAddress := sequencerValidator.Address.Bytes() + if !bytes.Equal(derivedAddress, proposerAddress) { + errMsg := fmt.Sprintf("ValidatorHasher: 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())) + logger.Error(errMsg) + return calculatedHash, fmt.Errorf("%s", errMsg) + } + + sequencerValidatorSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{sequencerValidator}) + + hashSumBytes := sequencerValidatorSet.Hash() + + calculatedHash = make(rollkittypes.Hash, stdsha256.Size) + copy(calculatedHash, hashSumBytes) + + logger.Debug("ValidatorHasher: Hashing process completed successfully", "calculatedHash", hex.EncodeToString(calculatedHash)) + return calculatedHash, nil + } +} diff --git a/pkg/rpc/core/blocks.go b/pkg/rpc/core/blocks.go index 99cff9b..3f8af19 100644 --- a/pkg/rpc/core/blocks.go +++ b/pkg/rpc/core/blocks.go @@ -115,7 +115,11 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error) return nil, err } - hash := header.Hash() + hash, err := env.HeaderHasher(&header.Header) + if err != nil { + return nil, err + } + abciBlock, err := common.ToABCIBlock(header, data) if err != nil { return nil, err @@ -162,25 +166,31 @@ func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, error) { wrappedCtx := ctx.Context() heightValue := normalizeHeight(heightPtr) - header, data, err := env.Adapter.RollkitStore.GetBlockData(wrappedCtx, heightValue) + rollkitSignedHeader, rollkitData, err := env.Adapter.RollkitStore.GetBlockData(wrappedCtx, heightValue) if err != nil { return nil, err } // we should have a single validator - if len(header.ProposerAddress) == 0 { + if len(rollkitSignedHeader.ProposerAddress) == 0 { return nil, errors.New("empty proposer address found in block header") } - val := header.ProposerAddress - commit := common.ToABCICommit(heightValue, header.Hash(), val, header.Time(), header.Signature) - - block, err := common.ToABCIBlock(header, data) + // Convert to CometBFT block to get the correct CometBFT header and its hash + abciBlock, err := common.ToABCIBlock(rollkitSignedHeader, rollkitData) if err != nil { return nil, err } - return ctypes.NewResultCommit(&block.Header, commit, true), nil + commitForAbciHeader := common.ToABCICommit( + uint64(abciBlock.Height), + abciBlock.Hash(), + rollkitSignedHeader.ProposerAddress, + abciBlock.Time, + rollkitSignedHeader.Signature, + ) + + return ctypes.NewResultCommit(&abciBlock.Header, commitForAbciHeader, true), nil } // BlockResults is not fully implemented as in FullClient because diff --git a/pkg/rpc/core/blocks_test.go b/pkg/rpc/core/blocks_test.go index 3c0d3ed..f2cc778 100644 --- a/pkg/rpc/core/blocks_test.go +++ b/pkg/rpc/core/blocks_test.go @@ -4,8 +4,15 @@ import ( "testing" "time" + cryptotypes "github.com/cometbft/cometbft/crypto" + "github.com/cometbft/cometbft/crypto/ed25519" 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" rpctypes "github.com/cometbft/cometbft/rpc/jsonrpc/types" + cmttypes "github.com/cometbft/cometbft/types" + "github.com/libp2p/go-libp2p/core/crypto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -13,6 +20,7 @@ import ( "github.com/rollkit/rollkit/types" "github.com/rollkit/go-execution-abci/pkg/adapter" + goexeccommon "github.com/rollkit/go-execution-abci/pkg/common" ) func newTestRPCContext() *rpctypes.Context { @@ -88,3 +96,153 @@ func TestBlockSearch_Success(t *testing.T) { mockRollkitStore.AssertExpectations(t) mockApp.AssertExpectations(t) } + +func TestCommit_VerifyCometBFTLightClientCompatibility_MultipleBlocks(t *testing.T) { + require := require.New(t) + assert := assert.New(t) + + mockTxIndexer := new(MockTxIndexer) + mockBlockIndexer := new(MockBlockIndexer) + mockApp := new(MockApp) + mockRollkitStore := new(MockRollkitStore) + + env = &Environment{ + Adapter: &adapter.Adapter{ + RollkitStore: mockRollkitStore, + App: mockApp, + }, + TxIndexer: mockTxIndexer, + BlockIndexer: mockBlockIndexer, + Logger: cmtlog.NewNopLogger(), + } + + privKey, pubKey, err := crypto.GenerateEd25519Key(nil) + require.NoError(err) + + pubKeyBytes, err := pubKey.Raw() + require.NoError(err) + var cmtEdPubKey cryptotypes.PubKey = ed25519.PubKey(pubKeyBytes) + + fixedValSet := &cmttypes.ValidatorSet{ + Validators: []*cmttypes.Validator{ + cmttypes.NewValidator(cmtEdPubKey, 1), + }, + Proposer: cmttypes.NewValidator(cmtEdPubKey, 1), + } + + var trustedHeader cmttypes.SignedHeader + setTrustedHeader := false + var lastRollkitHeaderHash []byte + var lastRollkitCommitHash []byte + chainID := "test-chain-multiple-blocks" + now := time.Now() + + for i := 1; i <= 3; i++ { + blockHeight := uint64(i) + heightForRPC := int64(blockHeight) + + // Create Rollkit Block Data + blockData := &types.Data{ + Metadata: &types.Metadata{ + ChainID: chainID, + Height: blockHeight, + Time: uint64(now.UnixNano() + int64(i-1)*int64(time.Second)), + LastDataHash: nil, + }, + Txs: make(types.Txs, 0), + } + dataHash := blockData.DACommitment() + + // Create Rollkit Header + rollkitHeader := types.Header{ + BaseHeader: types.BaseHeader{ + Height: blockHeight, + Time: uint64(now.UnixNano() + int64(i-1)*int64(time.Second)), + ChainID: chainID, + }, + Version: types.Version{Block: 1, App: 1}, + LastHeaderHash: lastRollkitHeaderHash, + LastCommitHash: lastRollkitCommitHash, + DataHash: dataHash, + ConsensusHash: BytesToSliceHash([]byte{byte(i)}), + AppHash: BytesToSliceHash([]byte{byte(i + 10)}), + LastResultsHash: BytesToSliceHash([]byte{byte(i + 20)}), + ValidatorHash: BytesToSliceHash(fixedValSet.Hash()), + ProposerAddress: fixedValSet.Proposer.Address, + } + + abciHeaderForSigning, err := goexeccommon.ToABCIHeader(&rollkitHeader) + require.NoError(err) + abciHeaderHashForSigning := abciHeaderForSigning.Hash() + abciHeaderTimeForSigning := abciHeaderForSigning.Time + + voteProto := cmtproto.Vote{ + Type: cmtproto.PrecommitType, + Height: heightForRPC, + Round: 0, + BlockID: cmtproto.BlockID{Hash: abciHeaderHashForSigning, PartSetHeader: cmtproto.PartSetHeader{Total: 0, Hash: nil}}, + Timestamp: abciHeaderTimeForSigning, + ValidatorAddress: fixedValSet.Proposer.Address, + ValidatorIndex: 0, + } + payloadBytes := cmttypes.VoteSignBytes(chainID, &voteProto) + realSignature, err := privKey.Sign(payloadBytes) + require.NoError(err) + + // Create Rollkit Signed Header with the new signature + signer, err := types.NewSigner(pubKey) + require.NoError(err) + rollkitSignedHeader := &types.SignedHeader{ + Header: rollkitHeader, + Signature: types.Signature(realSignature), + Signer: signer, + } + + // Mock RollkitStore + mockRollkitStore.On("GetBlockData", mock.Anything, blockHeight).Return(rollkitSignedHeader, blockData, nil).Once() + + // Call the Commit RPC method + rpcCtx := newTestRPCContext() + commitResult, err := Commit(rpcCtx, &heightForRPC) + require.NoError(err) + require.NotNil(commitResult) + require.NotNil(commitResult.Header) + require.NotNil(commitResult.Commit) + assert.Equal(heightForRPC, commitResult.Height) + assert.EqualValues(rollkitHeader.AppHash, commitResult.AppHash.Bytes()) // AppHash is []byte vs HexBytes + + // Verify with light client + if !setTrustedHeader { + trustedHeader = commitResult.SignedHeader + setTrustedHeader = true + } else { + trustingPeriod := 3 * time.Hour + trustLevel := math.Fraction{Numerator: 1, Denominator: 1} + maxClockDrift := 10 * time.Second + + err = light.Verify(&trustedHeader, fixedValSet, &commitResult.SignedHeader, fixedValSet, trustingPeriod, time.Unix(0, int64(rollkitHeader.BaseHeader.Time)), maxClockDrift, trustLevel) + require.NoError(err, "failed to pass light.Verify() for block %d", blockHeight) + trustedHeader = commitResult.SignedHeader + } + + // Update last hashes for the next iteration + currentRollkitHeaderHash := rollkitHeader.Hash() + lastRollkitHeaderHash = BytesToSliceHash(currentRollkitHeaderHash) + + if commitResult.Commit != nil { + lastRollkitCommitHash = BytesToSliceHash(commitResult.Commit.Hash()) + } + } + + mockRollkitStore.AssertExpectations(t) + mockApp.AssertExpectations(t) + mockTxIndexer.AssertExpectations(t) + mockBlockIndexer.AssertExpectations(t) +} + +// Renamed and modified helper function to return []byte of 32 length +func BytesToSliceHash(b []byte) []byte { + h := make([]byte, 32) + copy(h, b) // copy will take min(len(h), len(b)) + return h +} diff --git a/pkg/rpc/core/env.go b/pkg/rpc/core/env.go index ec3a1ce..5a859fa 100644 --- a/pkg/rpc/core/env.go +++ b/pkg/rpc/core/env.go @@ -8,6 +8,8 @@ import ( "github.com/cometbft/cometbft/state/indexer" "github.com/cometbft/cometbft/state/txindex" + "github.com/rollkit/rollkit/types" + "github.com/rollkit/go-execution-abci/pkg/adapter" ) @@ -30,6 +32,8 @@ type Environment struct { BlockIndexer indexer.BlockIndexer Logger cmtlog.Logger Config cmtcfg.RPCConfig + + HeaderHasher types.HeaderHasher } func validateSkipCount(page, perPage int) int { diff --git a/pkg/rpc/utils.go b/pkg/rpc/utils.go index 2a4adec..c173cc4 100644 --- a/pkg/rpc/utils.go +++ b/pkg/rpc/utils.go @@ -85,7 +85,7 @@ func ToABCIBlock(header *types.SignedHeader, data *types.Data) (*cmttypes.Block, return nil, errors.New("proposer address is not set") } - abciCommit := getABCICommit(header.Height(), header.Hash(), header.ProposerAddress, header.Time(), header.Signature) + abciCommit := GetABCICommit(header.Height(), header.Hash(), header.ProposerAddress, header.Time(), header.Signature) // This assumes that we have only one signature if len(abciCommit.Signatures) == 1 { @@ -123,9 +123,9 @@ func ToABCIBlockMeta(header *types.SignedHeader, data *types.Data) (*cmttypes.Bl }, nil } -// getABCICommit returns a commit format defined by ABCI. +// GetABCICommit returns a commit format defined by ABCI. // Other fields (especially ValidatorAddress and Timestamp of Signature) have to be filled by caller. -func getABCICommit(height uint64, hash []byte, val cmttypes.Address, time time.Time, signature []byte) *cmttypes.Commit { +func GetABCICommit(height uint64, hash []byte, val cmttypes.Address, time time.Time, signature []byte) *cmttypes.Commit { tmCommit := cmttypes.Commit{ Height: int64(height), //nolint:gosec Round: 0, diff --git a/server/start.go b/server/start.go index 795c330..6797983 100644 --- a/server/start.go +++ b/server/start.go @@ -34,6 +34,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + rlkblock "github.com/rollkit/rollkit/block" "github.com/rollkit/rollkit/da/jsonrpc" "github.com/rollkit/rollkit/node" "github.com/rollkit/rollkit/pkg/config" @@ -45,6 +46,7 @@ import ( "github.com/rollkit/rollkit/sequencers/single" "github.com/rollkit/go-execution-abci/pkg/adapter" + rollkit_adapter "github.com/rollkit/go-execution-abci/pkg/rollkit_adapter" "github.com/rollkit/go-execution-abci/pkg/rpc" "github.com/rollkit/go-execution-abci/pkg/rpc/core" execsigner "github.com/rollkit/go-execution-abci/pkg/signer" @@ -410,6 +412,11 @@ func setupNodeAndExecutor( } } + cometBFTHasher := rollkit_adapter.CreateCometBFTValidatorHasher(logger.With("module", "CometBFTValidatorHasher")) + cometBFTPayloadProvider := rollkit_adapter.CreateCometBFTPayloadProvider() + cometBFTHeaderHasher := rollkit_adapter.CreateCometBFTHeaderHasher() + cometBFTCommitHashProvider := rollkit_adapter.CreateCometBFTCommitHasher() + sequencer, err := single.NewSequencer( ctx, logger, @@ -436,6 +443,9 @@ func setupNodeAndExecutor( database, metrics, logger, + rlkblock.WithValidatorHasher(cometBFTHasher), + rlkblock.WithSignaturePayloadProvider(cometBFTPayloadProvider), + rlkblock.WithCommitHashProvider(cometBFTCommitHashProvider), ) if err != nil { return nil, nil, cleanupFn, err @@ -456,6 +466,7 @@ func setupNodeAndExecutor( BlockIndexer: blockIndexer, Logger: servercmtlog.CometLoggerWrapper{Logger: logger}, Config: *cfg.RPC, + HeaderHasher: cometBFTHeaderHasher, }) // Pass the created handler to the RPC server constructor