1
1
package lf3
2
2
3
3
import (
4
+ "bytes"
5
+ "compress/flate"
4
6
"context"
7
+ "encoding/binary"
8
+ "encoding/json"
5
9
"fmt"
10
+ "io"
11
+ "math"
6
12
"strings"
13
+ "time"
7
14
8
15
"github.com/ipfs/go-datastore"
9
16
"github.com/ipfs/go-datastore/namespace"
10
17
pubsub "github.com/libp2p/go-libp2p-pubsub"
18
+ "golang.org/x/sync/errgroup"
11
19
"golang.org/x/xerrors"
12
20
13
21
"github.com/filecoin-project/go-f3/ec"
22
+ "github.com/filecoin-project/go-f3/gpbft"
14
23
"github.com/filecoin-project/go-f3/manifest"
24
+ "github.com/filecoin-project/go-state-types/abi"
15
25
26
+ "github.com/filecoin-project/lotus/api"
16
27
"github.com/filecoin-project/lotus/build"
17
28
"github.com/filecoin-project/lotus/chain/store"
29
+ "github.com/filecoin-project/lotus/chain/types"
30
+ "github.com/filecoin-project/lotus/chain/types/ethtypes"
31
+ "github.com/filecoin-project/lotus/lib/must"
18
32
"github.com/filecoin-project/lotus/node/modules/dtypes"
19
33
"github.com/filecoin-project/lotus/node/modules/helpers"
20
34
)
@@ -35,12 +49,24 @@ func (hg *headGetter) GetHead(context.Context) (ec.TipSet, error) {
35
49
// message topic will be filtered
36
50
var MaxDynamicManifestChangesAllowed = 1000
37
51
38
- func NewManifestProvider (mctx helpers.MetricsCtx , config * Config , cs * store.ChainStore , ps * pubsub.PubSub , mds dtypes.MetadataDS ) (prov manifest.ManifestProvider , err error ) {
52
+ func NewManifestProvider (mctx helpers.MetricsCtx , config * Config , cs * store.ChainStore , ps * pubsub.PubSub , mds dtypes.MetadataDS , stateCaller StateCaller ) (prov manifest.ManifestProvider , err error ) {
53
+ var primaryManifest manifest.ManifestProvider
54
+ if config .StaticManifest != nil {
55
+ log .Infof ("using static maniest as primary" )
56
+ primaryManifest , err = manifest .NewStaticManifestProvider (config .StaticManifest )
57
+ } else if config .ContractAddress != "" {
58
+ log .Infow ("using contract maniest as primary" , "address" , config .ContractAddress )
59
+ primaryManifest , err = NewContractManifestProvider (mctx , config , stateCaller )
60
+ }
61
+ if err != nil {
62
+ return nil , fmt .Errorf ("creating primary manifest: %w" , err )
63
+ }
64
+
39
65
if config .DynamicManifestProvider == "" || ! build .IsF3PassiveTestingEnabled () {
40
- if config .StaticManifest == nil {
66
+ if config .StaticManifest == nil && config . ContractAddress == "" {
41
67
return manifest.NoopManifestProvider {}, nil
42
68
}
43
- return manifest . NewStaticManifestProvider ( config . StaticManifest )
69
+ return primaryManifest , nil
44
70
}
45
71
46
72
opts := []manifest.DynamicManifestProviderOption {
@@ -49,12 +75,6 @@ func NewManifestProvider(mctx helpers.MetricsCtx, config *Config, cs *store.Chai
49
75
),
50
76
}
51
77
52
- if config .StaticManifest != nil {
53
- opts = append (opts ,
54
- manifest .DynamicManifestProviderWithInitialManifest (config .StaticManifest ),
55
- )
56
- }
57
-
58
78
if config .AllowDynamicFinalize {
59
79
log .Error ("dynamic F3 manifests are allowed to finalize tipsets, do not enable this in production!" )
60
80
}
@@ -84,9 +104,232 @@ func NewManifestProvider(mctx helpers.MetricsCtx, config *Config, cs *store.Chai
84
104
if err != nil {
85
105
return nil , err
86
106
}
87
- if config .PrioritizeStaticManifest && config . StaticManifest != nil {
107
+ if config .PrioritizeStaticManifest && primaryManifest != nil {
88
108
prov , err = manifest .NewFusingManifestProvider (mctx ,
89
- (* headGetter )(cs ), prov , config . StaticManifest )
109
+ (* headGetter )(cs ), prov , primaryManifest )
90
110
}
91
111
return prov , err
92
112
}
113
+
114
+ type StateCaller interface {
115
+ StateCall (ctx context.Context , msg * types.Message , tsk types.TipSetKey ) (res * api.InvocResult , err error )
116
+ }
117
+
118
+ type ContractManifestProvider struct {
119
+ address string
120
+ networkName gpbft.NetworkName
121
+ stateCaller StateCaller
122
+ pollInterval time.Duration
123
+
124
+ manifestChanges chan * manifest.Manifest
125
+
126
+ errgrp * errgroup.Group
127
+ runningCtx context.Context
128
+ cancel context.CancelFunc
129
+ }
130
+
131
+ func NewContractManifestProvider (mctx helpers.MetricsCtx , config * Config , stateCaller StateCaller ) (* ContractManifestProvider , error ) {
132
+ ctx , cancel := context .WithCancel (context .WithoutCancel (mctx ))
133
+ errgrp , ctx := errgroup .WithContext (ctx )
134
+ return & ContractManifestProvider {
135
+ stateCaller : stateCaller ,
136
+ address : config .ContractAddress ,
137
+ networkName : config .BaseNetworkName ,
138
+ pollInterval : config .ContractPollInterval ,
139
+
140
+ manifestChanges : make (chan * manifest.Manifest , 1 ),
141
+
142
+ errgrp : errgrp ,
143
+ runningCtx : ctx ,
144
+ cancel : cancel ,
145
+ }, nil
146
+ }
147
+
148
+ func (cmp * ContractManifestProvider ) Start (context.Context ) error {
149
+ // no address, nothing to do
150
+ if len (cmp .address ) == 0 {
151
+ // send nil so fusing knows we have nothing
152
+ log .Infof ("contract manifest provider, address unknown, exiting" )
153
+ cmp .manifestChanges <- nil
154
+ return nil
155
+ }
156
+
157
+ var knownManifest * manifest.Manifest
158
+ knownManifest , err := cmp .fetchManifest (cmp .runningCtx )
159
+ if err != nil {
160
+ log .Warnw ("got error while fetching manifest from contract" , "error" , err )
161
+ }
162
+ cmp .manifestChanges <- knownManifest
163
+
164
+ cmp .errgrp .Go (func () error {
165
+ t := time .NewTicker (cmp .pollInterval )
166
+ defer t .Stop ()
167
+
168
+ loop:
169
+ for cmp .runningCtx .Err () == nil {
170
+ select {
171
+ case <- t .C :
172
+ m , err := cmp .fetchManifest (cmp .runningCtx )
173
+ if err != nil {
174
+ log .Warnw ("got error while fetching manifest from contract" , "error" , err )
175
+ continue loop
176
+ }
177
+
178
+ if knownManifest .Equal (m ) {
179
+ continue loop
180
+ }
181
+
182
+ c , err := m .Cid ()
183
+ if err != nil {
184
+ log .Errorf ("got error while computing manifest CID" )
185
+ }
186
+
187
+ if m != nil {
188
+ log .Infow ("new manifest from contract" , "enabled" , true ,
189
+ "bootstrapEpoch" , m .BootstrapEpoch ,
190
+ "manifestCID" , c )
191
+ } else {
192
+ log .Info ("new manifest from contract" , "enabled" , false )
193
+ }
194
+ cmp .manifestChanges <- m
195
+ knownManifest = m
196
+ case <- cmp .runningCtx .Done ():
197
+ }
198
+ }
199
+
200
+ return nil
201
+ })
202
+ return nil
203
+ }
204
+
205
+ func decompressManifest (compressedManifest []byte ) (* manifest.Manifest , error ) {
206
+ reader := io .LimitReader (flate .NewReader (bytes .NewReader (compressedManifest )), 1 << 20 )
207
+ var m manifest.Manifest
208
+ err := json .NewDecoder (reader ).Decode (& m )
209
+ if err != nil {
210
+ return nil , err
211
+ }
212
+ return & m , nil
213
+ }
214
+
215
+ func (cmp * ContractManifestProvider ) fetchManifest (ctx context.Context ) (* manifest.Manifest , error ) {
216
+ ethReturn , err := cmp .callContract (ctx )
217
+ if err != nil {
218
+ return nil , fmt .Errorf ("calling contract at %s: %w" , cmp .address , err )
219
+ }
220
+ if len (ethReturn ) == 0 {
221
+ return nil , nil
222
+ }
223
+
224
+ activationEpoch , compressedManifest , err := parseContractReturn (ethReturn )
225
+ if err != nil {
226
+ return nil , fmt .Errorf ("parsing contract information: %w" , err )
227
+ }
228
+
229
+ if activationEpoch == math .MaxUint64 || len (compressedManifest ) == 0 {
230
+ return nil , nil
231
+ }
232
+
233
+ m , err := decompressManifest (compressedManifest )
234
+ if err != nil {
235
+ return nil , fmt .Errorf ("got error while decoding manifest: %w" , err )
236
+ }
237
+
238
+ if m .BootstrapEpoch < 0 || uint64 (m .BootstrapEpoch ) != activationEpoch {
239
+ return nil , fmt .Errorf ("bootstrap epoch does not match: %d != %d" , m .BootstrapEpoch , activationEpoch )
240
+ }
241
+
242
+ if err := m .Validate (); err != nil {
243
+ return nil , fmt .Errorf ("manifest does not validate: %w" , err )
244
+ }
245
+
246
+ if m .NetworkName != cmp .networkName {
247
+ return nil , fmt .Errorf ("network name does not match, expected: %s, got: %s" ,
248
+ cmp .networkName , m .NetworkName )
249
+ }
250
+
251
+ return m , nil
252
+ }
253
+
254
+ func parseContractReturn (retBytes []byte ) (uint64 , []byte , error ) {
255
+ // 3*32 because there should be 3 slots minimum
256
+ if len (retBytes ) < 3 * 32 {
257
+ return 0 , nil , fmt .Errorf ("no activation information" )
258
+ }
259
+
260
+ var slot []byte
261
+ // split off first slot
262
+ slot , retBytes = retBytes [:32 ], retBytes [32 :]
263
+ // it is uint64 so we want the last 8 bytes
264
+ slot = slot [24 :32 ]
265
+ activationEpoch := binary .BigEndian .Uint64 (slot )
266
+
267
+ // next slot is the offest to variable length bytes
268
+ // it is always the same 0x00000...0040
269
+ slot , retBytes = retBytes [:32 ], retBytes [32 :]
270
+ for i := 0 ; i < 31 ; i ++ {
271
+ if slot [i ] != 0 {
272
+ return 0 , nil , fmt .Errorf ("wrong value for offest (padding): slot[%d] = 0x%x != 0x00" , i , slot [i ])
273
+ }
274
+ }
275
+ if slot [31 ] != 0x40 {
276
+ return 0 , nil , fmt .Errorf ("wrong value for offest : slot[31] = 0x%x != 0x40" , slot [31 ])
277
+ }
278
+
279
+ // finally after that there are manifest bytes
280
+ // starts with length in a full slot, slot no 3
281
+ slot , retBytes = retBytes [:32 ], retBytes [32 :]
282
+ slot = slot [24 :32 ]
283
+ pLen := binary .BigEndian .Uint64 (slot )
284
+ if pLen > 4 << 10 {
285
+ return 0 , nil , fmt .Errorf ("too long declared payload: %d > %d" , pLen , 4 << 10 )
286
+ }
287
+ payloadLength := int (pLen )
288
+
289
+ if payloadLength > len (retBytes ) {
290
+ return 0 , nil , fmt .Errorf ("not enough remaining bytes: %d > %d" , payloadLength , retBytes )
291
+ }
292
+
293
+ return activationEpoch , retBytes [:payloadLength ], nil
294
+ }
295
+
296
+ func (cmp * ContractManifestProvider ) callContract (ctx context.Context ) ([]byte , error ) {
297
+ address , err := ethtypes .ParseEthAddress (cmp .address )
298
+ if err != nil {
299
+ return nil , fmt .Errorf ("trying to parse contract address: %s: %w" , cmp .address , err )
300
+ }
301
+
302
+ ethCall := ethtypes.EthCall {
303
+ To : & address ,
304
+ Data : must .One (ethtypes .DecodeHexString ("0x2587660d" )), // method ID of activationInformation()
305
+ }
306
+
307
+ fMessage , err := ethCall .ToFilecoinMessage ()
308
+ if err != nil {
309
+ return nil , fmt .Errorf ("converting to filecoin message: %w" , err )
310
+ }
311
+
312
+ msgRes , err := cmp .stateCaller .StateCall (ctx , fMessage , types .EmptyTSK )
313
+ if err != nil {
314
+ return nil , fmt .Errorf ("state call error: %w" , err )
315
+ }
316
+ if msgRes .MsgRct .ExitCode != 0 {
317
+ return nil , fmt .Errorf ("message returned exit code %v: %v" , msgRes .MsgRct .ExitCode , msgRes .Error )
318
+ }
319
+
320
+ var ethReturn abi.CborBytes
321
+ err = ethReturn .UnmarshalCBOR (bytes .NewReader (msgRes .MsgRct .Return ))
322
+ if err != nil {
323
+ return nil , fmt .Errorf ("could not decode return value: %w" , err )
324
+ }
325
+ return []byte (ethReturn ), nil
326
+ }
327
+
328
+ func (cmp * ContractManifestProvider ) Stop (context.Context ) error {
329
+ cmp .cancel ()
330
+ return cmp .errgrp .Wait ()
331
+ }
332
+
333
+ func (cmp * ContractManifestProvider ) ManifestUpdates () <- chan * manifest.Manifest {
334
+ return cmp .manifestChanges
335
+ }
0 commit comments