Skip to content

Commit a3a1c1f

Browse files
feat: fix payload provider (#2368)
Required for evstack/ev-abci#101 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a customizable mechanism for generating signature payloads for headers, enabling more flexible signature verification. - **Bug Fixes** - Improved error handling when setting custom signature verifiers on headers. - **Chores** - Updated function signatures and related calls throughout the codebase to support the new signature payload provider. - Minor improvements to code comments and permission literal formatting in tests. - Added new optional parameters to node startup commands and tests for enhanced configuration flexibility. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Julien Robert <julien@rbrt.fr>
1 parent d98706b commit a3a1c1f

14 files changed

Lines changed: 118 additions & 64 deletions

File tree

apps/evm/based/cmd/run.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ func NewExtendedRunNodeCmd(ctx context.Context) *cobra.Command {
9696
return nil
9797
},
9898
RunE: func(cmd *cobra.Command, args []string) error {
99-
10099
nodeConfig, err := rollcmd.ParseConfig(cmd)
101100
if err != nil {
102101
return fmt.Errorf("failed to parse config: %w", err)
@@ -173,7 +172,7 @@ func NewExtendedRunNodeCmd(ctx context.Context) *cobra.Command {
173172
// StartNode might need adjustment if it strictly requires coreda.Client methods.
174173
// For now, assume it can work with coreda.DA or will be adjusted later.
175174
// We also need to pass the namespace config for rollDA.
176-
return rollcmd.StartNode(logger, cmd, executor, sequencer, rollDA, p2pClient, datastore, nodeConfig)
175+
return rollcmd.StartNode(logger, cmd, executor, sequencer, rollDA, p2pClient, datastore, nodeConfig, nil)
177176
},
178177
}
179178

apps/evm/single/cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ var RunCmd = &cobra.Command{
8989
return err
9090
}
9191

92-
return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig)
92+
return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, nil)
9393
},
9494
}
9595

apps/testapp/cmd/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ var RunCmd = &cobra.Command{
106106
return err
107107
}
108108

109-
return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig)
109+
return rollcmd.StartNode(logger, cmd, executor, sequencer, &daJrpc.DA, p2pClient, datastore, nodeConfig, nil)
110110
},
111111
}

block/manager.go

Lines changed: 70 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ var (
6969
// This allows for overriding the behavior in tests.
7070
type publishBlockFunc func(ctx context.Context) error
7171

72+
func defaultSignaturePayloadProvider(header *types.Header) ([]byte, error) {
73+
return header.MarshalBinary()
74+
}
75+
7276
// NewHeaderEvent is used to pass header and DA height to headerInCh
7377
type NewHeaderEvent struct {
7478
Header *types.SignedHeader
@@ -165,10 +169,18 @@ type Manager struct {
165169
// dataCommitmentToHeight tracks the height a data commitment (data hash) has been seen on.
166170
// Key: data commitment (string), Value: uint64 (height)
167171
dataCommitmentToHeight sync.Map
172+
173+
// signaturePayloadProvider is used to provide a signature payload for the header.
174+
// It is used to sign the header with the provided signer.
175+
signaturePayloadProvider types.SignaturePayloadProvider
168176
}
169177

170178
// getInitialState tries to load lastState from Store, and if it's not available it reads genesis.
171-
func getInitialState(ctx context.Context, genesis genesis.Genesis, signer signer.Signer, store store.Store, exec coreexecutor.Executor, logger log.Logger) (types.State, error) {
179+
func getInitialState(ctx context.Context, genesis genesis.Genesis, signer signer.Signer, store store.Store, exec coreexecutor.Executor, logger log.Logger, signaturePayloadProvider types.SignaturePayloadProvider) (types.State, error) {
180+
if signaturePayloadProvider == nil {
181+
signaturePayloadProvider = defaultSignaturePayloadProvider
182+
}
183+
172184
// Load the state from store.
173185
s, err := store.GetState(ctx)
174186

@@ -206,28 +218,33 @@ func getInitialState(ctx context.Context, genesis genesis.Genesis, signer signer
206218
return types.State{}, fmt.Errorf("failed to get public key: %w", err)
207219
}
208220

209-
b, err := header.MarshalBinary()
221+
b, err := signaturePayloadProvider(&header)
210222
if err != nil {
211-
return types.State{}, err
223+
return types.State{}, fmt.Errorf("failed to get signature payload: %w", err)
212224
}
213225
signature, err = signer.Sign(b)
214226
if err != nil {
215227
return types.State{}, fmt.Errorf("failed to get header signature: %w", err)
216228
}
217229
}
218230

219-
err = store.SaveBlockData(ctx,
220-
&types.SignedHeader{
221-
Header: header,
222-
Signer: types.Signer{
223-
PubKey: pubKey,
224-
Address: genesis.ProposerAddress,
225-
},
226-
Signature: signature,
231+
genesisHeader := &types.SignedHeader{
232+
Header: header,
233+
Signer: types.Signer{
234+
PubKey: pubKey,
235+
Address: genesis.ProposerAddress,
227236
},
228-
&types.Data{},
229-
&signature,
230-
)
237+
Signature: signature,
238+
}
239+
240+
// Set the same custom verifier used during normal block validation
241+
if err := genesisHeader.SetCustomVerifier(func(h *types.Header) ([]byte, error) {
242+
return signaturePayloadProvider(h)
243+
}); err != nil {
244+
return types.State{}, fmt.Errorf("failed to set custom verifier for genesis header: %w", err)
245+
}
246+
247+
err = store.SaveBlockData(ctx, genesisHeader, &types.Data{}, &signature)
231248
if err != nil {
232249
return types.State{}, fmt.Errorf("failed to save genesis block: %w", err)
233250
}
@@ -275,8 +292,13 @@ func NewManager(
275292
seqMetrics *Metrics,
276293
gasPrice float64,
277294
gasMultiplier float64,
295+
signaturePayloadProvider types.SignaturePayloadProvider,
278296
) (*Manager, error) {
279-
s, err := getInitialState(ctx, genesis, signer, store, exec, logger)
297+
if signaturePayloadProvider == nil {
298+
signaturePayloadProvider = defaultSignaturePayloadProvider
299+
}
300+
301+
s, err := getInitialState(ctx, genesis, signer, store, exec, logger, signaturePayloadProvider)
280302
if err != nil {
281303
return nil, fmt.Errorf("failed to get initial state: %w", err)
282304
}
@@ -339,29 +361,30 @@ func NewManager(
339361
headerBroadcaster: headerBroadcaster,
340362
dataBroadcaster: dataBroadcaster,
341363
// channels are buffered to avoid blocking on input/output operations, buffer sizes are arbitrary
342-
headerInCh: make(chan NewHeaderEvent, eventInChLength),
343-
dataInCh: make(chan NewDataEvent, eventInChLength),
344-
headerStoreCh: make(chan struct{}, 1),
345-
dataStoreCh: make(chan struct{}, 1),
346-
headerStore: headerStore,
347-
dataStore: dataStore,
348-
lastStateMtx: new(sync.RWMutex),
349-
lastBatchData: lastBatchData,
350-
headerCache: cache.NewCache[types.SignedHeader](),
351-
dataCache: cache.NewCache[types.Data](),
352-
retrieveCh: make(chan struct{}, 1),
353-
daIncluderCh: make(chan struct{}, 1),
354-
logger: logger,
355-
txsAvailable: false,
356-
pendingHeaders: pendingHeaders,
357-
metrics: seqMetrics,
358-
sequencer: sequencer,
359-
exec: exec,
360-
da: da,
361-
gasPrice: gasPrice,
362-
gasMultiplier: gasMultiplier,
363-
txNotifyCh: make(chan struct{}, 1), // Non-blocking channel
364-
batchSubmissionChan: make(chan coresequencer.Batch, eventInChLength),
364+
headerInCh: make(chan NewHeaderEvent, eventInChLength),
365+
dataInCh: make(chan NewDataEvent, eventInChLength),
366+
headerStoreCh: make(chan struct{}, 1),
367+
dataStoreCh: make(chan struct{}, 1),
368+
headerStore: headerStore,
369+
dataStore: dataStore,
370+
lastStateMtx: new(sync.RWMutex),
371+
lastBatchData: lastBatchData,
372+
headerCache: cache.NewCache[types.SignedHeader](),
373+
dataCache: cache.NewCache[types.Data](),
374+
retrieveCh: make(chan struct{}, 1),
375+
daIncluderCh: make(chan struct{}, 1),
376+
logger: logger,
377+
txsAvailable: false,
378+
pendingHeaders: pendingHeaders,
379+
metrics: seqMetrics,
380+
sequencer: sequencer,
381+
exec: exec,
382+
da: da,
383+
gasPrice: gasPrice,
384+
gasMultiplier: gasMultiplier,
385+
txNotifyCh: make(chan struct{}, 1), // Non-blocking channel
386+
batchSubmissionChan: make(chan coresequencer.Batch, eventInChLength),
387+
signaturePayloadProvider: signaturePayloadProvider,
365388
}
366389

367390
// initialize da included height
@@ -611,6 +634,14 @@ func (m *Manager) publishBlockInternal(ctx context.Context) error {
611634
// set the signature to current block's signed header
612635
header.Signature = signature
613636

637+
// Set the custom verifier to ensure proper signature validation (if not already set by executor)
638+
// Note: The executor may have already set a custom verifier during transaction execution
639+
if err := header.SetCustomVerifier(func(h *types.Header) ([]byte, error) {
640+
return m.signaturePayloadProvider(h)
641+
}); err != nil {
642+
return fmt.Errorf("failed to set custom verifier: %w", err)
643+
}
644+
614645
if err := header.ValidateBasic(); err != nil {
615646
// If this ever happens, for recovery, check for a mismatch between the configured signing key and the proposer address in the genesis file
616647
return fmt.Errorf("header validation error: %w", err)
@@ -914,7 +945,7 @@ func bytesToBatchData(data []byte) ([][]byte, error) {
914945
}
915946

916947
func (m *Manager) getHeaderSignature(header types.Header) (types.Signature, error) {
917-
b, err := header.MarshalBinary()
948+
b, err := m.signaturePayloadProvider(&header)
918949
if err != nil {
919950
return nil, err
920951
}

block/manager_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func TestInitialStateClean(t *testing.T) {
7474
mockExecutor.On("InitChain", ctx, genesisData.GenesisDAStartTime, genesisData.InitialHeight, genesisData.ChainID).
7575
Return([]byte("mockAppHash"), uint64(1000), nil).Once()
7676

77-
s, err := getInitialState(ctx, genesisData, nil, emptyStore, mockExecutor, logger)
77+
s, err := getInitialState(ctx, genesisData, nil, emptyStore, mockExecutor, logger, nil /* uses default signature verification */)
7878
require.NoError(err)
7979
initialHeight := genesisData.InitialHeight
8080
require.Equal(initialHeight-1, s.LastBlockHeight)
@@ -105,7 +105,7 @@ func TestInitialStateStored(t *testing.T) {
105105
mockExecutor := mocks.NewExecutor(t)
106106

107107
// getInitialState should not call InitChain if state exists
108-
s, err := getInitialState(ctx, genesisData, nil, store, mockExecutor, logger)
108+
s, err := getInitialState(ctx, genesisData, nil, store, mockExecutor, logger, nil /* uses default signature verification */)
109109
require.NoError(err)
110110
require.Equal(s.LastBlockHeight, uint64(100))
111111
require.Equal(s.InitialHeight, uint64(1))
@@ -140,7 +140,7 @@ func TestInitialStateUnexpectedHigherGenesis(t *testing.T) {
140140
require.NoError(err)
141141
mockExecutor := mocks.NewExecutor(t)
142142

143-
_, err = getInitialState(ctx, genesis, nil, store, mockExecutor, logger)
143+
_, err = getInitialState(ctx, genesis, nil, store, mockExecutor, logger, nil /* uses default signature verification */)
144144
require.EqualError(err, "genesis.InitialHeight (2) is greater than last stored state's LastBlockHeight (0)")
145145

146146
// Assert mock expectations (InitChain should not have been called)

block/publish_block2_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ func setupBlockManager(t *testing.T, ctx context.Context, workDir string, mainKV
212212
NopMetrics(),
213213
1.,
214214
1.,
215+
nil, // using default signature verification
215216
)
216217
require.NoError(t, err)
217218
return result, headerSyncService, dataSyncService

block/publish_block_test.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,14 +78,15 @@ func setupManagerForPublishBlockTest(
7878
dataBroadcaster: broadcasterFn[*types.Data](func(ctx context.Context, payload *types.Data) error {
7979
return nil
8080
}),
81-
headerStore: headerStore,
82-
daHeight: &atomic.Uint64{},
83-
dataStore: dataStore,
84-
headerCache: cache.NewCache[types.SignedHeader](),
85-
dataCache: cache.NewCache[types.Data](),
86-
lastStateMtx: &sync.RWMutex{},
87-
metrics: NopMetrics(),
88-
pendingHeaders: nil,
81+
headerStore: headerStore,
82+
daHeight: &atomic.Uint64{},
83+
dataStore: dataStore,
84+
headerCache: cache.NewCache[types.SignedHeader](),
85+
dataCache: cache.NewCache[types.Data](),
86+
lastStateMtx: &sync.RWMutex{},
87+
metrics: NopMetrics(),
88+
pendingHeaders: nil,
89+
signaturePayloadProvider: defaultSignaturePayloadProvider,
8990
}
9091
manager.publishBlock = manager.publishBlockInternal
9192

@@ -168,8 +169,9 @@ func Test_publishBlock_NoBatch(t *testing.T) {
168169
store: mockStore,
169170
logger: logger,
170171
},
171-
lastStateMtx: &sync.RWMutex{},
172-
metrics: NopMetrics(),
172+
lastStateMtx: &sync.RWMutex{},
173+
metrics: NopMetrics(),
174+
signaturePayloadProvider: defaultSignaturePayloadProvider,
173175
}
174176

175177
m.publishBlock = m.publishBlockInternal
@@ -267,7 +269,8 @@ func Test_publishBlock_EmptyBatch(t *testing.T) {
267269
dataBroadcaster: broadcasterFn[*types.Data](func(ctx context.Context, payload *types.Data) error {
268270
return nil
269271
}),
270-
daHeight: &daH,
272+
daHeight: &daH,
273+
signaturePayloadProvider: defaultSignaturePayloadProvider,
271274
}
272275

273276
m.publishBlock = m.publishBlockInternal

node/full.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"github.com/rollkit/rollkit/pkg/signer"
3030
"github.com/rollkit/rollkit/pkg/store"
3131
rollkitsync "github.com/rollkit/rollkit/pkg/sync"
32+
"github.com/rollkit/rollkit/types"
3233
)
3334

3435
// prefixes used in KV store to separate rollkit data from execution environment data (if the same data base is reused)
@@ -80,6 +81,7 @@ func newFullNode(
8081
da coreda.DA,
8182
metricsProvider MetricsProvider,
8283
logger log.Logger,
84+
signaturePayloadProvider types.SignaturePayloadProvider,
8385
) (fn *FullNode, err error) {
8486
seqMetrics, _ := metricsProvider(genesis.ChainID)
8587

@@ -111,6 +113,7 @@ func newFullNode(
111113
seqMetrics,
112114
nodeConfig.DA.GasPrice,
113115
nodeConfig.DA.GasMultiplier,
116+
signaturePayloadProvider,
114117
)
115118
if err != nil {
116119
return nil, err
@@ -197,6 +200,7 @@ func initBlockManager(
197200
seqMetrics *block.Metrics,
198201
gasPrice float64,
199202
gasMultiplier float64,
203+
signaturePayloadProvider types.SignaturePayloadProvider,
200204
) (*block.Manager, error) {
201205
logger.Debug("Proposer address", "address", genesis.ProposerAddress)
202206

@@ -217,6 +221,7 @@ func initBlockManager(
217221
seqMetrics,
218222
gasPrice,
219223
gasMultiplier,
224+
signaturePayloadProvider,
220225
)
221226
if err != nil {
222227
return nil, fmt.Errorf("error while initializing BlockManager: %w", err)

node/helpers_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ func newTestNode(
123123
ds,
124124
DefaultMetricsProvider(rollkitconfig.DefaultInstrumentationConfig()),
125125
log.NewTestLogger(t),
126+
nil,
126127
)
127128
require.NoError(t, err)
128129

@@ -187,6 +188,7 @@ func createNodesWithCleanup(t *testing.T, num int, config rollkitconfig.Config)
187188
ds,
188189
DefaultMetricsProvider(rollkitconfig.DefaultInstrumentationConfig()),
189190
log.NewTestLogger(t),
191+
nil,
190192
)
191193
require.NoError(err)
192194

@@ -224,6 +226,7 @@ func createNodesWithCleanup(t *testing.T, num int, config rollkitconfig.Config)
224226
dssync.MutexWrap(datastore.NewMapDatastore()),
225227
DefaultMetricsProvider(rollkitconfig.DefaultInstrumentationConfig()),
226228
log.NewTestLogger(t),
229+
nil,
227230
)
228231
require.NoError(err)
229232
// Update cleanup to cancel the context instead of calling Stop

node/node.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/rollkit/rollkit/pkg/p2p"
1515
"github.com/rollkit/rollkit/pkg/service"
1616
"github.com/rollkit/rollkit/pkg/signer"
17+
"github.com/rollkit/rollkit/types"
1718
)
1819

1920
// Node is the interface for an application node
@@ -38,6 +39,7 @@ func NewNode(
3839
database ds.Batching,
3940
metricsProvider MetricsProvider,
4041
logger log.Logger,
42+
signaturePayloadProvider types.SignaturePayloadProvider,
4143
) (Node, error) {
4244
if conf.Node.Light {
4345
return newLightNode(conf, genesis, p2pClient, database, logger)
@@ -55,5 +57,6 @@ func NewNode(
5557
da,
5658
metricsProvider,
5759
logger,
60+
signaturePayloadProvider,
5861
)
5962
}

0 commit comments

Comments
 (0)