-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
driver.go
360 lines (316 loc) · 11.5 KB
/
driver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
package conformance
import (
"context"
gobig "math/big"
"os"
"github.com/ipfs/go-cid"
ds "github.com/ipfs/go-datastore"
cbor "github.com/ipfs/go-ipld-cbor"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/abi"
actorstypes "github.com/filecoin-project/go-state-types/actors"
"github.com/filecoin-project/go-state-types/big"
"github.com/filecoin-project/go-state-types/crypto"
"github.com/filecoin-project/go-state-types/network"
rtt "github.com/filecoin-project/go-state-types/rt"
"github.com/filecoin-project/test-vectors/schema"
"github.com/filecoin-project/lotus/blockstore"
"github.com/filecoin-project/lotus/chain/actors/builtin"
"github.com/filecoin-project/lotus/chain/consensus"
"github.com/filecoin-project/lotus/chain/consensus/filcns"
"github.com/filecoin-project/lotus/chain/index"
"github.com/filecoin-project/lotus/chain/rand"
"github.com/filecoin-project/lotus/chain/state"
"github.com/filecoin-project/lotus/chain/stmgr"
"github.com/filecoin-project/lotus/chain/store"
"github.com/filecoin-project/lotus/chain/types"
"github.com/filecoin-project/lotus/chain/vm"
"github.com/filecoin-project/lotus/conformance/chaos"
_ "github.com/filecoin-project/lotus/lib/sigs/bls" // enable bls signatures
_ "github.com/filecoin-project/lotus/lib/sigs/delegated"
_ "github.com/filecoin-project/lotus/lib/sigs/secp" // enable secp signatures
"github.com/filecoin-project/lotus/storage/sealer/ffiwrapper"
)
var (
// DefaultCirculatingSupply is the fallback circulating supply returned by
// the driver's CircSupplyCalculator function, used if the vector specifies
// no circulating supply.
DefaultCirculatingSupply = types.TotalFilecoinInt
// DefaultBaseFee to use in the VM, if one is not supplied in the vector.
DefaultBaseFee = abi.NewTokenAmount(100)
)
type Driver struct {
ctx context.Context
selector schema.Selector
vmFlush bool
}
type DriverOpts struct {
// DisableVMFlush, when true, avoids calling VM.Flush(), forces a blockstore
// recursive copy, from the temporary buffer blockstore, to the real
// system's blockstore. Disabling VM flushing is useful when extracting test
// vectors and trimming state, as we don't want to force an accidental
// deep copy of the state tree.
//
// Disabling VM flushing almost always should go hand-in-hand with
// LOTUS_DISABLE_VM_BUF=iknowitsabadidea. That way, state tree writes are
// immediately committed to the blockstore.
DisableVMFlush bool
}
func NewDriver(ctx context.Context, selector schema.Selector, opts DriverOpts) *Driver {
return &Driver{ctx: ctx, selector: selector, vmFlush: !opts.DisableVMFlush}
}
type ExecuteTipsetResult struct {
ReceiptsRoot cid.Cid
PostStateRoot cid.Cid
// AppliedMessages stores the messages that were applied, in the order they
// were applied. It includes implicit messages (cron, rewards).
AppliedMessages []*types.Message
// AppliedResults stores the results of AppliedMessages, in the same order.
AppliedResults []*vm.ApplyRet
// PostBaseFee returns the basefee after applying this tipset.
PostBaseFee abi.TokenAmount
}
type ExecuteTipsetParams struct {
Preroot cid.Cid
// ParentEpoch is the last epoch in which an actual tipset was processed. This
// is used by Lotus for null block counting and cron firing.
ParentEpoch abi.ChainEpoch
Tipset *schema.Tipset
ExecEpoch abi.ChainEpoch
// Rand is an optional rand.Rand implementation to use. If nil, the driver
// will use a rand.Rand that returns a fixed value for all calls.
Rand rand.Rand
// BaseFee if not nil or zero, will override the basefee of the tipset.
BaseFee abi.TokenAmount
}
// ExecuteTipset executes the supplied tipset on top of the state represented
// by the preroot CID.
//
// This method returns the the receipts root, the poststate root, and the VM
// message results. The latter _include_ implicit messages, such as cron ticks
// and reward withdrawal per miner.
func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params ExecuteTipsetParams) (*ExecuteTipsetResult, error) {
var (
tipset = params.Tipset
syscalls = vm.Syscalls(ffiwrapper.ProofVerifier)
cs = store.NewChainStore(bs, bs, ds, filcns.Weight, nil)
tse = consensus.NewTipSetExecutor(filcns.RewardFunc)
sm, err = stmgr.NewStateManager(cs, tse, syscalls, filcns.DefaultUpgradeSchedule(), nil, ds, index.DummyMsgIndex)
)
if err != nil {
return nil, err
}
if params.Rand == nil {
params.Rand = NewFixedRand()
}
if params.BaseFee.NilOrZero() {
params.BaseFee = abi.NewTokenAmount(tipset.BaseFee.Int64())
}
defer cs.Close() //nolint:errcheck
blocks := make([]consensus.FilecoinBlockMessages, 0, len(tipset.Blocks))
for _, b := range tipset.Blocks {
sb := store.BlockMessages{
Miner: b.MinerAddr,
}
for _, m := range b.Messages {
msg, err := types.DecodeMessage(m)
if err != nil {
return nil, err
}
switch msg.From.Protocol() {
case address.SECP256K1:
sb.SecpkMessages = append(sb.SecpkMessages, toChainMsg(msg))
case address.BLS:
sb.BlsMessages = append(sb.BlsMessages, toChainMsg(msg))
default:
// sneak in messages originating from other addresses as both kinds.
// these should fail, as they are actually invalid senders.
sb.SecpkMessages = append(sb.SecpkMessages, msg)
sb.BlsMessages = append(sb.BlsMessages, msg)
}
}
blocks = append(blocks, consensus.FilecoinBlockMessages{
BlockMessages: sb,
WinCount: b.WinCount,
})
}
recordOutputs := &outputRecorder{
messages: []*types.Message{},
results: []*vm.ApplyRet{},
}
sm.SetVMConstructor(func(ctx context.Context, vmopt *vm.VMOpts) (vm.Interface, error) {
vmopt.CircSupplyCalc = func(context.Context, abi.ChainEpoch, *state.StateTree) (abi.TokenAmount, error) {
return big.Zero(), nil
}
return vm.NewVM(ctx, vmopt)
})
postcid, receiptsroot, err := tse.ApplyBlocks(context.Background(),
sm,
params.ParentEpoch,
params.Preroot,
blocks,
params.ExecEpoch,
params.Rand,
recordOutputs,
true,
params.BaseFee,
nil,
)
if err != nil {
return nil, err
}
ret := &ExecuteTipsetResult{
ReceiptsRoot: receiptsroot,
PostStateRoot: postcid,
AppliedMessages: recordOutputs.messages,
AppliedResults: recordOutputs.results,
}
return ret, nil
}
type ExecuteMessageParams struct {
Preroot cid.Cid
Epoch abi.ChainEpoch
Message *types.Message
CircSupply abi.TokenAmount
BaseFee abi.TokenAmount
NetworkVersion network.Version
// Rand is an optional rand.Rand implementation to use. If nil, the driver
// will use a rand.Rand that returns a fixed value for all calls.
Rand rand.Rand
// Lookback is the LookbackStateGetter; returns the state tree at a given epoch.
Lookback vm.LookbackStateGetter
// TipSetGetter returns the tipset key at any given epoch.
TipSetGetter vm.TipSetGetter
}
// ExecuteMessage executes a conformance test vector message in a temporary VM.
func (d *Driver) ExecuteMessage(bs blockstore.Blockstore, params ExecuteMessageParams) (*vm.ApplyRet, cid.Cid, error) {
if !d.vmFlush {
// do not flush the VM, just the state tree; this should be used with
// LOTUS_DISABLE_VM_BUF enabled, so writes will anyway be visible.
_ = os.Setenv("LOTUS_DISABLE_VM_BUF", "iknowitsabadidea")
}
if params.Rand == nil {
params.Rand = NewFixedRand()
}
if params.TipSetGetter == nil {
// TODO: If/when we start writing conformance tests against the EVM, we'll need to
// actually implement this and (unfortunately) capture any tipsets looked up by
// messages.
params.TipSetGetter = func(context.Context, abi.ChainEpoch) (types.TipSetKey, error) {
return types.EmptyTSK, nil
}
}
if params.Lookback == nil {
// TODO: This lookback state returns the supplied precondition state tree, unconditionally.
// This is obviously not correct, but the lookback state tree is only used to validate the
// worker key when verifying a consensus fault. If the worker key hasn't changed in the
// current finality window, this workaround is enough.
// The correct solutions are documented in https://github.com/filecoin-project/ref-fvm/issues/381,
// but they're much harder to implement, and the tradeoffs aren't clear.
params.Lookback = func(ctx context.Context, epoch abi.ChainEpoch) (*state.StateTree, error) {
cst := cbor.NewCborStore(bs)
return state.LoadStateTree(cst, params.Preroot)
}
}
vmOpts := &vm.VMOpts{
StateBase: params.Preroot,
Epoch: params.Epoch,
Bstore: bs,
Syscalls: vm.Syscalls(ffiwrapper.ProofVerifier),
CircSupplyCalc: func(_ context.Context, _ abi.ChainEpoch, _ *state.StateTree) (abi.TokenAmount, error) {
return params.CircSupply, nil
},
Rand: params.Rand,
BaseFee: params.BaseFee,
NetworkVersion: params.NetworkVersion,
LookbackState: params.Lookback,
TipSetGetter: params.TipSetGetter,
}
var vmi vm.Interface
if chaosOn, ok := d.selector["chaos_actor"]; ok && chaosOn == "true" {
lvm, err := vm.NewLegacyVM(context.TODO(), vmOpts)
if err != nil {
return nil, cid.Undef, err
}
invoker := consensus.NewActorRegistry()
av, _ := actorstypes.VersionForNetwork(params.NetworkVersion)
registry := builtin.MakeRegistryLegacy([]rtt.VMActor{chaos.Actor{}})
invoker.Register(av, nil, registry)
lvm.SetInvoker(invoker)
vmi = lvm
} else {
if vmOpts.NetworkVersion >= network.Version16 {
fvm, err := vm.NewFVM(context.TODO(), vmOpts)
if err != nil {
return nil, cid.Undef, err
}
vmi = fvm
} else {
lvm, err := vm.NewLegacyVM(context.TODO(), vmOpts)
if err != nil {
return nil, cid.Undef, err
}
invoker := consensus.NewActorRegistry()
lvm.SetInvoker(invoker)
vmi = lvm
}
}
ret, err := vmi.ApplyMessage(d.ctx, toChainMsg(params.Message))
if err != nil {
return nil, cid.Undef, err
}
var root cid.Cid
if lvm, ok := vmi.(*vm.LegacyVM); ok && !d.vmFlush {
root, err = lvm.StateTree().(*state.StateTree).Flush(d.ctx)
} else {
// flush the VM, committing the state tree changes and forcing a
// recursive copy from the temporary blockstore to the real blockstore.
root, err = vmi.Flush(d.ctx)
}
return ret, root, err
}
// toChainMsg injects a synthetic 0-filled signature of the right length to
// messages that originate from secp256k senders, leaving all
// others untouched.
// TODO: generate a signature in the DSL so that it's encoded in
//
// the test vector.
func toChainMsg(msg *types.Message) (ret types.ChainMsg) {
ret = msg
if msg.From.Protocol() == address.SECP256K1 {
ret = &types.SignedMessage{
Message: *msg,
Signature: crypto.Signature{
Type: crypto.SigTypeSecp256k1,
Data: make([]byte, 65),
},
}
}
return ret
}
// BaseFeeOrDefault converts a basefee as passed in a test vector (go *big.Int
// type) to an abi.TokenAmount, or if nil it returns the DefaultBaseFee.
func BaseFeeOrDefault(basefee *gobig.Int) abi.TokenAmount {
if basefee == nil {
return DefaultBaseFee
}
return big.NewFromGo(basefee)
}
// CircSupplyOrDefault converts a circulating supply as passed in a test vector
// (go *big.Int type) to an abi.TokenAmount, or if nil it returns the
// DefaultCirculatingSupply.
func CircSupplyOrDefault(circSupply *gobig.Int) abi.TokenAmount {
if circSupply == nil {
return DefaultCirculatingSupply
}
return big.NewFromGo(circSupply)
}
type outputRecorder struct {
messages []*types.Message
results []*vm.ApplyRet
}
func (o *outputRecorder) MessageApplied(ctx context.Context, ts *types.TipSet, mcid cid.Cid, msg *types.Message, ret *vm.ApplyRet, implicit bool) error {
o.messages = append(o.messages, msg)
o.results = append(o.results, ret)
return nil
}