@@ -22,8 +22,10 @@ import (
2222 "fmt"
2323 "sync"
2424
25+ "github.com/ava-labs/libevm/common"
2526 "github.com/ava-labs/libevm/core"
2627 "github.com/ava-labs/libevm/core/types"
28+ "github.com/ava-labs/libevm/core/vm"
2729)
2830
2931// A Handler is responsible for processing [types.Transactions] in an
@@ -47,21 +49,28 @@ type Processor[R any] struct {
4749 handler Handler [R ]
4850 workers sync.WaitGroup
4951 work chan * job
50- results [](chan * R )
52+ results [](chan result [R ])
53+ txGas map [common.Hash ]uint64
5154}
5255
5356type job struct {
5457 index int
5558 tx * types.Transaction
5659}
5760
61+ type result [T any ] struct {
62+ tx common.Hash
63+ val * T
64+ }
65+
5866// New constructs a new [Processor] with the specified number of concurrent
5967// workers. [Processor.Close] must be called after the final call to
6068// [Processor.FinishBlock] to avoid leaking goroutines.
6169func New [R any ](h Handler [R ], workers int ) * Processor [R ] {
6270 p := & Processor [R ]{
6371 handler : h ,
6472 work : make (chan * job ),
73+ txGas : make (map [common.Hash ]uint64 ),
6574 }
6675
6776 workers = max (workers , 1 )
@@ -81,7 +90,10 @@ func (p *Processor[R]) worker() {
8190 }
8291
8392 r := p .handler .Process (w .index , w .tx )
84- p .results [w .index ] <- & r
93+ p .results [w .index ] <- result [R ]{
94+ tx : w .tx .Hash (),
95+ val : & r ,
96+ }
8597 }
8698}
8799
@@ -101,7 +113,7 @@ func (p *Processor[R]) StartBlock(b *types.Block) error {
101113 // We can reuse the channels already in the results slice because they're
102114 // emptied by [Processor.FinishBlock].
103115 for i , n := len (p .results ), len (txs ); i < n ; i ++ {
104- p .results = append (p .results , make (chan * R , 1 ))
116+ p .results = append (p .results , make (chan result [ R ] , 1 ))
105117 }
106118
107119 for i , tx := range txs {
@@ -116,7 +128,10 @@ func (p *Processor[R]) StartBlock(b *types.Block) error {
116128 })
117129
118130 default :
119- p .results [i ] <- nil
131+ p .results [i ] <- result [R ]{
132+ tx : tx .Hash (),
133+ val : nil ,
134+ }
120135 }
121136 }
122137
@@ -138,7 +153,7 @@ func (p *Processor[R]) FinishBlock(b *types.Block) {
138153 // Every result channel is guaranteed to have some value in its buffer
139154 // because [Processor.BeforeBlock] either sends a nil *R or it
140155 // dispatches a job that will send a non-nil *R.
141- <- p .results [i ]
156+ delete ( p . txGas , ( <- p .results [i ]). tx )
142157 }
143158}
144159
@@ -147,27 +162,38 @@ func (p *Processor[R]) FinishBlock(b *types.Block) {
147162// [Handler]. The returned boolean will be false if no processing occurred,
148163// either because the [Handler] indicated as such or because the transaction
149164// supplied insufficient gas.
165+ //
166+ // Multiple calls to Result with the same argument are allowed. Callers MUST NOT
167+ // charge the gas price for preprocessing as this is handled by
168+ // [Processor.PreprocessingGasCharge] if registered as a [vm.Preprocessor].
169+ // The same value will be returned by each call with the same argument, such
170+ // that if R is a pointer then modifications will persist between calls.
150171func (p * Processor [R ]) Result (i int ) (R , bool ) {
151172 ch := p .results [i ]
152- r := <- ch
173+ r := ( <- ch )
153174 defer func () {
154175 ch <- r
155176 }()
156177
157- if r == nil {
178+ if r . val == nil {
158179 // TODO(arr4n) if we're here then the implementoor might have a bug in
159180 // their [Handler], so logging a warning is probably a good idea.
160181 var zero R
161182 return zero , false
162183 }
163- return * r , true
184+ return * r . val , true
164185}
165186
166- func (p * Processor [R ]) shouldProcess (tx * types.Transaction ) (bool , error ) {
187+ func (p * Processor [R ]) shouldProcess (tx * types.Transaction ) (ok bool , err error ) {
167188 cost , ok := p .handler .Gas (tx )
168189 if ! ok {
169190 return false , nil
170191 }
192+ defer func () {
193+ if ok && err == nil {
194+ p .txGas [tx .Hash ()] = cost
195+ }
196+ }()
171197
172198 spent , err := core .IntrinsicGas (
173199 tx .Data (),
@@ -186,3 +212,11 @@ func (p *Processor[R]) shouldProcess(tx *types.Transaction) (bool, error) {
186212 left := tx .Gas () - spent
187213 return left >= cost , nil
188214}
215+
216+ var _ vm.Preprocessor = (* Processor [struct {}])(nil )
217+
218+ // PreprocessingGasCharge implements the [vm.Preprocessor] interface and MUST be
219+ // registered via [vm.RegisterHooks] to ensure proper gas accounting.
220+ func (p * Processor [R ]) PreprocessingGasCharge (tx common.Hash ) uint64 {
221+ return p .txGas [tx ]
222+ }
0 commit comments