diff --git a/pkg/adapter/adapter_test.go b/pkg/adapter/adapter_test.go index 3f55cb4..ecb9ed9 100644 --- a/pkg/adapter/adapter_test.go +++ b/pkg/adapter/adapter_test.go @@ -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) { @@ -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 @@ -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) @@ -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) diff --git a/pkg/common/convert.go b/pkg/common/convert.go new file mode 100644 index 0000000..0f71045 --- /dev/null +++ b/pkg/common/convert.go @@ -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" +) + +// 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 + } + 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, + }}, + } +} diff --git a/pkg/rpc/core/blocks.go b/pkg/rpc/core/blocks.go index b89dd59..e396076 100644 --- a/pkg/rpc/core/blocks.go +++ b/pkg/rpc/core/blocks.go @@ -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 @@ -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 } @@ -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 } @@ -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 } @@ -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 } @@ -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 } @@ -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 } diff --git a/pkg/rpc/core/tx_test.go b/pkg/rpc/core/tx_test.go index 94b1176..fe9c313 100644 --- a/pkg/rpc/core/tx_test.go +++ b/pkg/rpc/core/tx_test.go @@ -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) @@ -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) diff --git a/pkg/rpc/core/utils.go b/pkg/rpc/core/utils.go index d66ca7b..a9bd628 100644 --- a/pkg/rpc/core/utils.go +++ b/pkg/rpc/core/utils.go @@ -5,121 +5,14 @@ import ( "encoding/hex" "errors" "fmt" - "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" + "github.com/rollkit/go-execution-abci/pkg/common" ) const NodeIDByteLength = 20 -// 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 := getABCICommit(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 - } - 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 -} - -// 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 { - tmCommit := cmttypes.Commit{ - Height: int64(height), //nolint:gosec - Round: 0, - BlockID: cmttypes.BlockID{ - Hash: cmbytes.HexBytes(hash), - PartSetHeader: cmttypes.PartSetHeader{}, - }, - Signatures: make([]cmttypes.CommitSig, 1), - } - commitSig := cmttypes.CommitSig{ - BlockIDFlag: cmttypes.BlockIDFlagCommit, - Signature: signature, - ValidatorAddress: val, - Timestamp: time, - } - tmCommit.Signatures[0] = commitSig - - return &tmCommit -} - func normalizeHeight(height *int64) uint64 { var heightValue uint64 if height == nil { @@ -159,7 +52,7 @@ func getBlockMeta(ctx context.Context, n uint64) *cmttypes.BlockMeta { return nil } // Assuming ToABCIBlockMeta is now in pkg/rpc/provider/provider_utils.go - bmeta, err := ToABCIBlockMeta(header, data) // Removed rpc. prefix + bmeta, err := common.ToABCIBlockMeta(header, data) // Removed rpc. prefix if err != nil { env.Logger.Error("Failed to convert block to ABCI block meta", "height", n, "err", err) return nil @@ -199,29 +92,6 @@ func filterMinMax(base, height, mini, maxi, limit int64) (int64, int64, error) { return mini, maxi, nil } -// 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 rlktypes.Hash, val cmttypes.Address, time time.Time, signature rlktypes.Signature) *cmttypes.Commit { - tmCommit := cmttypes.Commit{ - Height: int64(height), //nolint:gosec - Round: 0, - BlockID: cmttypes.BlockID{ - Hash: cmbytes.HexBytes(hash), - PartSetHeader: cmttypes.PartSetHeader{}, - }, - Signatures: make([]cmttypes.CommitSig, 1), - } - commitSig := cmttypes.CommitSig{ - BlockIDFlag: cmttypes.BlockIDFlagCommit, - Signature: signature, - ValidatorAddress: val, - Timestamp: time, - } - tmCommit.Signatures[0] = commitSig - - return &tmCommit -} - // TruncateNodeID from rollkit we receive a 32 bytes node id, but we only need the first 20 bytes // to be compatible with the ABCI node info func TruncateNodeID(idStr string) (string, error) { diff --git a/pkg/rpc/core/utils_test.go b/pkg/rpc/core/utils_test.go index cb60a5d..8194654 100644 --- a/pkg/rpc/core/utils_test.go +++ b/pkg/rpc/core/utils_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestTruncateNodeID(t *testing.T) { @@ -21,7 +22,7 @@ func TestTruncateNodeID(t *testing.T) { expectedPrefix := fullNodeIDStr[:NodeIDByteLength*2] // First 20 bytes -> 40 chars truncatedNodeID, err := TruncateNodeID(fullNodeIDStr) - assert.NoError(t, err, "TruncateNodeID should not return an error for a valid ID") + require.NoError(t, err, "TruncateNodeID should not return an error for a valid ID") assert.Equal(t, NodeIDByteLength*2, len(truncatedNodeID), fmt.Sprintf("Truncated Node ID should be %d characters long", NodeIDByteLength*2)) assert.Equal(t, expectedPrefix, truncatedNodeID, "Truncated Node ID should be the first 20 bytes of the original ID") }) @@ -37,12 +38,9 @@ func TestTruncateNodeID(t *testing.T) { // Test case 3: Invalid hex string t.Run("invalid hex string", func(t *testing.T) { invalidHexStr := "not-a-hex-string-at-all-and-should-be-long-enough-to-pass-length-check" - // Ensure it would be long enough if it were hex - if len(invalidHexStr) < NodeIDByteLength*2 { - invalidHexStr = strings.Repeat("x", NodeIDByteLength*2) // Make it long enough but still invalid hex - } + require.GreaterOrEqual(t, len(invalidHexStr), NodeIDByteLength*2) _, err := TruncateNodeID(invalidHexStr) - assert.Error(t, err, "TruncateNodeID should return an error for an invalid hex string") + require.Error(t, err, "TruncateNodeID should return an error for an invalid hex string") assert.Contains(t, err.Error(), "failed to decode node ID", "Error message should indicate hex decoding failure") }) @@ -56,7 +54,7 @@ func TestTruncateNodeID(t *testing.T) { t.Run("exact length node ID", func(t *testing.T) { exactLengthNodeIDStr := strings.Repeat("a", NodeIDByteLength*2) // 40 chars truncatedNodeID, err := TruncateNodeID(exactLengthNodeIDStr) - assert.NoError(t, err, "TruncateNodeID should not return an error for an ID of exact length") + require.NoError(t, err, "TruncateNodeID should not return an error for an ID of exact length") assert.Equal(t, exactLengthNodeIDStr, truncatedNodeID, "Truncated Node ID should be the same as input for exact length") }) }