@@ -588,6 +588,25 @@ func (n *Node) runtimeExecuteTxBatch(
588588func (n * Node ) startProcessingBatch (ctx context.Context , proposal * commitment.Proposal , rank uint64 , batch transaction.RawBatch ) {
589589 // This method runs within its own goroutine and is always stopped before the runtime
590590 // worker finishes. Therefore, it is safe to read local round variables (block info, ...).
591+ n .logger .Debug ("verifying batch" )
592+
593+ ioRoot , err := n .computeIORoot (ctx , proposal .Header .Round , batch )
594+ if err != nil {
595+ n .logger .Error ("failed to compute I/O root" ,
596+ "err" , err ,
597+ )
598+ // Notify the round worker that the execution failed.
599+ n .processedBatchCh <- nil
600+ return
601+ }
602+ if ! ioRoot .Equal (& proposal .Header .BatchHash ) {
603+ n .logger .Debug ("batch I/O root mismatch" )
604+ n .proposals .Reject (proposal , rank )
605+ // Notify the round worker that the execution failed.
606+ n .processedBatchCh <- nil
607+ return
608+ }
609+
591610 n .logger .Debug ("processing batch" ,
592611 "batch_size" , len (batch ),
593612 )
@@ -624,6 +643,39 @@ func (n *Node) startProcessingBatch(ctx context.Context, proposal *commitment.Pr
624643 }
625644}
626645
646+ func (n * Node ) computeIORoot (ctx context.Context , round uint64 , batch transaction.RawBatch ) (hash.Hash , error ) {
647+ txs := make ([]* transaction.Transaction , 0 , len (batch ))
648+ for idx , tx := range batch {
649+ txs = append (txs , & transaction.Transaction {
650+ Input : tx ,
651+ BatchOrder : uint32 (idx ),
652+ })
653+ }
654+
655+ emptyRoot := storage.Root {
656+ Namespace : n .rt .ID (),
657+ Version : round ,
658+ Type : storage .RootTypeIO ,
659+ }
660+ emptyRoot .Hash .Empty ()
661+
662+ ioTree := transaction .NewTree (nil , emptyRoot )
663+ defer ioTree .Close ()
664+
665+ for _ , tx := range txs {
666+ if err := ioTree .AddTransaction (ctx , * tx , nil ); err != nil {
667+ return hash.Hash {}, fmt .Errorf ("failed to add transaction to tree: %w" , err )
668+ }
669+ }
670+
671+ _ , ioRoot , err := ioTree .Commit (ctx )
672+ if err != nil {
673+ return hash.Hash {}, fmt .Errorf ("failed to commit I/O tree: %w" , err )
674+ }
675+
676+ return ioRoot , nil
677+ }
678+
627679func (n * Node ) abortBatch (state * StateProcessingBatch ) {
628680 n .logger .Warn ("aborting processing batch" )
629681
0 commit comments