Skip to content

chore: Use stored block data instead #90

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 37 additions & 32 deletions pkg/adapter/adapter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,10 @@ import (
cmtypes "github.com/cometbft/cometbft/types"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
ds "github.com/ipfs/go-datastore"
kt "github.com/ipfs/go-datastore/keytransform"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

rollnode "github.com/rollkit/rollkit/node"
rstore "github.com/rollkit/rollkit/pkg/store"
"github.com/rollkit/rollkit/types"
)

func TestExecuteFiresEvents(t *testing.T) {
Expand Down Expand Up @@ -76,37 +74,16 @@ func TestExecuteFiresEvents(t *testing.T) {

capturedBlockEvents, blockMx := captureEvents(ctx, eventBus, "tm.event='NewBlock'", 1)
capturedTxEvents, txMx := captureEvents(ctx, eventBus, "tm.event='Tx'", 2)
mockApp := &MockABCIApp{
ProcessProposalFn: func(proposal *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
},
FinalizeBlockFn: func(block *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
return &abci.ResponseFinalizeBlock{
TxResults: myExecResult,
}, nil
},
CommitFn: func() (*abci.ResponseCommit, error) {
return &abci.ResponseCommit{}, nil
},
}
spec.mockMutator(mockApp)
myMockApp := mockApp(myExecResult, spec.mockMutator)

originStore := ds.NewMapDatastore()
rollkitPrefixStore := kt.Wrap(originStore, &kt.PrefixTransform{
Prefix: ds.NewKey(rollnode.RollkitPrefix),
})
rollkitStore := rstore.New(rollkitPrefixStore)
abciStore := NewStore(originStore)
adapter := &Adapter{
App: mockApp,
Store: abciStore,
RollkitStore: rollkitStore,
EventBus: eventBus,
Metrics: NopMetrics(),
Logger: log.NewTestLogger(t),
MempoolIDs: newMempoolIDs(),
Mempool: &mempool.NopMempool{},
}
adapter := NewABCIExecutor(myMockApp, originStore, nil, nil, log.NewTestLogger(t), nil, nil, NopMetrics())
adapter.EventBus = eventBus
adapter.MempoolIDs = newMempoolIDs()
adapter.Mempool = &mempool.NopMempool{}

var sig types.Signature = make([]byte, 32)
require.NoError(t, adapter.RollkitStore.SaveBlockData(ctx, headerFixture(), &types.Data{Txs: make(types.Txs, 0)}, &sig))
require.NoError(t, adapter.Store.SaveState(ctx, stateFixture()))

// when
Expand Down Expand Up @@ -154,6 +131,24 @@ func TestExecuteFiresEvents(t *testing.T) {
}
}

func mockApp(myExecResult []*abci.ExecTxResult, mutator ...func(*MockABCIApp)) *MockABCIApp {
r := &MockABCIApp{
ProcessProposalFn: func(proposal *abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error) {
return &abci.ResponseProcessProposal{Status: abci.ResponseProcessProposal_ACCEPT}, nil
},
FinalizeBlockFn: func(block *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) {
return &abci.ResponseFinalizeBlock{TxResults: myExecResult}, nil
},
CommitFn: func() (*abci.ResponseCommit, error) {
return &abci.ResponseCommit{}, nil
},
}
for _, m := range mutator {
m(r)
}
return r
}

func captureEvents(ctx context.Context, eventBus *cmtypes.EventBus, query string, numEventsExpected int) (*[]cmtpubsub.Message, *sync.RWMutex) {
subscriber := fmt.Sprintf("test-%d", time.Now().UnixNano())
evSub, err := eventBus.Subscribe(ctx, subscriber, cmtquery.MustCompile(query), numEventsExpected)
Expand Down Expand Up @@ -181,6 +176,16 @@ func captureEvents(ctx context.Context, eventBus *cmtypes.EventBus, query string
return &capturedEvents, &mx
}

func headerFixture() *types.SignedHeader {
return &types.SignedHeader{
Header: types.Header{
BaseHeader: types.BaseHeader{Height: 2, Time: uint64(time.Now().UnixNano())},
ProposerAddress: []byte("proposer1"),
AppHash: []byte("apphash1"),
},
}
}

type MockABCIApp struct {
servertypes.ABCI // satisfy the interface
ProcessProposalFn func(*abci.RequestProcessProposal) (*abci.ResponseProcessProposal, error)
Expand Down
112 changes: 112 additions & 0 deletions pkg/common/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package common

import (
"errors"
"time"

cmbytes "github.com/cometbft/cometbft/libs/bytes"
cmversion "github.com/cometbft/cometbft/proto/tendermint/version"
cmttypes "github.com/cometbft/cometbft/types"

rlktypes "github.com/rollkit/rollkit/types"
)
Comment on lines +1 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider organizing imports into standard groups with blank lines between them:

  1. Standard library imports
  2. External package imports
  3. Internal package imports

This pattern improves readability and follows Go conventions. For example:

import (
    "errors"
    "time"
    
    cmbytes "github.com/cometbft/cometbft/libs/bytes"
    cmversion "github.com/cometbft/cometbft/proto/tendermint/version"
    cmttypes "github.com/cometbft/cometbft/types"
    
    rlktypes "github.com/rollkit/rollkit/types"
)
Suggested change
package common
import (
"errors"
"time"
cmbytes "github.com/cometbft/cometbft/libs/bytes"
cmversion "github.com/cometbft/cometbft/proto/tendermint/version"
cmttypes "github.com/cometbft/cometbft/types"
rlktypes "github.com/rollkit/rollkit/types"
)
package common
import (
"errors"
"time"
cmbytes "github.com/cometbft/cometbft/libs/bytes"
cmversion "github.com/cometbft/cometbft/proto/tendermint/version"
cmttypes "github.com/cometbft/cometbft/types"
rlktypes "github.com/rollkit/rollkit/types"
)

Spotted by Diamond (based on custom rules)

Is this helpful? React 👍 or 👎 to let us know.


// ToABCIHeader converts Rollkit header to Header format defined in ABCI.
// 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,
App: header.Version.App,
},
Height: int64(header.Height()), //nolint:gosec
Time: header.Time(),
LastBlockID: cmttypes.BlockID{
Hash: cmbytes.HexBytes(header.LastHeaderHash[:]),
PartSetHeader: cmttypes.PartSetHeader{
Total: 0,
Hash: nil,
},
},
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(),
ValidatorsHash: cmbytes.HexBytes(header.ValidatorHash),
NextValidatorsHash: cmbytes.HexBytes(header.ValidatorHash),
}, nil
}

// ToABCIBlock converts Rolkit block into block format defined by ABCI.
// Returned block should pass `ValidateBasic`.
func ToABCIBlock(header *rlktypes.SignedHeader, data *rlktypes.Data) (*cmttypes.Block, error) {
abciHeader, err := ToABCIHeader(&header.Header)
if err != nil {
return nil, err
}

// we have one validator
if len(header.ProposerAddress) == 0 {
return nil, errors.New("proposer address is not set")
}

abciCommit := ToABCICommit(header.Height(), header.Hash(), header.ProposerAddress, header.Time(), header.Signature)

// This assumes that we have only one signature
if len(abciCommit.Signatures) == 1 {
abciCommit.Signatures[0].ValidatorAddress = header.ProposerAddress
}
Comment on lines +59 to +62
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Hardcoded assumption about signature count.

The code assumes there will only be one signature, which is currently correct but may not remain true if requirements change. Consider making this more robust by iterating through signatures or documenting this assumption clearly.


🏁 Script executed:

#!/bin/bash
# Check if there are any Rollkit files that handle multiple signatures
rg -A 2 -B 2 "[]signature" --type go -g "*.go" 

Length of output: 122


🏁 Script executed:

#!/bin/bash
# Search for all occurrences of the Signatures field across Go files
rg -n "Signatures" -g "*.go"

Length of output: 518


Avoid hardcoding single‐signature assumption

The code currently assumes there will only ever be one signature in Commit.Signatures, which could break if multi‐signature support is added. This pattern appears in:

  • pkg/common/convert.go

    • Lines 60–62:
      // This assumes that we have only one signature
      if len(abciCommit.Signatures) == 1 {
          abciCommit.Signatures[0].ValidatorAddress = header.ProposerAddress
      }
    • Line 105:
      Signatures: []cmttypes.CommitSig{{ /* … */ }},
  • pkg/rpc/utils.go

    • Lines 91–92:
      if len(abciCommit.Signatures) == 1 {
          abciCommit.Signatures[0].ValidatorAddress = header.ProposerAddress
      }
    • Line 136:
      Signatures: make([]cmttypes.CommitSig, 1),
    • Line 144:
      tmCommit.Signatures[0] = commitSig

Rather than special-casing a single signature, consider uniformly handling all signatures. For example, replace the one-off check/assignment with:

- if len(abciCommit.Signatures) == 1 {
-     abciCommit.Signatures[0].ValidatorAddress = header.ProposerAddress
- }
+ for i := range abciCommit.Signatures {
+     abciCommit.Signatures[i].ValidatorAddress = header.ProposerAddress
+ }

And update the static slice constructions to collect or initialize all signatures dynamically. If single-signature is an explicit protocol limitation, add a clear comment/TODO in one shared helper to document this behavior.

abciBlock := cmttypes.Block{
Header: abciHeader,
Evidence: cmttypes.EvidenceData{
Evidence: nil,
},
LastCommit: abciCommit,
}
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) (*cmttypes.BlockMeta, error) {
cmblock, err := ToABCIBlock(header, data)
if err != nil {
return nil, err
}
blockID := cmttypes.BlockID{Hash: cmblock.Hash()}

return &cmttypes.BlockMeta{
BlockID: blockID,
BlockSize: cmblock.Size(),
Header: cmblock.Header,
NumTxs: len(cmblock.Txs),
}, nil
}

// 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 {
return &cmttypes.Commit{
Height: int64(height), //nolint:gosec
Round: 0,
BlockID: cmttypes.BlockID{
Hash: cmbytes.HexBytes(hash),
PartSetHeader: cmttypes.PartSetHeader{},
},
Signatures: []cmttypes.CommitSig{{
BlockIDFlag: cmttypes.BlockIDFlagCommit,
Signature: signature,
ValidatorAddress: val,
Timestamp: time,
}},
}
}
16 changes: 9 additions & 7 deletions pkg/rpc/core/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (

"github.com/rollkit/rollkit/block"
rlktypes "github.com/rollkit/rollkit/types"

"github.com/rollkit/go-execution-abci/pkg/common"
)

// BlockSearch searches for a paginated set of blocks matching BeginBlock and
Expand Down Expand Up @@ -69,7 +71,7 @@ func BlockSearch(
if err != nil {
return nil, err
}
block, err := ToABCIBlock(header, data)
block, err := common.ToABCIBlock(header, data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -114,7 +116,7 @@ func Block(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultBlock, error)
}

hash := header.Hash()
abciBlock, err := ToABCIBlock(header, data)
abciBlock, err := common.ToABCIBlock(header, data)
if err != nil {
return nil, err
}
Expand All @@ -138,7 +140,7 @@ func BlockByHash(ctx *rpctypes.Context, hash []byte) (*ctypes.ResultBlock, error
return nil, err
}

abciBlock, err := ToABCIBlock(header, data)
abciBlock, err := common.ToABCIBlock(header, data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -171,9 +173,9 @@ func Commit(ctx *rpctypes.Context, heightPtr *int64) (*ctypes.ResultCommit, erro
}

val := header.ProposerAddress
commit := GetABCICommit(heightValue, header.Hash(), val, header.Time(), header.Signature)
commit := common.ToABCICommit(heightValue, header.Hash(), val, header.Time(), header.Signature)

block, err := ToABCIBlock(header, data)
block, err := common.ToABCIBlock(header, data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -238,7 +240,7 @@ func HeaderByHash(ctx *rpctypes.Context, hash cmbytes.HexBytes) (*ctypes.ResultH
return nil, err
}

blockMeta, err := ToABCIBlockMeta(header, data)
blockMeta, err := common.ToABCIBlockMeta(header, data)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -280,7 +282,7 @@ func BlockchainInfo(ctx *rpctypes.Context, minHeight, maxHeight int64) (*ctypes.
return nil, err
}
if header != nil && data != nil {
cmblockmeta, err := ToABCIBlockMeta(header, data)
cmblockmeta, err := common.ToABCIBlockMeta(header, data)
if err != nil {
return nil, err
}
Expand Down
6 changes: 2 additions & 4 deletions pkg/rpc/core/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,9 @@ func TestTxSearch(t *testing.T) {
require.NotNil(q)
}).Return(searchResults, nil).Once()
mockStore.On("GetBlockData", mock.Anything, uint64(res1.Height)).Return(nil,
&rktypes.Data{Txs: rktypes.Txs{[]byte{0}, []byte{1}}}, nil).Once()
&rktypes.Data{Txs: rktypes.Txs{[]byte{0}, []byte{1}}}, nil).Twice()
mockStore.On("GetBlockData", mock.Anything, uint64(res2.Height)).Return(nil,
&rktypes.Data{Txs: rktypes.Txs{[]byte{2}}}, nil).Once()
mockStore.On("GetBlockData", mock.Anything, uint64(res3.Height)).Return(nil,
&rktypes.Data{Txs: rktypes.Txs{[]byte{3}, []byte{4}, []byte{5}}}, nil).Once()

result, err := TxSearch(ctx, query, true, &defaultPage, &defaultPerPage, orderBy)

Expand All @@ -263,7 +261,7 @@ func TestTxSearch(t *testing.T) {
assert.Equal(int64(10), result.Txs[1].Height)
assert.Equal(uint32(1), result.Txs[1].Index)
assert.Equal(tx1.Hash(), []byte(result.Txs[1].Hash))
assert.Equal(int64(3), result.Txs[1].Proof.Proof.Total)
assert.Equal(int64(2), result.Txs[1].Proof.Proof.Total)
assert.Equal(int64(1), result.Txs[1].Proof.Proof.Index)
assert.NotEmpty(result.Txs[1].Proof.Proof.LeafHash)

Expand Down
Loading