Skip to content

Commit 686cd69

Browse files
committed
feat: integration of parallel.Processor with EVM.Call
1 parent 9780de0 commit 686cd69

File tree

8 files changed

+269
-10
lines changed

8 files changed

+269
-10
lines changed

core/vm/evm.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func (evm *EVM) Interpreter() *EVMInterpreter {
187187
// parameters. It also handles any necessary value transfer required and takes
188188
// the necessary steps to create accounts and reverses the state in case of an
189189
// execution error or failed value transfer.
190-
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
190+
func (evm *EVM) call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
191191
// Fail if we're trying to execute above the call depth limit
192192
if evm.depth > int(params.CallCreateDepth) {
193193
return nil, gas, ErrDepth

core/vm/evm.libevm.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/ava-labs/libevm/common"
2121
"github.com/ava-labs/libevm/libevm"
2222
"github.com/ava-labs/libevm/log"
23+
"github.com/holiman/uint256"
2324
)
2425

2526
// canCreateContract is a convenience wrapper for calling the
@@ -52,6 +53,29 @@ func (evm *EVM) canCreateContract(caller ContractRef, contractToCreate common.Ad
5253
return gas, err
5354
}
5455

56+
// Call executes the contract associated with the addr with the given input as
57+
// parameters. It also handles any necessary value transfer required and takes
58+
// the necessary steps to create accounts and reverses the state in case of an
59+
// execution error or failed value transfer.
60+
func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas uint64, value *uint256.Int) (ret []byte, leftOverGas uint64, err error) {
61+
gas, err = evm.spendPreprocessingGas(gas)
62+
if err != nil {
63+
return nil, gas, err
64+
}
65+
return evm.call(caller, addr, input, gas, value)
66+
}
67+
68+
func (evm *EVM) spendPreprocessingGas(gas uint64) (uint64, error) {
69+
if evm.depth > 0 || !libevmHooks.Registered() {
70+
return gas, nil
71+
}
72+
c := libevmHooks.Get().PreprocessingGasCharge(evm.StateDB.TxHash())
73+
if c > gas {
74+
return 0, ErrOutOfGas
75+
}
76+
return gas - c, nil
77+
}
78+
5579
// InvalidateExecution sets the error that will be returned by
5680
// [EVM.ExecutionInvalidated] for the length of the current transaction; i.e.
5781
// until [EVM.Reset] is called. This is honoured by state-transition logic to

core/vm/evm.libevm_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/stretchr/testify/assert"
2323
"github.com/stretchr/testify/require"
2424

25+
"github.com/ava-labs/libevm/common"
2526
"github.com/ava-labs/libevm/params"
2627
)
2728

@@ -46,6 +47,8 @@ func (o *evmArgOverrider) OverrideEVMResetArgs(r params.Rules, _ *EVMResetArgs)
4647
}
4748
}
4849

50+
func (o *evmArgOverrider) PreprocessingGasCharge(common.Hash) uint64 { return 0 }
51+
4952
func (o *evmArgOverrider) register(t *testing.T) {
5053
t.Helper()
5154
TestOnlyClearRegisteredHooks()

core/vm/hooks.libevm.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package vm
1818

1919
import (
20+
"github.com/ava-labs/libevm/common"
2021
"github.com/ava-labs/libevm/libevm/register"
2122
"github.com/ava-labs/libevm/params"
2223
)
@@ -40,6 +41,14 @@ var libevmHooks register.AtMostOnce[Hooks]
4041
type Hooks interface {
4142
OverrideNewEVMArgs(*NewEVMArgs) *NewEVMArgs
4243
OverrideEVMResetArgs(params.Rules, *EVMResetArgs) *EVMResetArgs
44+
Preprocessor
45+
}
46+
47+
// A Preprocessor performs computation on a transaction before the
48+
// [EVMInterpreter] is invoked and reports its gas charge for spending at the
49+
// beginning of [EVM.Call].
50+
type Preprocessor interface {
51+
PreprocessingGasCharge(tx common.Hash) uint64
4352
}
4453

4554
// NewEVMArgs are the arguments received by [NewEVM], available for override

core/vm/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ type StateDB interface {
8282

8383
AddLog(*types.Log)
8484
AddPreimage(common.Hash, []byte)
85+
86+
StateDBRemainder
8587
}
8688

8789
// CallContext provides a basic interface for the EVM calling conventions. The EVM

core/vm/interface.libevm.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
package vm
18+
19+
import "github.com/ava-labs/libevm/common"
20+
21+
// StateDBRemainder defines methods not included in the geth definition of
22+
// [StateDB] but present on the concrete type and exposed for libevm
23+
// functionality.
24+
type StateDBRemainder interface {
25+
TxHash() common.Hash
26+
TxIndex() int
27+
}

libevm/precompiles/parallel/parallel.go

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

5356
type 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.
6169
func 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.
150171
func (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

Comments
 (0)