@@ -152,11 +152,12 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
152152 if height == s .genesis .InitialHeight {
153153 // For the first block, use genesis state.
154154 prevState = types.State {
155- ChainID : s .genesis .ChainID ,
156- InitialHeight : s .genesis .InitialHeight ,
157- LastBlockHeight : s .genesis .InitialHeight - 1 ,
158- LastBlockTime : s .genesis .StartTime ,
159- AppHash : header .AppHash , // Genesis app hash (input to first block execution)
155+ ChainID : s .genesis .ChainID ,
156+ InitialHeight : s .genesis .InitialHeight ,
157+ LastBlockHeight : s .genesis .InitialHeight - 1 ,
158+ LastBlockTime : s .genesis .StartTime ,
159+ AppHash : header .AppHash , // Genesis app hash (input to first block execution)
160+ NextProposerAddress : append ([]byte (nil ), s .genesis .ProposerAddress ... ),
160161 }
161162 } else {
162163 // GetStateAtHeight(height-1) returns the state AFTER block height-1 was executed,
@@ -179,10 +180,25 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
179180 Int ("tx_count" , len (rawTxs )).
180181 Msg ("executing transactions on execution layer" )
181182
182- newAppHash , err := s .exec .ExecuteTxs (ctx , rawTxs , height , header .Time (), prevState .AppHash )
183+ result , err := s .exec .ExecuteTxs (ctx , rawTxs , height , header .Time (), prevState .AppHash )
183184 if err != nil {
184185 return fmt .Errorf ("failed to execute transactions: %w" , err )
185186 }
187+ newAppHash := result .UpdatedStateRoot
188+ if len (result .NextProposerAddress ) > 0 {
189+ if len (header .NextProposerAddress ) == 0 {
190+ return fmt .Errorf ("next proposer mismatch at height %d: header empty, execution %x" , height , result .NextProposerAddress )
191+ }
192+ if ! bytes .Equal (header .NextProposerAddress , result .NextProposerAddress ) {
193+ return fmt .Errorf ("next proposer mismatch at height %d: header %x, execution %x" ,
194+ height ,
195+ header .NextProposerAddress ,
196+ result .NextProposerAddress ,
197+ )
198+ }
199+ } else if len (header .NextProposerAddress ) > 0 && ! bytes .Equal (header .NextProposerAddress , header .ProposerAddress ) {
200+ return fmt .Errorf ("next proposer mismatch at height %d: header %x, execution unchanged" , height , header .NextProposerAddress )
201+ }
186202
187203 // The result of ExecuteTxs (newAppHash) should match the stored state at this height.
188204 // Note: header.AppHash is the PREVIOUS state's app hash (input), not the expected output.
@@ -207,6 +223,22 @@ func (s *Replayer) replayBlock(ctx context.Context, height uint64) error {
207223 Msg ("app hash mismatch during replay" )
208224 return err
209225 }
226+ if len (expectedState .NextProposerAddress ) > 0 {
227+ expectedNextProposer := header .NextProposerAddress
228+ if len (expectedNextProposer ) == 0 {
229+ expectedNextProposer = result .NextProposerAddress
230+ }
231+ if len (expectedNextProposer ) == 0 {
232+ expectedNextProposer = header .ProposerAddress
233+ }
234+ if ! bytes .Equal (expectedNextProposer , expectedState .NextProposerAddress ) {
235+ return fmt .Errorf ("next proposer mismatch at height %d: expected %x got %x" ,
236+ height ,
237+ expectedState .NextProposerAddress ,
238+ expectedNextProposer ,
239+ )
240+ }
241+ }
210242 s .logger .Debug ().
211243 Uint64 ("height" , height ).
212244 Str ("app_hash" , hex .EncodeToString (newAppHash )).
0 commit comments