@@ -14,6 +14,7 @@ import (
14
14
"github.com/filecoin-project/go-state-types/abi"
15
15
"github.com/filecoin-project/go-state-types/big"
16
16
"github.com/filecoin-project/go-state-types/crypto"
17
+ "github.com/filecoin-project/go-state-types/network"
17
18
18
19
"github.com/filecoin-project/lotus/api"
19
20
"github.com/filecoin-project/lotus/blockstore"
@@ -29,74 +30,7 @@ var ErrExpensiveFork = errors.New("refusing explicit call due to state fork at e
29
30
// Call applies the given message to the given tipset's parent state, at the epoch following the
30
31
// tipset's parent. In the presence of null blocks, the height at which the message is invoked may
31
32
// be less than the specified tipset.
32
- //
33
- // - If no tipset is specified, the first tipset without an expensive migration is used.
34
- // - If executing a message at a given tipset would trigger an expensive migration, the call will
35
- // fail with ErrExpensiveFork.
36
33
func (sm * StateManager ) Call (ctx context.Context , msg * types.Message , ts * types.TipSet ) (* api.InvocResult , error ) {
37
- ctx , span := trace .StartSpan (ctx , "statemanager.Call" )
38
- defer span .End ()
39
-
40
- var pheight abi.ChainEpoch = - 1
41
-
42
- // If no tipset is provided, try to find one without a fork.
43
- if ts == nil {
44
- ts = sm .cs .GetHeaviestTipSet ()
45
- // Search back till we find a height with no fork, or we reach the beginning.
46
- for ts .Height () > 0 {
47
- pts , err := sm .cs .GetTipSetFromKey (ctx , ts .Parents ())
48
- if err != nil {
49
- return nil , xerrors .Errorf ("failed to find a non-forking epoch: %w" , err )
50
- }
51
- if ! sm .hasExpensiveFork (pts .Height ()) {
52
- pheight = pts .Height ()
53
- break
54
- }
55
- ts = pts
56
- }
57
- } else if ts .Height () > 0 {
58
- pts , err := sm .cs .LoadTipSet (ctx , ts .Parents ())
59
- if err != nil {
60
- return nil , xerrors .Errorf ("failed to load parent tipset: %w" , err )
61
- }
62
- pheight = pts .Height ()
63
- if sm .hasExpensiveFork (pheight ) {
64
- return nil , ErrExpensiveFork
65
- }
66
- } else {
67
- // We can't get the parent tipset in this case.
68
- pheight = ts .Height () - 1
69
- }
70
-
71
- // Since we're simulating a future message, pretend we're applying it in the "next" tipset
72
- vmHeight := pheight + 1
73
- bstate := ts .ParentState ()
74
-
75
- // Run the (not expensive) migration.
76
- bstate , err := sm .HandleStateForks (ctx , bstate , pheight , nil , ts )
77
- if err != nil {
78
- return nil , fmt .Errorf ("failed to handle fork: %w" , err )
79
- }
80
-
81
- vmopt := & vm.VMOpts {
82
- StateBase : bstate ,
83
- Epoch : vmHeight ,
84
- Rand : rand .NewStateRand (sm .cs , ts .Cids (), sm .beacon , sm .GetNetworkVersion ),
85
- Bstore : sm .cs .StateBlockstore (),
86
- Actors : sm .tsExec .NewActorRegistry (),
87
- Syscalls : sm .Syscalls ,
88
- CircSupplyCalc : sm .GetVMCirculatingSupply ,
89
- NetworkVersion : sm .GetNetworkVersion (ctx , pheight + 1 ),
90
- BaseFee : types .NewInt (0 ),
91
- LookbackState : LookbackStateGetterForTipset (sm , ts ),
92
- Tracing : true ,
93
- }
94
-
95
- vmi , err := sm .newVM (ctx , vmopt )
96
- if err != nil {
97
- return nil , xerrors .Errorf ("failed to set up vm: %w" , err )
98
- }
99
-
100
34
if msg .GasLimit == 0 {
101
35
msg .GasLimit = build .BlockGasLimit
102
36
}
@@ -106,61 +40,43 @@ func (sm *StateManager) Call(ctx context.Context, msg *types.Message, ts *types.
106
40
if msg .GasPremium == types .EmptyInt {
107
41
msg .GasPremium = types .NewInt (0 )
108
42
}
109
-
110
43
if msg .Value == types .EmptyInt {
111
44
msg .Value = types .NewInt (0 )
112
45
}
113
46
114
- if span .IsRecordingEvents () {
115
- span .AddAttributes (
116
- trace .Int64Attribute ("gas_limit" , msg .GasLimit ),
117
- trace .StringAttribute ("gas_feecap" , msg .GasFeeCap .String ()),
118
- trace .StringAttribute ("value" , msg .Value .String ()),
119
- )
120
- }
121
-
122
- stTree , err := sm .StateTree (bstate )
123
- if err != nil {
124
- return nil , xerrors .Errorf ("failed to load state tree: %w" , err )
125
- }
126
-
127
- fromActor , err := stTree .GetActor (msg .From )
128
- if err != nil {
129
- return nil , xerrors .Errorf ("call raw get actor: %s" , err )
130
- }
131
-
132
- msg .Nonce = fromActor .Nonce
47
+ return sm .callInternal (ctx , msg , nil , ts , cid .Undef , sm .GetNetworkVersion , false )
48
+ }
133
49
134
- // TODO: maybe just use the invoker directly?
135
- ret , err := vmi .ApplyImplicitMessage (ctx , msg )
136
- if err != nil && ret == nil {
137
- return nil , xerrors .Errorf ("apply message failed: %w" , err )
138
- }
50
+ // CallWithGas calculates the state for a given tipset, and then applies the given message on top of that state.
51
+ func (sm * StateManager ) CallWithGas (ctx context.Context , msg * types.Message , priorMsgs []types.ChainMsg , ts * types.TipSet ) (* api.InvocResult , error ) {
52
+ return sm .callInternal (ctx , msg , priorMsgs , ts , cid .Undef , sm .GetNetworkVersion , true )
53
+ }
139
54
140
- var errs string
141
- if ret .ActorErr != nil {
142
- errs = ret .ActorErr .Error ()
143
- log .Warnf ("chain call failed: %s" , ret .ActorErr )
55
+ // CallAtStateAndVersion allows you to specify a message to execute on the given stateCid and network version.
56
+ // This should mostly be used for gas modelling on a migrated state.
57
+ // Tipset here is not needed because stateCid and network version fully describe execution we want. The internal function
58
+ // will get the heaviest tipset for use for things like basefee, which we don't really care about here.
59
+ func (sm * StateManager ) CallAtStateAndVersion (ctx context.Context , msg * types.Message , stateCid cid.Cid , v network.Version ) (* api.InvocResult , error ) {
60
+ nvGetter := func (context.Context , abi.ChainEpoch ) network.Version {
61
+ return v
144
62
}
145
63
146
- return & api.InvocResult {
147
- MsgCid : msg .Cid (),
148
- Msg : msg ,
149
- MsgRct : & ret .MessageReceipt ,
150
- ExecutionTrace : ret .ExecutionTrace ,
151
- Error : errs ,
152
- Duration : ret .Duration ,
153
- }, err
64
+ return sm .callInternal (ctx , msg , nil , nil , stateCid , nvGetter , true )
154
65
}
155
66
156
- func (sm * StateManager ) CallWithGas (ctx context.Context , msg * types.Message , priorMsgs []types.ChainMsg , ts * types.TipSet ) (* api.InvocResult , error ) {
157
- ctx , span := trace .StartSpan (ctx , "statemanager.CallWithGas" )
67
+ // - If no tipset is specified, the first tipset without an expensive migration or one in its parent is used.
68
+ // - If executing a message at a given tipset or its parent would trigger an expensive migration, the call will
69
+ // fail with ErrExpensiveFork.
70
+ func (sm * StateManager ) callInternal (ctx context.Context , msg * types.Message , priorMsgs []types.ChainMsg , ts * types.TipSet , stateCid cid.Cid , nvGetter rand.NetworkVersionGetter , checkGas bool ) (* api.InvocResult , error ) {
71
+ ctx , span := trace .StartSpan (ctx , "statemanager.callInternal" )
158
72
defer span .End ()
159
73
160
74
// Copy the message as we'll be modifying the nonce.
161
75
msgCopy := * msg
162
76
msg = & msgCopy
163
77
78
+ var err error
79
+ var pts * types.TipSet
164
80
if ts == nil {
165
81
ts = sm .cs .GetHeaviestTipSet ()
166
82
@@ -170,18 +86,19 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
170
86
// height to have no fork, because we'll run it inside this
171
87
// function before executing the given message.
172
88
for ts .Height () > 0 {
173
- pts , err : = sm .cs .GetTipSetFromKey (ctx , ts .Parents ())
89
+ pts , err = sm .cs .GetTipSetFromKey (ctx , ts .Parents ())
174
90
if err != nil {
175
91
return nil , xerrors .Errorf ("failed to find a non-forking epoch: %w" , err )
176
92
}
93
+ // Checks for expensive forks from the parents to the tipset, including nil tipsets
177
94
if ! sm .hasExpensiveForkBetween (pts .Height (), ts .Height ()+ 1 ) {
178
95
break
179
96
}
180
97
181
98
ts = pts
182
99
}
183
100
} else if ts .Height () > 0 {
184
- pts , err : = sm .cs .GetTipSetFromKey (ctx , ts .Parents ())
101
+ pts , err = sm .cs .GetTipSetFromKey (ctx , ts .Parents ())
185
102
if err != nil {
186
103
return nil , xerrors .Errorf ("failed to find a non-forking epoch: %w" , err )
187
104
}
@@ -190,12 +107,22 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
190
107
}
191
108
}
192
109
193
- // Since we're simulating a future message, pretend we're applying it in the "next" tipset
194
- vmHeight := ts .Height () + 1
195
-
196
- stateCid , _ , err := sm .TipSetState (ctx , ts )
197
- if err != nil {
198
- return nil , xerrors .Errorf ("computing tipset state: %w" , err )
110
+ var vmHeight abi.ChainEpoch
111
+ if checkGas {
112
+ // Since we're simulating a future message, pretend we're applying it in the "next" tipset
113
+ vmHeight = ts .Height () + 1
114
+ if stateCid == cid .Undef {
115
+ stateCid , _ , err = sm .TipSetState (ctx , ts )
116
+ if err != nil {
117
+ return nil , xerrors .Errorf ("computing tipset state: %w" , err )
118
+ }
119
+ }
120
+ } else {
121
+ // If we're not checking gas, we don't want to have to execute the tipset like above. This saves a lot of computation time
122
+ vmHeight = pts .Height () + 1
123
+ if stateCid == cid .Undef {
124
+ stateCid = ts .ParentState ()
125
+ }
199
126
}
200
127
201
128
// Technically, the tipset we're passing in here should be ts+1, but that may not exist.
@@ -204,8 +131,6 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
204
131
return nil , fmt .Errorf ("failed to handle fork: %w" , err )
205
132
}
206
133
207
- r := rand .NewStateRand (sm .cs , ts .Cids (), sm .beacon , sm .GetNetworkVersion )
208
-
209
134
if span .IsRecordingEvents () {
210
135
span .AddAttributes (
211
136
trace .Int64Attribute ("gas_limit" , msg .GasLimit ),
@@ -218,12 +143,12 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
218
143
vmopt := & vm.VMOpts {
219
144
StateBase : stateCid ,
220
145
Epoch : vmHeight ,
221
- Rand : r ,
146
+ Rand : rand . NewStateRand ( sm . cs , ts . Cids (), sm . beacon , nvGetter ) ,
222
147
Bstore : buffStore ,
223
148
Actors : sm .tsExec .NewActorRegistry (),
224
149
Syscalls : sm .Syscalls ,
225
150
CircSupplyCalc : sm .GetVMCirculatingSupply ,
226
- NetworkVersion : sm . GetNetworkVersion (ctx , ts . Height () + 1 ),
151
+ NetworkVersion : nvGetter (ctx , vmHeight ),
227
152
BaseFee : ts .Blocks ()[0 ].ParentBaseFee ,
228
153
LookbackState : LookbackStateGetterForTipset (sm , ts ),
229
154
Tracing : true ,
@@ -233,7 +158,7 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
233
158
return nil , xerrors .Errorf ("failed to set up vm: %w" , err )
234
159
}
235
160
for i , m := range priorMsgs {
236
- _ , err : = vmi .ApplyMessage (ctx , m )
161
+ _ , err = vmi .ApplyMessage (ctx , m )
237
162
if err != nil {
238
163
return nil , xerrors .Errorf ("applying prior message (%d, %s): %w" , i , m .Cid (), err )
239
164
}
@@ -258,27 +183,6 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
258
183
259
184
msg .Nonce = fromActor .Nonce
260
185
261
- fromKey , err := sm .ResolveToKeyAddress (ctx , msg .From , ts )
262
- if err != nil {
263
- return nil , xerrors .Errorf ("could not resolve key: %w" , err )
264
- }
265
-
266
- var msgApply types.ChainMsg
267
-
268
- switch fromKey .Protocol () {
269
- case address .BLS :
270
- msgApply = msg
271
- case address .SECP256K1 :
272
- msgApply = & types.SignedMessage {
273
- Message : * msg ,
274
- Signature : crypto.Signature {
275
- Type : crypto .SigTypeSecp256k1 ,
276
- Data : make ([]byte , 65 ),
277
- },
278
- }
279
-
280
- }
281
-
282
186
// If the fee cap is set to zero, make gas free.
283
187
if msg .GasFeeCap .NilOrZero () {
284
188
// Now estimate with a new VM with no base fee.
@@ -291,9 +195,39 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
291
195
}
292
196
}
293
197
294
- ret , err := vmi .ApplyMessage (ctx , msgApply )
295
- if err != nil {
296
- return nil , xerrors .Errorf ("gas estimation failed: %w" , err )
198
+ var ret * vm.ApplyRet
199
+ var gasInfo api.MsgGasCost
200
+ if checkGas {
201
+ fromKey , err := sm .ResolveToKeyAddress (ctx , msg .From , ts )
202
+ if err != nil {
203
+ return nil , xerrors .Errorf ("could not resolve key: %w" , err )
204
+ }
205
+
206
+ var msgApply types.ChainMsg
207
+
208
+ switch fromKey .Protocol () {
209
+ case address .BLS :
210
+ msgApply = msg
211
+ case address .SECP256K1 :
212
+ msgApply = & types.SignedMessage {
213
+ Message : * msg ,
214
+ Signature : crypto.Signature {
215
+ Type : crypto .SigTypeSecp256k1 ,
216
+ Data : make ([]byte , 65 ),
217
+ },
218
+ }
219
+ }
220
+
221
+ ret , err = vmi .ApplyMessage (ctx , msgApply )
222
+ if err != nil {
223
+ return nil , xerrors .Errorf ("gas estimation failed: %w" , err )
224
+ }
225
+ gasInfo = MakeMsgGasCost (msg , ret )
226
+ } else {
227
+ ret , err = vmi .ApplyImplicitMessage (ctx , msg )
228
+ if err != nil && ret == nil {
229
+ return nil , xerrors .Errorf ("apply message failed: %w" , err )
230
+ }
297
231
}
298
232
299
233
var errs string
@@ -305,11 +239,11 @@ func (sm *StateManager) CallWithGas(ctx context.Context, msg *types.Message, pri
305
239
MsgCid : msg .Cid (),
306
240
Msg : msg ,
307
241
MsgRct : & ret .MessageReceipt ,
308
- GasCost : MakeMsgGasCost ( msg , ret ) ,
242
+ GasCost : gasInfo ,
309
243
ExecutionTrace : ret .ExecutionTrace ,
310
244
Error : errs ,
311
245
Duration : ret .Duration ,
312
- }, nil
246
+ }, err
313
247
}
314
248
315
249
var errHaltExecution = fmt .Errorf ("halt" )
0 commit comments