From 7e3339820ff7f544e0dbdcb59d043a9f8a3e77ec Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Tue, 6 Oct 2020 15:12:03 -0300 Subject: [PATCH 001/246] fix(sync): remove checks bypass when we submit the block --- chain/sync.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/chain/sync.go b/chain/sync.go index b5716a343..1ce7c5dc0 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -250,18 +250,6 @@ func (syncer *Syncer) InformNewHead(from peer.ID, fts *store.FullTipSet) bool { syncer.incoming.Pub(fts.TipSet().Blocks(), LocalIncoming) - if from == syncer.self { - // TODO: this is kindof a hack... - log.Debug("got block from ourselves") - - if err := syncer.Sync(ctx, fts.TipSet()); err != nil { - log.Errorf("failed to sync our own block %s: %+v", fts.TipSet().Cids(), err) - return false - } - - return true - } - // TODO: IMPORTANT(GARBAGE) this needs to be put in the 'temporary' side of // the blockstore if err := syncer.store.PersistBlockHeaders(fts.TipSet().Blocks()...); err != nil { From fe40a53e8f12cd8f595a3b6a4b204cb146213c80 Mon Sep 17 00:00:00 2001 From: zgfzgf <1901989065@qq.com> Date: Sat, 24 Oct 2020 13:46:15 +0800 Subject: [PATCH 002/246] delete slash-consensus miner --- cli/chain.go | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/cli/chain.go b/cli/chain.go index e2d0ebb4a..53db7e82e 100644 --- a/cli/chain.go +++ b/cli/chain.go @@ -1101,10 +1101,6 @@ var slashConsensusFault = &cli.Command{ Usage: "Report consensus fault", ArgsUsage: "[blockCid1 blockCid2]", Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "miner", - Usage: "Miner address", - }, &cli.StringFlag{ Name: "extra", Usage: "Extra block cid", @@ -1138,6 +1134,10 @@ var slashConsensusFault = &cli.Command{ return xerrors.Errorf("getting block 2: %w", err) } + if b1.Miner != b2.Miner { + return xerrors.Errorf("block1.miner:%s block2.miner:%s", b1.Miner, b2.Miner) + } + def, err := api.WalletDefaultAddress(ctx) if err != nil { return err @@ -1182,17 +1182,8 @@ var slashConsensusFault = &cli.Command{ return err } - if cctx.String("miner") == "" { - return xerrors.Errorf("--miner flag is required") - } - - maddr, err := address.NewFromString(cctx.String("miner")) - if err != nil { - return err - } - msg := &types.Message{ - To: maddr, + To: b2.Miner, From: def, Value: types.NewInt(0), Method: builtin.MethodsMiner.ReportConsensusFault, From 370817eb60bb202d931ca84bb287b6df1721cb70 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 2 Dec 2020 01:21:29 -0500 Subject: [PATCH 003/246] Allow miners to filter (un)verified deals --- api/api_storage.go | 4 ++ api/apistruct/struct.go | 44 ++++++++++++++------ cmd/lotus-storage-miner/market.go | 30 ++++++++++++++ documentation/en/api-methods-miner.md | 50 ++++++++++++++++++++++ node/builder.go | 4 ++ node/config/def.go | 26 +++++++----- node/impl/storminer.go | 48 ++++++++++++++------- node/modules/dtypes/miner.go | 16 +++++++ node/modules/storageminer.go | 60 +++++++++++++++++++++++++++ testplans/lotus-soup/go.mod | 6 +-- testplans/lotus-soup/go.sum | 8 ++++ 11 files changed, 256 insertions(+), 40 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index 5d0ec73a7..911701376 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -104,6 +104,10 @@ type StorageMiner interface { DealsSetConsiderOfflineStorageDeals(context.Context, bool) error DealsConsiderOfflineRetrievalDeals(context.Context) (bool, error) DealsSetConsiderOfflineRetrievalDeals(context.Context, bool) error + DealsConsiderVerifiedStorageDeals(context.Context) (bool, error) + DealsSetConsiderVerifiedStorageDeals(context.Context, bool) error + DealsConsiderUnverifiedStorageDeals(context.Context) (bool, error) + DealsSetConsiderUnverifiedStorageDeals(context.Context, bool) error StorageAddLocal(ctx context.Context, path string) error diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index 3cf0647e3..55b5a16f3 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -342,18 +342,22 @@ type StorageMinerStruct struct { StorageLock func(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) error `perm:"admin"` StorageTryLock func(ctx context.Context, sector abi.SectorID, read storiface.SectorFileType, write storiface.SectorFileType) (bool, error) `perm:"admin"` - DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` - DealsList func(ctx context.Context) ([]api.MarketDeal, error) `perm:"read"` - DealsConsiderOnlineStorageDeals func(context.Context) (bool, error) `perm:"read"` - DealsSetConsiderOnlineStorageDeals func(context.Context, bool) error `perm:"admin"` - DealsConsiderOnlineRetrievalDeals func(context.Context) (bool, error) `perm:"read"` - DealsSetConsiderOnlineRetrievalDeals func(context.Context, bool) error `perm:"admin"` - DealsConsiderOfflineStorageDeals func(context.Context) (bool, error) `perm:"read"` - DealsSetConsiderOfflineStorageDeals func(context.Context, bool) error `perm:"admin"` - DealsConsiderOfflineRetrievalDeals func(context.Context) (bool, error) `perm:"read"` - DealsSetConsiderOfflineRetrievalDeals func(context.Context, bool) error `perm:"admin"` - DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"read"` - DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"admin"` + DealsImportData func(ctx context.Context, dealPropCid cid.Cid, file string) error `perm:"write"` + DealsList func(ctx context.Context) ([]api.MarketDeal, error) `perm:"read"` + DealsConsiderOnlineStorageDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderOnlineStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsConsiderOnlineRetrievalDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderOnlineRetrievalDeals func(context.Context, bool) error `perm:"admin"` + DealsConsiderOfflineStorageDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderOfflineStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsConsiderOfflineRetrievalDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderOfflineRetrievalDeals func(context.Context, bool) error `perm:"admin"` + DealsConsiderVerifiedStorageDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderVerifiedStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsConsiderUnverifiedStorageDeals func(context.Context) (bool, error) `perm:"read"` + DealsSetConsiderUnverifiedStorageDeals func(context.Context, bool) error `perm:"admin"` + DealsPieceCidBlocklist func(context.Context) ([]cid.Cid, error) `perm:"read"` + DealsSetPieceCidBlocklist func(context.Context, []cid.Cid) error `perm:"admin"` StorageAddLocal func(ctx context.Context, path string) error `perm:"admin"` @@ -1492,6 +1496,22 @@ func (c *StorageMinerStruct) DealsSetConsiderOfflineRetrievalDeals(ctx context.C return c.Internal.DealsSetConsiderOfflineRetrievalDeals(ctx, b) } +func (c *StorageMinerStruct) DealsConsiderVerifiedStorageDeals(ctx context.Context) (bool, error) { + return c.Internal.DealsConsiderVerifiedStorageDeals(ctx) +} + +func (c *StorageMinerStruct) DealsSetConsiderVerifiedStorageDeals(ctx context.Context, b bool) error { + return c.Internal.DealsSetConsiderVerifiedStorageDeals(ctx, b) +} + +func (c *StorageMinerStruct) DealsConsiderUnverifiedStorageDeals(ctx context.Context) (bool, error) { + return c.Internal.DealsConsiderUnverifiedStorageDeals(ctx) +} + +func (c *StorageMinerStruct) DealsSetConsiderUnverifiedStorageDeals(ctx context.Context, b bool) error { + return c.Internal.DealsSetConsiderUnverifiedStorageDeals(ctx, b) +} + func (c *StorageMinerStruct) StorageAddLocal(ctx context.Context, path string) error { return c.Internal.StorageAddLocal(ctx, path) } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 15a3c8b64..57a2ad086 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -114,6 +114,16 @@ var storageDealSelectionResetCmd = &cli.Command{ return err } + err = smapi.DealsSetConsiderVerifiedStorageDeals(lcli.DaemonContext(cctx), true) + if err != nil { + return err + } + + err = smapi.DealsSetConsiderUnverifiedStorageDeals(lcli.DaemonContext(cctx), true) + if err != nil { + return err + } + return nil }, } @@ -128,6 +138,12 @@ var storageDealSelectionRejectCmd = &cli.Command{ &cli.BoolFlag{ Name: "offline", }, + &cli.BoolFlag{ + Name: "verified", + }, + &cli.BoolFlag{ + Name: "unverified", + }, }, Action: func(cctx *cli.Context) error { smapi, closer, err := lcli.GetStorageMinerAPI(cctx) @@ -150,6 +166,20 @@ var storageDealSelectionRejectCmd = &cli.Command{ } } + if cctx.Bool("verified") { + err = smapi.DealsSetConsiderVerifiedStorageDeals(lcli.DaemonContext(cctx), false) + if err != nil { + return err + } + } + + if cctx.Bool("unverified") { + err = smapi.DealsSetConsiderUnverifiedStorageDeals(lcli.DaemonContext(cctx), false) + if err != nil { + return err + } + } + return nil }, } diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index d3f9880aa..0dce60c75 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -19,6 +19,8 @@ * [DealsConsiderOfflineStorageDeals](#DealsConsiderOfflineStorageDeals) * [DealsConsiderOnlineRetrievalDeals](#DealsConsiderOnlineRetrievalDeals) * [DealsConsiderOnlineStorageDeals](#DealsConsiderOnlineStorageDeals) + * [DealsConsiderUnverifiedStorageDeals](#DealsConsiderUnverifiedStorageDeals) + * [DealsConsiderVerifiedStorageDeals](#DealsConsiderVerifiedStorageDeals) * [DealsImportData](#DealsImportData) * [DealsList](#DealsList) * [DealsPieceCidBlocklist](#DealsPieceCidBlocklist) @@ -26,6 +28,8 @@ * [DealsSetConsiderOfflineStorageDeals](#DealsSetConsiderOfflineStorageDeals) * [DealsSetConsiderOnlineRetrievalDeals](#DealsSetConsiderOnlineRetrievalDeals) * [DealsSetConsiderOnlineStorageDeals](#DealsSetConsiderOnlineStorageDeals) + * [DealsSetConsiderUnverifiedStorageDeals](#DealsSetConsiderUnverifiedStorageDeals) + * [DealsSetConsiderVerifiedStorageDeals](#DealsSetConsiderVerifiedStorageDeals) * [DealsSetPieceCidBlocklist](#DealsSetPieceCidBlocklist) * [I](#I) * [ID](#ID) @@ -304,6 +308,24 @@ Inputs: `null` Response: `true` +### DealsConsiderUnverifiedStorageDeals +There are not yet any comments for this method. + +Perms: read + +Inputs: `null` + +Response: `true` + +### DealsConsiderVerifiedStorageDeals +There are not yet any comments for this method. + +Perms: read + +Inputs: `null` + +Response: `true` + ### DealsImportData There are not yet any comments for this method. @@ -395,6 +417,34 @@ Inputs: Response: `{}` +### DealsSetConsiderUnverifiedStorageDeals +There are not yet any comments for this method. + +Perms: admin + +Inputs: +```json +[ + true +] +``` + +Response: `{}` + +### DealsSetConsiderVerifiedStorageDeals +There are not yet any comments for this method. + +Perms: admin + +Inputs: +```json +[ + true +] +``` + +Response: `{}` + ### DealsSetPieceCidBlocklist There are not yet any comments for this method. diff --git a/node/builder.go b/node/builder.go index 04400e1e1..a5b6a63c4 100644 --- a/node/builder.go +++ b/node/builder.go @@ -388,6 +388,10 @@ func Online() Option { Override(new(dtypes.SetConsiderOfflineStorageDealsConfigFunc), modules.NewSetConsideringOfflineStorageDealsFunc), Override(new(dtypes.ConsiderOfflineRetrievalDealsConfigFunc), modules.NewConsiderOfflineRetrievalDealsConfigFunc), Override(new(dtypes.SetConsiderOfflineRetrievalDealsConfigFunc), modules.NewSetConsiderOfflineRetrievalDealsConfigFunc), + Override(new(dtypes.ConsiderVerifiedStorageDealsConfigFunc), modules.NewConsiderVerifiedStorageDealsConfigFunc), + Override(new(dtypes.SetConsiderVerifiedStorageDealsConfigFunc), modules.NewSetConsideringVerifiedStorageDealsFunc), + Override(new(dtypes.ConsiderUnverifiedStorageDealsConfigFunc), modules.NewConsiderUnverifiedStorageDealsConfigFunc), + Override(new(dtypes.SetConsiderUnverifiedStorageDealsConfigFunc), modules.NewSetConsideringUnverifiedStorageDealsFunc), Override(new(dtypes.SetSealingConfigFunc), modules.NewSetSealConfigFunc), Override(new(dtypes.GetSealingConfigFunc), modules.NewGetSealConfigFunc), Override(new(dtypes.SetExpectedSealDurationFunc), modules.NewSetExpectedSealDurationFunc), diff --git a/node/config/def.go b/node/config/def.go index 7e45b88a3..7ae998cd2 100644 --- a/node/config/def.go +++ b/node/config/def.go @@ -39,12 +39,14 @@ type StorageMiner struct { } type DealmakingConfig struct { - ConsiderOnlineStorageDeals bool - ConsiderOfflineStorageDeals bool - ConsiderOnlineRetrievalDeals bool - ConsiderOfflineRetrievalDeals bool - PieceCidBlocklist []cid.Cid - ExpectedSealDuration Duration + ConsiderOnlineStorageDeals bool + ConsiderOfflineStorageDeals bool + ConsiderOnlineRetrievalDeals bool + ConsiderOfflineRetrievalDeals bool + ConsiderVerifiedStorageDeals bool + ConsiderUnverifiedStorageDeals bool + PieceCidBlocklist []cid.Cid + ExpectedSealDuration Duration Filter string RetrievalFilter string @@ -189,11 +191,13 @@ func DefaultStorageMiner() *StorageMiner { }, Dealmaking: DealmakingConfig{ - ConsiderOnlineStorageDeals: true, - ConsiderOfflineStorageDeals: true, - ConsiderOnlineRetrievalDeals: true, - ConsiderOfflineRetrievalDeals: true, - PieceCidBlocklist: []cid.Cid{}, + ConsiderOnlineStorageDeals: true, + ConsiderOfflineStorageDeals: true, + ConsiderOnlineRetrievalDeals: true, + ConsiderOfflineRetrievalDeals: true, + ConsiderVerifiedStorageDeals: true, + ConsiderUnverifiedStorageDeals: true, + PieceCidBlocklist: []cid.Cid{}, // TODO: It'd be nice to set this based on sector size ExpectedSealDuration: Duration(time.Hour * 24), }, diff --git a/node/impl/storminer.go b/node/impl/storminer.go index a1a6bec24..d74d6671e 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -60,20 +60,24 @@ type StorageMinerAPI struct { DS dtypes.MetadataDS - ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc - SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc - ConsiderOnlineRetrievalDealsConfigFunc dtypes.ConsiderOnlineRetrievalDealsConfigFunc - SetConsiderOnlineRetrievalDealsConfigFunc dtypes.SetConsiderOnlineRetrievalDealsConfigFunc - StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc - SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc - ConsiderOfflineStorageDealsConfigFunc dtypes.ConsiderOfflineStorageDealsConfigFunc - SetConsiderOfflineStorageDealsConfigFunc dtypes.SetConsiderOfflineStorageDealsConfigFunc - ConsiderOfflineRetrievalDealsConfigFunc dtypes.ConsiderOfflineRetrievalDealsConfigFunc - SetConsiderOfflineRetrievalDealsConfigFunc dtypes.SetConsiderOfflineRetrievalDealsConfigFunc - SetSealingConfigFunc dtypes.SetSealingConfigFunc - GetSealingConfigFunc dtypes.GetSealingConfigFunc - GetExpectedSealDurationFunc dtypes.GetExpectedSealDurationFunc - SetExpectedSealDurationFunc dtypes.SetExpectedSealDurationFunc + ConsiderOnlineStorageDealsConfigFunc dtypes.ConsiderOnlineStorageDealsConfigFunc + SetConsiderOnlineStorageDealsConfigFunc dtypes.SetConsiderOnlineStorageDealsConfigFunc + ConsiderOnlineRetrievalDealsConfigFunc dtypes.ConsiderOnlineRetrievalDealsConfigFunc + SetConsiderOnlineRetrievalDealsConfigFunc dtypes.SetConsiderOnlineRetrievalDealsConfigFunc + StorageDealPieceCidBlocklistConfigFunc dtypes.StorageDealPieceCidBlocklistConfigFunc + SetStorageDealPieceCidBlocklistConfigFunc dtypes.SetStorageDealPieceCidBlocklistConfigFunc + ConsiderOfflineStorageDealsConfigFunc dtypes.ConsiderOfflineStorageDealsConfigFunc + SetConsiderOfflineStorageDealsConfigFunc dtypes.SetConsiderOfflineStorageDealsConfigFunc + ConsiderOfflineRetrievalDealsConfigFunc dtypes.ConsiderOfflineRetrievalDealsConfigFunc + SetConsiderOfflineRetrievalDealsConfigFunc dtypes.SetConsiderOfflineRetrievalDealsConfigFunc + ConsiderVerifiedStorageDealsConfigFunc dtypes.ConsiderVerifiedStorageDealsConfigFunc + SetConsiderVerifiedStorageDealsConfigFunc dtypes.SetConsiderVerifiedStorageDealsConfigFunc + ConsiderUnverifiedStorageDealsConfigFunc dtypes.ConsiderUnverifiedStorageDealsConfigFunc + SetConsiderUnverifiedStorageDealsConfigFunc dtypes.SetConsiderUnverifiedStorageDealsConfigFunc + SetSealingConfigFunc dtypes.SetSealingConfigFunc + GetSealingConfigFunc dtypes.GetSealingConfigFunc + GetExpectedSealDurationFunc dtypes.GetExpectedSealDurationFunc + SetExpectedSealDurationFunc dtypes.SetExpectedSealDurationFunc } func (sm *StorageMinerAPI) ServeRemote(w http.ResponseWriter, r *http.Request) { @@ -481,6 +485,22 @@ func (sm *StorageMinerAPI) DealsSetConsiderOfflineRetrievalDeals(ctx context.Con return sm.SetConsiderOfflineRetrievalDealsConfigFunc(b) } +func (sm *StorageMinerAPI) DealsConsiderVerifiedStorageDeals(ctx context.Context) (bool, error) { + return sm.ConsiderVerifiedStorageDealsConfigFunc() +} + +func (sm *StorageMinerAPI) DealsSetConsiderVerifiedStorageDeals(ctx context.Context, b bool) error { + return sm.SetConsiderVerifiedStorageDealsConfigFunc(b) +} + +func (sm *StorageMinerAPI) DealsConsiderUnverifiedStorageDeals(ctx context.Context) (bool, error) { + return sm.ConsiderUnverifiedStorageDealsConfigFunc() +} + +func (sm *StorageMinerAPI) DealsSetConsiderUnverifiedStorageDeals(ctx context.Context, b bool) error { + return sm.SetConsiderUnverifiedStorageDealsConfigFunc(b) +} + func (sm *StorageMinerAPI) DealsGetExpectedSealDurationFunc(ctx context.Context) (time.Duration, error) { return sm.GetExpectedSealDurationFunc() } diff --git a/node/modules/dtypes/miner.go b/node/modules/dtypes/miner.go index 1ef157b7e..16af48add 100644 --- a/node/modules/dtypes/miner.go +++ b/node/modules/dtypes/miner.go @@ -58,6 +58,22 @@ type ConsiderOfflineRetrievalDealsConfigFunc func() (bool, error) // disable or enable retrieval deal acceptance. type SetConsiderOfflineRetrievalDealsConfigFunc func(bool) error +// ConsiderVerifiedStorageDealsConfigFunc is a function which reads from miner +// config to determine if the user has disabled verified storage deals (or not). +type ConsiderVerifiedStorageDealsConfigFunc func() (bool, error) + +// SetConsiderVerifiedStorageDealsConfigFunc is a function which is used to +// disable or enable verified storage deal acceptance. +type SetConsiderVerifiedStorageDealsConfigFunc func(bool) error + +// ConsiderUnverifiedStorageDealsConfigFunc is a function which reads from miner +// config to determine if the user has disabled unverified storage deals (or not). +type ConsiderUnverifiedStorageDealsConfigFunc func() (bool, error) + +// SetConsiderUnverifiedStorageDealsConfigFunc is a function which is used to +// disable or enable unverified storage deal acceptance. +type SetConsiderUnverifiedStorageDealsConfigFunc func(bool) error + // SetSealingDelay sets how long a sector waits for more deals before sealing begins. type SetSealingConfigFunc func(sealiface.Config) error diff --git a/node/modules/storageminer.go b/node/modules/storageminer.go index c5d8e7e54..0d1d05f5d 100644 --- a/node/modules/storageminer.go +++ b/node/modules/storageminer.go @@ -430,11 +430,15 @@ func NewStorageAsk(ctx helpers.MetricsCtx, fapi lapi.FullNode, ds dtypes.Metadat func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, + verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, + unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { return func(onlineOk dtypes.ConsiderOnlineStorageDealsConfigFunc, offlineOk dtypes.ConsiderOfflineStorageDealsConfigFunc, + verifiedOk dtypes.ConsiderVerifiedStorageDealsConfigFunc, + unverifiedOk dtypes.ConsiderUnverifiedStorageDealsConfigFunc, blocklistFunc dtypes.StorageDealPieceCidBlocklistConfigFunc, expectedSealTimeFunc dtypes.GetExpectedSealDurationFunc, spn storagemarket.StorageProviderNode) dtypes.StorageDealFilter { @@ -460,6 +464,26 @@ func BasicDealFilter(user dtypes.StorageDealFilter) func(onlineOk dtypes.Conside return false, "miner is not accepting offline storage deals", nil } + b, err = verifiedOk() + if err != nil { + return false, "miner error", err + } + + if deal.Proposal.VerifiedDeal && !b { + log.Warnf("verified storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) + return false, "miner is not accepting verified storage deals", nil + } + + b, err = unverifiedOk() + if err != nil { + return false, "miner error", err + } + + if !deal.Proposal.VerifiedDeal && !b { + log.Warnf("unverified storage deal consideration disabled; rejecting storage deal proposal from client: %s", deal.Client.String()) + return false, "miner is not accepting unverified storage deals", nil + } + blocklist, err := blocklistFunc() if err != nil { return false, "miner error", err @@ -706,6 +730,42 @@ func NewSetConsiderOfflineRetrievalDealsConfigFunc(r repo.LockedRepo) (dtypes.Se }, nil } +func NewConsiderVerifiedStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderVerifiedStorageDealsConfigFunc, error) { + return func() (out bool, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = cfg.Dealmaking.ConsiderVerifiedStorageDeals + }) + return + }, nil +} + +func NewSetConsideringVerifiedStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderVerifiedStorageDealsConfigFunc, error) { + return func(b bool) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.ConsiderVerifiedStorageDeals = b + }) + return + }, nil +} + +func NewConsiderUnverifiedStorageDealsConfigFunc(r repo.LockedRepo) (dtypes.ConsiderUnverifiedStorageDealsConfigFunc, error) { + return func() (out bool, err error) { + err = readCfg(r, func(cfg *config.StorageMiner) { + out = cfg.Dealmaking.ConsiderUnverifiedStorageDeals + }) + return + }, nil +} + +func NewSetConsideringUnverifiedStorageDealsFunc(r repo.LockedRepo) (dtypes.SetConsiderUnverifiedStorageDealsConfigFunc, error) { + return func(b bool) (err error) { + err = mutateCfg(r, func(cfg *config.StorageMiner) { + cfg.Dealmaking.ConsiderUnverifiedStorageDeals = b + }) + return + }, nil +} + func NewSetSealConfigFunc(r repo.LockedRepo) (dtypes.SetSealingConfigFunc, error) { return func(cfg sealiface.Config) (err error) { err = mutateCfg(r, func(c *config.StorageMiner) { diff --git a/testplans/lotus-soup/go.mod b/testplans/lotus-soup/go.mod index 4b260cd20..cd71b258a 100644 --- a/testplans/lotus-soup/go.mod +++ b/testplans/lotus-soup/go.mod @@ -8,8 +8,8 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/drand/drand v1.2.1 github.com/filecoin-project/go-address v0.0.5-0.20201103152444-f2023ef3f5bb - github.com/filecoin-project/go-fil-markets v1.0.6 - github.com/filecoin-project/go-jsonrpc v0.1.2-0.20201008195726-68c6a2704e49 + github.com/filecoin-project/go-fil-markets v1.0.9 + github.com/filecoin-project/go-jsonrpc v0.1.2 github.com/filecoin-project/go-state-types v0.0.0-20201102161440-c8033295a1fc github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/lotus v0.9.2-0.20201012041700-a2e0832a12f2 @@ -33,7 +33,7 @@ require ( github.com/multiformats/go-multiaddr v0.3.1 github.com/multiformats/go-multiaddr-net v0.2.0 github.com/testground/sdk-go v0.2.6-0.20201016180515-1e40e1b0ec3a - go.opencensus.io v0.22.4 + go.opencensus.io v0.22.5 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 ) diff --git a/testplans/lotus-soup/go.sum b/testplans/lotus-soup/go.sum index ca19673cb..805811a24 100644 --- a/testplans/lotus-soup/go.sum +++ b/testplans/lotus-soup/go.sum @@ -44,6 +44,7 @@ github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voi github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee h1:8doiS7ib3zi6/K172oDhSKU0dJ/miJramo9NITOMyZQ= github.com/Gurpartap/async v0.0.0-20180927173644-4f7f499dd9ee/go.mod h1:W0GbEAA4uFNYOGG2cJpmFJ04E6SD1NLELPYZB57/7AY= +github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Kubuxu/go-os-helper v0.0.1/go.mod h1:N8B+I7vPCT80IcP58r50u4+gEEcsZETFUpAzWW2ep1Y= github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= @@ -268,6 +269,7 @@ github.com/filecoin-project/go-data-transfer v0.6.7/go.mod h1:C++k1U6+jMQODOaen5 github.com/filecoin-project/go-data-transfer v1.0.1/go.mod h1:UxvfUAY9v3ub0a21BSK9u3pB2aq30Y0KMsG+w9/ysyo= github.com/filecoin-project/go-data-transfer v1.2.0 h1:LM+K+J+y9t8e3gYskJHWDlyHJsF6aaxoHOP+HIiVE1U= github.com/filecoin-project/go-data-transfer v1.2.0/go.mod h1:ZAH51JZFR8NZC4FPiDPG+swjgui0q6zTMJbztc6pHhY= +github.com/filecoin-project/go-data-transfer v1.2.2/go.mod h1:ZAH51JZFR8NZC4FPiDPG+swjgui0q6zTMJbztc6pHhY= github.com/filecoin-project/go-ds-versioning v0.1.0 h1:y/X6UksYTsK8TLCI7rttCKEvl8btmWxyFMEeeWGUxIQ= github.com/filecoin-project/go-ds-versioning v0.1.0/go.mod h1:mp16rb4i2QPmxBnmanUx8i/XANp+PFCCJWiAb+VW4/s= github.com/filecoin-project/go-fil-commcid v0.0.0-20200716160307-8f644712406f h1:GxJzR3oRIMTPtpZ0b7QF8FKPK6/iPAc7trhlL5k/g+s= @@ -279,12 +281,14 @@ github.com/filecoin-project/go-fil-markets v0.7.1/go.mod h1:5Pt4DXQqUoUrp9QzlSdl github.com/filecoin-project/go-fil-markets v1.0.5-0.20201113164554-c5eba40d5335/go.mod h1:AJySOJC00JRWEZzRG2KsfUnqEf5ITXxeX09BE9N4f9c= github.com/filecoin-project/go-fil-markets v1.0.6 h1:JTZBMKJ19YpK/vQfE0rHvjELy83NeGor5d4dBnoIlK0= github.com/filecoin-project/go-fil-markets v1.0.6/go.mod h1:iVYc+VrHIP15F5COkHNM6ndTwKSJ7qPrHSKCfFUnve4= +github.com/filecoin-project/go-fil-markets v1.0.9/go.mod h1:uOikzYK7aNbSWMczCp6Ru257ML4PplLRBfDk/NAOgaY= github.com/filecoin-project/go-hamt-ipld v0.1.5 h1:uoXrKbCQZ49OHpsTCkrThPNelC4W3LPEk0OrS/ytIBM= github.com/filecoin-project/go-hamt-ipld v0.1.5/go.mod h1:6Is+ONR5Cd5R6XZoCse1CWaXZc0Hdb/JeX+EQCQzX24= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0 h1:b3UDemBYN2HNfk3KOXNuxgTTxlWi3xVvbQP0IT38fvM= github.com/filecoin-project/go-hamt-ipld/v2 v2.0.0/go.mod h1:7aWZdaQ1b16BVoQUYR+eEvrDCGJoPLxFpDynFjYfBjI= github.com/filecoin-project/go-jsonrpc v0.1.2-0.20201008195726-68c6a2704e49 h1:FSY245KeXFCUgyfFEu+bhrZNk8BGGJyfpSmQl2aiPU8= github.com/filecoin-project/go-jsonrpc v0.1.2-0.20201008195726-68c6a2704e49/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= +github.com/filecoin-project/go-jsonrpc v0.1.2/go.mod h1:XBBpuKIMaXIIzeqzO1iucq4GvbF8CxmXRFoezRh+Cx4= github.com/filecoin-project/go-multistore v0.0.3 h1:vaRBY4YiA2UZFPK57RNuewypB8u0DzzQwqsL0XarpnI= github.com/filecoin-project/go-multistore v0.0.3/go.mod h1:kaNqCC4IhU4B1uyr7YWFHd23TL4KM32aChS0jNkyUvQ= github.com/filecoin-project/go-padreader v0.0.0-20200903213702-ed5fae088b20 h1:+/4aUeUoKr6AKfPE3mBhXA5spIV6UcKdTYDPNU2Tdmg= @@ -605,6 +609,7 @@ github.com/ipfs/go-graphsync v0.4.2/go.mod h1:/VmbZTUdUMTbNkgzAiCEucIIAU3BkLE2cZ github.com/ipfs/go-graphsync v0.4.3/go.mod h1:mPOwDYv128gf8gxPFgXnz4fNrSYPsWyqisJ7ych+XDY= github.com/ipfs/go-graphsync v0.5.0 h1:iaByvxq88Ys1KcaQzTS1wmRhNsNEo3SaUiSGqTSbGmM= github.com/ipfs/go-graphsync v0.5.0/go.mod h1:e2ZxnClqBBYAtd901g9vXMJzS47labjAtOzsWtOzKNk= +github.com/ipfs/go-graphsync v0.5.1/go.mod h1:e2ZxnClqBBYAtd901g9vXMJzS47labjAtOzsWtOzKNk= github.com/ipfs/go-hamt-ipld v0.1.1/go.mod h1:1EZCr2v0jlCnhpa+aZ0JZYp8Tt2w16+JJOAVz17YcDk= github.com/ipfs/go-ipfs-blockstore v0.0.1/go.mod h1:d3WClOmRQKFnJ0Jz/jj/zmksX0ma1gROTlovZKBmN08= github.com/ipfs/go-ipfs-blockstore v0.1.0/go.mod h1:5aD0AvHPi7mZc6Ci1WCAhiBQu2IsfTduLl+422H6Rqw= @@ -1631,12 +1636,14 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.10.0 h1:yLmDDj9/zuDjv3gz8GQGviXMs9TfysIUMUilCpgzUJY= go.uber.org/dig v1.10.0/go.mod h1:X34SnWGr8Fyla9zQNO2GSO2D+TIuqB14OS8JhYocIyw= go.uber.org/fx v1.9.0 h1:7OAz8ucp35AU8eydejpYG7QrbE8rLKzGhHbZlJi5LYY= @@ -1648,6 +1655,7 @@ go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+ go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= From d56170d70e19097a5b188be01aea37b3e6605713 Mon Sep 17 00:00:00 2001 From: Dan Shao Date: Sun, 6 Dec 2020 08:51:48 +0800 Subject: [PATCH 004/246] Optimize sectors info loading --- api/api_storage.go | 6 +++++ api/apistruct/struct.go | 10 +++++++ cmd/lotus-storage-miner/info.go | 18 +++++-------- cmd/lotus-storage-miner/sectors.go | 23 ++++++++++++++-- node/impl/storminer.go | 43 ++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 13 deletions(-) diff --git a/api/api_storage.go b/api/api_storage.go index a0600a5f4..a71b1a0a2 100644 --- a/api/api_storage.go +++ b/api/api_storage.go @@ -43,6 +43,12 @@ type StorageMiner interface { // List all staged sectors SectorsList(context.Context) ([]abi.SectorNumber, error) + // Get summary info of sectors + SectorsSummary(ctx context.Context) (map[SectorState]int, error) + + // List sectors in particular states + SectorsListInStates(context.Context, []SectorState) ([]abi.SectorNumber, error) + SectorsRefs(context.Context) (map[string][]SealedRef, error) // SectorStartSealing can be called on sectors in Empty or WaitDeals states diff --git a/api/apistruct/struct.go b/api/apistruct/struct.go index cca498bf5..c2b245cf7 100644 --- a/api/apistruct/struct.go +++ b/api/apistruct/struct.go @@ -301,6 +301,8 @@ type StorageMinerStruct struct { SectorsStatus func(ctx context.Context, sid abi.SectorNumber, showOnChainInfo bool) (api.SectorInfo, error) `perm:"read"` SectorsList func(context.Context) ([]abi.SectorNumber, error) `perm:"read"` + SectorsListInStates func(context.Context, []api.SectorState) ([]abi.SectorNumber, error) `perm:"read"` + SectorsSummary func(ctx context.Context) (map[api.SectorState]int, error) `perm:"read"` SectorsRefs func(context.Context) (map[string][]api.SealedRef, error) `perm:"read"` SectorStartSealing func(context.Context, abi.SectorNumber) error `perm:"write"` SectorSetSealDelay func(context.Context, time.Duration) error `perm:"write"` @@ -1249,6 +1251,14 @@ func (c *StorageMinerStruct) SectorsList(ctx context.Context) ([]abi.SectorNumbe return c.Internal.SectorsList(ctx) } +func (c *StorageMinerStruct) SectorsListInStates(ctx context.Context, states []api.SectorState) ([]abi.SectorNumber, error) { + return c.Internal.SectorsListInStates(ctx, states) +} + +func (c *StorageMinerStruct) SectorsSummary(ctx context.Context) (map[api.SectorState]int, error) { + return c.Internal.SectorsSummary(ctx) +} + func (c *StorageMinerStruct) SectorsRefs(ctx context.Context) (map[string][]api.SealedRef, error) { return c.Internal.SectorsRefs(ctx) } diff --git a/cmd/lotus-storage-miner/info.go b/cmd/lotus-storage-miner/info.go index 7bedd2b94..ed74da96b 100644 --- a/cmd/lotus-storage-miner/info.go +++ b/cmd/lotus-storage-miner/info.go @@ -327,22 +327,18 @@ func init() { } func sectorsInfo(ctx context.Context, napi api.StorageMiner) error { - sectors, err := napi.SectorsList(ctx) + summary, err := napi.SectorsSummary(ctx) if err != nil { return err } - buckets := map[sealing.SectorState]int{ - "Total": len(sectors), - } - for _, s := range sectors { - st, err := napi.SectorsStatus(ctx, s, false) - if err != nil { - return err - } - - buckets[sealing.SectorState(st.State)]++ + buckets := make(map[sealing.SectorState]int) + var total int + for s, c := range summary { + buckets[sealing.SectorState(s)] = c + total += c } + buckets["Total"] = total var sorted []stateMeta for state, i := range buckets { diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 37eb06284..1c3e4858c 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -164,6 +164,10 @@ var sectorsListCmd = &cli.Command{ Name: "seal-time", Usage: "display how long it took for the sector to be sealed", }, + &cli.StringFlag{ + Name: "states", + Usage: "filter sectors by a comma-separated list of states", + }, }, Action: func(cctx *cli.Context) error { color.NoColor = !cctx.Bool("color") @@ -182,7 +186,22 @@ var sectorsListCmd = &cli.Command{ ctx := lcli.ReqContext(cctx) - list, err := nodeApi.SectorsList(ctx) + var list []abi.SectorNumber + + showRemoved := cctx.Bool("show-removed") + states := cctx.String("states") + if len(states) == 0 { + list, err = nodeApi.SectorsList(ctx) + } else { + showRemoved = true + sList := strings.Split(states, ",") + ss := make([]api.SectorState, len(sList)) + for i := range sList { + ss[i] = api.SectorState(sList[i]) + } + list, err = nodeApi.SectorsListInStates(ctx, ss) + } + if err != nil { return err } @@ -244,7 +263,7 @@ var sectorsListCmd = &cli.Command{ continue } - if cctx.Bool("show-removed") || st.State != api.SectorState(sealing.Removed) { + if showRemoved || st.State != api.SectorState(sealing.Removed) { _, inSSet := commitedIDs[s] _, inASet := activeIDs[s] diff --git a/node/impl/storminer.go b/node/impl/storminer.go index 85d76f354..bedff5999 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -218,6 +218,49 @@ func (sm *StorageMinerAPI) SectorsList(context.Context) ([]abi.SectorNumber, err return out, nil } +func (sm *StorageMinerAPI) SectorsListInStates(ctx context.Context, states []api.SectorState) ([]abi.SectorNumber, error) { + filterStates := make(map[sealing.SectorState]struct{}) + for _, state := range states { + st := sealing.SectorState(state) + if _, ok := sealing.ExistSectorStateList[st]; !ok { + continue + } + filterStates[st] = struct{}{} + } + + var sns []abi.SectorNumber + if len(filterStates) == 0 { + return sns, nil + } + + sectors, err := sm.Miner.ListSectors() + if err != nil { + return nil, err + } + + for i := range sectors { + if _, ok := filterStates[sectors[i].State]; ok { + sns = append(sns, sectors[i].SectorNumber) + } + } + return sns, nil +} + +func (sm *StorageMinerAPI) SectorsSummary(ctx context.Context) (map[api.SectorState]int, error) { + sectors, err := sm.Miner.ListSectors() + if err != nil { + return nil, err + } + + out := make(map[api.SectorState]int) + for i := range sectors { + state := api.SectorState(sectors[i].State) + out[state] += 1 + } + + return out, nil +} + func (sm *StorageMinerAPI) StorageLocal(ctx context.Context) (map[stores.ID]string, error) { return sm.StorageMgr.StorageLocal(ctx) } From 3c50baaf5be00506c21557999928a78cb2674b73 Mon Sep 17 00:00:00 2001 From: stirlingx Date: Thu, 10 Dec 2020 11:04:42 +0800 Subject: [PATCH 005/246] fix curSealing out of MaxSealingSectors limit --- extern/storage-sealing/garbage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/storage-sealing/garbage.go b/extern/storage-sealing/garbage.go index c3b282d79..185bebe35 100644 --- a/extern/storage-sealing/garbage.go +++ b/extern/storage-sealing/garbage.go @@ -38,7 +38,7 @@ func (m *Sealing) PledgeSector() error { } if cfg.MaxSealingSectors > 0 { - if m.stats.curSealing() > cfg.MaxSealingSectors { + if m.stats.curSealing() >= cfg.MaxSealingSectors { return xerrors.Errorf("too many sectors sealing (curSealing: %d, max: %d)", m.stats.curSealing(), cfg.MaxSealingSectors) } } From d4582fb32e297d56dbef99964ae04abf1667c924 Mon Sep 17 00:00:00 2001 From: caoao Date: Thu, 10 Dec 2020 11:07:17 +0800 Subject: [PATCH 006/246] write size according to RegisteredSealProof --- extern/sector-storage/stores/remote.go | 87 ++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/extern/sector-storage/stores/remote.go b/extern/sector-storage/stores/remote.go index bf66c1bb5..dd5add9c8 100644 --- a/extern/sector-storage/stores/remote.go +++ b/extern/sector-storage/stores/remote.go @@ -3,6 +3,8 @@ package stores import ( "context" "encoding/json" + "fmt" + "io" "io/ioutil" "math/bits" "mime" @@ -59,6 +61,30 @@ func NewRemote(local *Local, index SectorIndex, auth http.Header, fetchLimit int } } +var SealProofIosizes = map[abi.RegisteredSealProof]uint64{ + abi.RegisteredSealProof_StackedDrg2KiBV1: 32 * 1024, + abi.RegisteredSealProof_StackedDrg8MiBV1: 32 * 1024, + abi.RegisteredSealProof_StackedDrg512MiBV1: 1024 * 1024, + abi.RegisteredSealProof_StackedDrg32GiBV1: 1024 * 1024, + abi.RegisteredSealProof_StackedDrg64GiBV1: 1024 * 1024, + + abi.RegisteredSealProof_StackedDrg2KiBV1_1: 32 * 1024, + abi.RegisteredSealProof_StackedDrg8MiBV1_1: 32 * 1024, + abi.RegisteredSealProof_StackedDrg512MiBV1_1: 1024 * 1024, + abi.RegisteredSealProof_StackedDrg32GiBV1_1: 1024 * 1024, + abi.RegisteredSealProof_StackedDrg64GiBV1_1: 1024 * 1024, +} + +const MaxIoSize = 64 * 1024 * 1024 + +func GetIoSizeByProofType(p abi.RegisteredSealProof) (uint64, error) { + size, ok := SealProofIosizes[p] + if !ok { + return 0, xerrors.Errorf("unsupported proof type: %v", p) + } + return size, nil +} + func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existing storiface.SectorFileType, allocate storiface.SectorFileType, pathType storiface.PathType, op storiface.AcquireMode) (storiface.SectorPaths, storiface.SectorPaths, error) { if existing|allocate != existing^allocate { return storiface.SectorPaths{}, storiface.SectorPaths{}, xerrors.New("can't both find and allocate a sector") @@ -123,6 +149,12 @@ func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existin } defer releaseStorage() + var iosize uint64 = 0 + ssize, err := GetIoSizeByProofType(s.ProofType) + if err == nil { + iosize = ssize + } + for _, fileType := range storiface.PathTypes { if fileType&existing == 0 { continue @@ -135,7 +167,7 @@ func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existin dest := storiface.PathByType(apaths, fileType) storageID := storiface.PathByType(ids, fileType) - url, err := r.acquireFromRemote(ctx, s.ID, fileType, dest) + url, err := r.acquireFromRemote(ctx, s.ID, fileType, dest, iosize) if err != nil { return storiface.SectorPaths{}, storiface.SectorPaths{}, err } @@ -170,7 +202,7 @@ func tempFetchDest(spath string, create bool) (string, error) { return filepath.Join(tempdir, b), nil } -func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType storiface.SectorFileType, dest string) (string, error) { +func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType storiface.SectorFileType, dest string, iosize uint64) (string, error) { si, err := r.index.StorageFindSector(ctx, s, fileType, 0, false) if err != nil { return "", err @@ -198,7 +230,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("removing dest: %w", err) } - err = r.fetch(ctx, url, tempDest) + err = r.fetch(ctx, url, tempDest, iosize) if err != nil { merr = multierror.Append(merr, xerrors.Errorf("fetch error %s (storage %s) -> %s: %w", url, info.ID, tempDest, err)) continue @@ -218,7 +250,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("failed to acquire sector %v from remote (tried %v): %w", s, si, merr) } -func (r *Remote) fetch(ctx context.Context, url, outname string) error { +func (r *Remote) fetch(ctx context.Context, url, outname string, iosize uint64) error { log.Infof("Fetch %s -> %s", url, outname) if len(r.limit) >= cap(r.limit) { @@ -276,12 +308,57 @@ func (r *Remote) fetch(ctx context.Context, url, outname string) error { case "application/x-tar": return tarutil.ExtractTar(resp.Body, outname) case "application/octet-stream": - return files.WriteTo(files.NewReaderFile(resp.Body), outname) + return WriteWithSize(files.NewReaderFile(resp.Body), outname, iosize) default: return xerrors.Errorf("unknown content type: '%s'", mediatype) } } +func WriteWithSize(nd files.Node, fpath string, size uint64) error { + switch nd := nd.(type) { + case *files.Symlink: + return os.Symlink(nd.Target, fpath) + case files.File: + f, err := os.Create(fpath) + defer func() { + err = f.Close() + if err != nil { + log.Warnf("failed to close file:%v, err:%v", fpath, err) + } + }() + if err != nil { + return err + } + if size <= 0 || size > MaxIoSize { + _, err = io.Copy(f, nd) + } else { + buf := make([]byte, size) + _, err = io.CopyBuffer(f, nd, buf) + } + + if err != nil { + return err + } + return nil + case files.Directory: + err := os.Mkdir(fpath, 0777) + if err != nil { + return err + } + + entries := nd.Entries() + for entries.Next() { + child := filepath.Join(fpath, entries.Name()) + if err := WriteWithSize(entries.Node(), child, size); err != nil { + return err + } + } + return entries.Err() + default: + return fmt.Errorf("file type %T at %q is not supported", nd, fpath) + } +} + func (r *Remote) MoveStorage(ctx context.Context, s storage.SectorRef, types storiface.SectorFileType) error { // Make sure we have the data local _, _, err := r.AcquireSector(ctx, s, types, storiface.FTNone, storiface.PathStorage, storiface.AcquireMove) From 0f08da3272103ac6e256b97a0e5246010ca6ba26 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Dec 2020 17:21:32 +0800 Subject: [PATCH 007/246] add mpoolpending from to filter --- cli/mpool.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/cli/mpool.go b/cli/mpool.go index d30b85f4f..a84865547 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -49,6 +49,14 @@ var mpoolPending = &cli.Command{ Name: "cids", Usage: "only print cids of messages in output", }, + &cli.StringFlag{ + Name: "to", + Usage: "return messages to a given address", + }, + &cli.StringFlag{ + Name: "from", + Usage: "return messages from a given address", + }, }, Action: func(cctx *cli.Context) error { api, closer, err := GetFullNodeAPI(cctx) @@ -59,6 +67,23 @@ var mpoolPending = &cli.Command{ ctx := ReqContext(cctx) + var toa, froma address.Address + if tos := cctx.String("to"); tos != "" { + a, err := address.NewFromString(tos) + if err != nil { + return fmt.Errorf("given 'to' address %q was invalid: %w", tos, err) + } + toa = a + } + + if froms := cctx.String("from"); froms != "" { + a, err := address.NewFromString(froms) + if err != nil { + return fmt.Errorf("given 'from' address %q was invalid: %w", froms, err) + } + froma = a + } + var filter map[address.Address]struct{} if cctx.Bool("local") { filter = map[address.Address]struct{}{} @@ -85,6 +110,13 @@ var mpoolPending = &cli.Command{ } } + if toa != address.Undef && msg.Message.To != toa { + continue + } + if froma != address.Undef && msg.Message.From != froma { + continue + } + if cctx.Bool("cids") { fmt.Println(msg.Cid()) } else { From 0570306bb6d73d8b025f11ea9062dadcfdfb951d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Thu, 10 Dec 2020 14:48:37 +0000 Subject: [PATCH 008/246] add metrics for delayed blocks. --- chain/sub/incoming.go | 4 ++++ metrics/metrics.go | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/chain/sub/incoming.go b/chain/sub/incoming.go index f161bca57..eeaa9af72 100644 --- a/chain/sub/incoming.go +++ b/chain/sub/incoming.go @@ -97,6 +97,10 @@ func HandleIncomingBlocks(ctx context.Context, bsub *pubsub.Subscription, s *cha log.Warnw("Slow msg fetch", "cid", blk.Header.Cid(), "source", msg.GetFrom(), "msgfetch", took) } if delay := build.Clock.Now().Unix() - int64(blk.Header.Timestamp); delay > 5 { + _ = stats.RecordWithTags(ctx, + []tag.Mutator{tag.Insert(metrics.MinerID, blk.Header.Miner.String())}, + metrics.BlockDelay.M(delay), + ) log.Warnf("Received block with large delay %d from miner %s", delay, blk.Header.Miner) } diff --git a/metrics/metrics.go b/metrics/metrics.go index 9f0cad27f..996fa95b9 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -19,6 +19,7 @@ var ( Version, _ = tag.NewKey("version") Commit, _ = tag.NewKey("commit") PeerID, _ = tag.NewKey("peer_id") + MinerID, _ = tag.NewKey("miner_id") FailureType, _ = tag.NewKey("failure_type") Local, _ = tag.NewKey("local") MessageFrom, _ = tag.NewKey("message_from") @@ -44,6 +45,7 @@ var ( BlockValidationFailure = stats.Int64("block/failure", "Counter for block validation failures", stats.UnitDimensionless) BlockValidationSuccess = stats.Int64("block/success", "Counter for block validation successes", stats.UnitDimensionless) BlockValidationDurationMilliseconds = stats.Float64("block/validation_ms", "Duration for Block Validation in ms", stats.UnitMilliseconds) + BlockDelay = stats.Int64("block/delay", "Delay of accepted blocks, where delay is >5s", stats.UnitMilliseconds) PeerCount = stats.Int64("peer/count", "Current number of FIL peers", stats.UnitDimensionless) PubsubPublishMessage = stats.Int64("pubsub/published", "Counter for total published messages", stats.UnitDimensionless) PubsubDeliverMessage = stats.Int64("pubsub/delivered", "Counter for total delivered messages", stats.UnitDimensionless) @@ -94,6 +96,24 @@ var ( Measure: BlockValidationDurationMilliseconds, Aggregation: defaultMillisecondsDistribution, } + BlockDelayView = &view.View{ + Measure: BlockDelay, + TagKeys: []tag.Key{MinerID}, + Aggregation: func() *view.Aggregation { + var bounds []float64 + for i := 5; i < 29; i++ { // 5-29s, step 1s + bounds = append(bounds, float64(i*1000)) + } + for i := 30; i < 60; i += 2 { // 30-58s, step 2s + bounds = append(bounds, float64(i*1000)) + } + for i := 60; i <= 300; i += 10 { // 60-300s, step 10s + bounds = append(bounds, float64(i*1000)) + } + bounds = append(bounds, 600*1000) // final cutoff at 10m + return view.Distribution(bounds...) + }(), + } MessagePublishedView = &view.View{ Measure: MessagePublished, Aggregation: view.Count(), @@ -168,6 +188,7 @@ var DefaultViews = append([]*view.View{ BlockValidationFailureView, BlockValidationSuccessView, BlockValidationDurationView, + BlockDelayView, MessagePublishedView, MessageReceivedView, MessageValidationFailureView, From e07059e73da644e87de380b72f38e9bcb37c756c Mon Sep 17 00:00:00 2001 From: vyzo Date: Thu, 10 Dec 2020 17:26:41 +0200 Subject: [PATCH 009/246] fix PushUntrusted publishing -- the message is local --- chain/messagepool/messagepool.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/messagepool/messagepool.go b/chain/messagepool/messagepool.go index 1e5c25587..5fae5e262 100644 --- a/chain/messagepool/messagepool.go +++ b/chain/messagepool/messagepool.go @@ -871,7 +871,7 @@ func (mp *MessagePool) PushUntrusted(m *types.SignedMessage) (cid.Cid, error) { }() mp.curTsLk.Lock() - publish, err := mp.addTs(m, mp.curTs, false, true) + publish, err := mp.addTs(m, mp.curTs, true, true) if err != nil { mp.curTsLk.Unlock() return cid.Undef, err From 564b88af52a71eb9e347f42410f0d422473e6f45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 10 Dec 2020 19:36:02 +0100 Subject: [PATCH 010/246] docsgen, lint --- api/docgen/docgen.go | 3 +++ documentation/en/api-methods-miner.md | 32 +++++++++++++++++++++++++++ node/impl/storminer.go | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 074b6a787..29bf89936 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -237,6 +237,9 @@ func init() { addExample(map[abi.SectorNumber]string{ 123: "can't acquire read lock", }) + addExample(map[api.SectorState]int{ + api.SectorState(sealing.Proving): 120, + }) // worker specific addExample(storiface.AcquireMove) diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index 9bad3d424..7ac714ee1 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -97,8 +97,10 @@ * [SectorStartSealing](#SectorStartSealing) * [Sectors](#Sectors) * [SectorsList](#SectorsList) + * [SectorsListInStates](#SectorsListInStates) * [SectorsRefs](#SectorsRefs) * [SectorsStatus](#SectorsStatus) + * [SectorsSummary](#SectorsSummary) * [SectorsUpdate](#SectorsUpdate) * [Storage](#Storage) * [StorageAddLocal](#StorageAddLocal) @@ -1496,6 +1498,21 @@ Inputs: `null` Response: `null` +### SectorsListInStates +List sectors in particular states + + +Perms: read + +Inputs: +```json +[ + null +] +``` + +Response: `null` + ### SectorsRefs There are not yet any comments for this method. @@ -1564,6 +1581,21 @@ Response: } ``` +### SectorsSummary +Get summary info of sectors + + +Perms: read + +Inputs: `null` + +Response: +```json +{ + "Proving": 120 +} +``` + ### SectorsUpdate There are not yet any comments for this method. diff --git a/node/impl/storminer.go b/node/impl/storminer.go index bedff5999..6eb2d3bf7 100644 --- a/node/impl/storminer.go +++ b/node/impl/storminer.go @@ -255,7 +255,7 @@ func (sm *StorageMinerAPI) SectorsSummary(ctx context.Context) (map[api.SectorSt out := make(map[api.SectorState]int) for i := range sectors { state := api.SectorState(sectors[i].State) - out[state] += 1 + out[state]++ } return out, nil From 47346907ddfa29bea01520f4e303032512e95da7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 10 Dec 2020 19:55:59 +0100 Subject: [PATCH 011/246] docsgen: Add []abi.SectorNumber example --- api/docgen/docgen.go | 1 + documentation/en/api-methods-miner.md | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/api/docgen/docgen.go b/api/docgen/docgen.go index 29bf89936..7d3ac4bcf 100644 --- a/api/docgen/docgen.go +++ b/api/docgen/docgen.go @@ -240,6 +240,7 @@ func init() { addExample(map[api.SectorState]int{ api.SectorState(sealing.Proving): 120, }) + addExample([]abi.SectorNumber{123, 124}) // worker specific addExample(storiface.AcquireMove) diff --git a/documentation/en/api-methods-miner.md b/documentation/en/api-methods-miner.md index 7ac714ee1..17845afbb 100644 --- a/documentation/en/api-methods-miner.md +++ b/documentation/en/api-methods-miner.md @@ -1496,7 +1496,13 @@ Perms: read Inputs: `null` -Response: `null` +Response: +```json +[ + 123, + 124 +] +``` ### SectorsListInStates List sectors in particular states @@ -1511,7 +1517,13 @@ Inputs: ] ``` -Response: `null` +Response: +```json +[ + 123, + 124 +] +``` ### SectorsRefs There are not yet any comments for this method. From 46a5013dc102acb6a3c559f904c927e8e75a2149 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 10 Dec 2020 20:51:50 +0100 Subject: [PATCH 012/246] stores: Simplify remote copy buf size logic --- extern/sector-storage/stores/remote.go | 98 +++++--------------------- 1 file changed, 16 insertions(+), 82 deletions(-) diff --git a/extern/sector-storage/stores/remote.go b/extern/sector-storage/stores/remote.go index dd5add9c8..4388a2ffb 100644 --- a/extern/sector-storage/stores/remote.go +++ b/extern/sector-storage/stores/remote.go @@ -3,7 +3,6 @@ package stores import ( "context" "encoding/json" - "fmt" "io" "io/ioutil" "math/bits" @@ -24,12 +23,13 @@ import ( "github.com/filecoin-project/specs-storage/storage" "github.com/hashicorp/go-multierror" - files "github.com/ipfs/go-ipfs-files" "golang.org/x/xerrors" ) var FetchTempSubdir = "fetching" +var CopyBuf = 1 << 20 + type Remote struct { local *Local index SectorIndex @@ -61,30 +61,6 @@ func NewRemote(local *Local, index SectorIndex, auth http.Header, fetchLimit int } } -var SealProofIosizes = map[abi.RegisteredSealProof]uint64{ - abi.RegisteredSealProof_StackedDrg2KiBV1: 32 * 1024, - abi.RegisteredSealProof_StackedDrg8MiBV1: 32 * 1024, - abi.RegisteredSealProof_StackedDrg512MiBV1: 1024 * 1024, - abi.RegisteredSealProof_StackedDrg32GiBV1: 1024 * 1024, - abi.RegisteredSealProof_StackedDrg64GiBV1: 1024 * 1024, - - abi.RegisteredSealProof_StackedDrg2KiBV1_1: 32 * 1024, - abi.RegisteredSealProof_StackedDrg8MiBV1_1: 32 * 1024, - abi.RegisteredSealProof_StackedDrg512MiBV1_1: 1024 * 1024, - abi.RegisteredSealProof_StackedDrg32GiBV1_1: 1024 * 1024, - abi.RegisteredSealProof_StackedDrg64GiBV1_1: 1024 * 1024, -} - -const MaxIoSize = 64 * 1024 * 1024 - -func GetIoSizeByProofType(p abi.RegisteredSealProof) (uint64, error) { - size, ok := SealProofIosizes[p] - if !ok { - return 0, xerrors.Errorf("unsupported proof type: %v", p) - } - return size, nil -} - func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existing storiface.SectorFileType, allocate storiface.SectorFileType, pathType storiface.PathType, op storiface.AcquireMode) (storiface.SectorPaths, storiface.SectorPaths, error) { if existing|allocate != existing^allocate { return storiface.SectorPaths{}, storiface.SectorPaths{}, xerrors.New("can't both find and allocate a sector") @@ -149,12 +125,6 @@ func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existin } defer releaseStorage() - var iosize uint64 = 0 - ssize, err := GetIoSizeByProofType(s.ProofType) - if err == nil { - iosize = ssize - } - for _, fileType := range storiface.PathTypes { if fileType&existing == 0 { continue @@ -167,7 +137,7 @@ func (r *Remote) AcquireSector(ctx context.Context, s storage.SectorRef, existin dest := storiface.PathByType(apaths, fileType) storageID := storiface.PathByType(ids, fileType) - url, err := r.acquireFromRemote(ctx, s.ID, fileType, dest, iosize) + url, err := r.acquireFromRemote(ctx, s.ID, fileType, dest) if err != nil { return storiface.SectorPaths{}, storiface.SectorPaths{}, err } @@ -202,7 +172,7 @@ func tempFetchDest(spath string, create bool) (string, error) { return filepath.Join(tempdir, b), nil } -func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType storiface.SectorFileType, dest string, iosize uint64) (string, error) { +func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType storiface.SectorFileType, dest string) (string, error) { si, err := r.index.StorageFindSector(ctx, s, fileType, 0, false) if err != nil { return "", err @@ -230,7 +200,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("removing dest: %w", err) } - err = r.fetch(ctx, url, tempDest, iosize) + err = r.fetch(ctx, url, tempDest) if err != nil { merr = multierror.Append(merr, xerrors.Errorf("fetch error %s (storage %s) -> %s: %w", url, info.ID, tempDest, err)) continue @@ -250,7 +220,7 @@ func (r *Remote) acquireFromRemote(ctx context.Context, s abi.SectorID, fileType return "", xerrors.Errorf("failed to acquire sector %v from remote (tried %v): %w", s, si, merr) } -func (r *Remote) fetch(ctx context.Context, url, outname string, iosize uint64) error { +func (r *Remote) fetch(ctx context.Context, url, outname string) error { log.Infof("Fetch %s -> %s", url, outname) if len(r.limit) >= cap(r.limit) { @@ -308,57 +278,21 @@ func (r *Remote) fetch(ctx context.Context, url, outname string, iosize uint64) case "application/x-tar": return tarutil.ExtractTar(resp.Body, outname) case "application/octet-stream": - return WriteWithSize(files.NewReaderFile(resp.Body), outname, iosize) + f, err := os.Create(outname) + if err != nil { + return err + } + _, err = io.CopyBuffer(f, resp.Body, make([]byte, CopyBuf)) + if err != nil { + f.Close() // nolint + return err + } + return f.Close() default: return xerrors.Errorf("unknown content type: '%s'", mediatype) } } -func WriteWithSize(nd files.Node, fpath string, size uint64) error { - switch nd := nd.(type) { - case *files.Symlink: - return os.Symlink(nd.Target, fpath) - case files.File: - f, err := os.Create(fpath) - defer func() { - err = f.Close() - if err != nil { - log.Warnf("failed to close file:%v, err:%v", fpath, err) - } - }() - if err != nil { - return err - } - if size <= 0 || size > MaxIoSize { - _, err = io.Copy(f, nd) - } else { - buf := make([]byte, size) - _, err = io.CopyBuffer(f, nd, buf) - } - - if err != nil { - return err - } - return nil - case files.Directory: - err := os.Mkdir(fpath, 0777) - if err != nil { - return err - } - - entries := nd.Entries() - for entries.Next() { - child := filepath.Join(fpath, entries.Name()) - if err := WriteWithSize(entries.Node(), child, size); err != nil { - return err - } - } - return entries.Err() - default: - return fmt.Errorf("file type %T at %q is not supported", nd, fpath) - } -} - func (r *Remote) MoveStorage(ctx context.Context, s storage.SectorRef, types storiface.SectorFileType) error { // Make sure we have the data local _, _, err := r.AcquireSector(ctx, s, types, storiface.FTNone, storiface.PathStorage, storiface.AcquireMove) From e33f873df8b445ac40f3c36cd5c80b3f08adc70d Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Thu, 10 Dec 2020 15:38:18 -0500 Subject: [PATCH 013/246] Print MinPieceSize when querying ask --- cli/client.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cli/client.go b/cli/client.go index 0dc117eeb..129ede94b 100644 --- a/cli/client.go +++ b/cli/client.go @@ -1429,6 +1429,7 @@ var clientQueryAskCmd = &cli.Command{ afmt.Printf("Price per GiB: %s\n", types.FIL(ask.Price)) afmt.Printf("Verified Price per GiB: %s\n", types.FIL(ask.VerifiedPrice)) afmt.Printf("Max Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MaxPieceSize)))) + afmt.Printf("Min Piece size: %s\n", types.SizeStr(types.NewInt(uint64(ask.MinPieceSize)))) size := cctx.Int64("size") if size == 0 { From 7db5e8061deede7726016bf9ee0dd907deb6f574 Mon Sep 17 00:00:00 2001 From: zzx234234 <215874917@qq.com> Date: Fri, 11 Dec 2020 11:31:36 +0800 Subject: [PATCH 014/246] fix a coding error in truncating sector log --- extern/storage-sealing/fsm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/storage-sealing/fsm.go b/extern/storage-sealing/fsm.go index 740e4243a..09ac8c61b 100644 --- a/extern/storage-sealing/fsm.go +++ b/extern/storage-sealing/fsm.go @@ -196,7 +196,7 @@ func (m *Sealing) plan(events []statemachine.Event, state *SectorInfo) (func(sta Kind: fmt.Sprintf("truncate"), } - state.Log = append(state.Log[:2000], state.Log[:6000]...) + state.Log = append(state.Log[:2000], state.Log[6000:]...) } state.Log = append(state.Log, l) From dc8ff27b362e3eeee506cf8e9b1401ba4e32558e Mon Sep 17 00:00:00 2001 From: Dirk McCormick Date: Mon, 14 Dec 2020 12:30:52 +0100 Subject: [PATCH 015/246] fix: when waiting for deal commit messages, ignore unsuccessful messages --- .../storageadapter/ondealsectorcommitted.go | 10 ++++ .../ondealsectorcommitted_test.go | 51 +++++++++++++++++-- paychmgr/settler/settler.go | 5 ++ 3 files changed, 61 insertions(+), 5 deletions(-) diff --git a/markets/storageadapter/ondealsectorcommitted.go b/markets/storageadapter/ondealsectorcommitted.go index bfa084638..bd59da750 100644 --- a/markets/storageadapter/ondealsectorcommitted.go +++ b/markets/storageadapter/ondealsectorcommitted.go @@ -75,6 +75,11 @@ func OnDealSectorPreCommitted(ctx context.Context, api getCurrentDealInfoAPI, ev return false, err } + // Ignore the pre-commit message if it was not executed successfully + if rec.ExitCode != 0 { + return true, nil + } + // Extract the message parameters var params miner.SectorPreCommitInfo if err := params.UnmarshalCBOR(bytes.NewReader(msg.Params)); err != nil { @@ -175,6 +180,11 @@ func OnDealSectorCommitted(ctx context.Context, api getCurrentDealInfoAPI, event return false, err } + // Ignore the prove-commit message if it was not executed successfully + if rec.ExitCode != 0 { + return true, nil + } + // Get the deal info _, sd, err := GetCurrentDealInfo(ctx, ts, api, dealID, proposal, publishCid) if err != nil { diff --git a/markets/storageadapter/ondealsectorcommitted_test.go b/markets/storageadapter/ondealsectorcommitted_test.go index 30fbfea76..b74a1e532 100644 --- a/markets/storageadapter/ondealsectorcommitted_test.go +++ b/markets/storageadapter/ondealsectorcommitted_test.go @@ -114,6 +114,25 @@ func TestOnDealSectorPreCommitted(t *testing.T) { expectedCBIsActive: false, expectedCBSectorNumber: sectorNumber, }, + "ignores unsuccessful pre-commit message": { + checkTsDeals: map[abi.DealID]*api.MarketDeal{ + startDealID: unfinishedDeal, + }, + matchStates: []matchState{ + { + msg: makeMessage(t, provider, miner.Methods.PreCommitSector, &miner.SectorPreCommitInfo{ + SectorNumber: sectorNumber, + SealedCID: sealedCid, + DealIDs: []abi.DealID{startDealID}, + }), + deals: map[abi.DealID]*api.MarketDeal{ + startDealID: unfinishedDeal, + }, + receipt: &types.MessageReceipt{ExitCode: 1}, + }, + }, + expectedCBCallCount: 0, + }, "error on deal in check": { checkTsDeals: map[abi.DealID]*api.MarketDeal{}, searchMessageErr: errors.New("something went wrong"), @@ -179,7 +198,7 @@ func TestOnDealSectorPreCommitted(t *testing.T) { matchMessages[i] = matchMessage{ curH: 5, msg: ms.msg, - msgReceipt: nil, + msgReceipt: ms.receipt, ts: matchTs, } } @@ -297,6 +316,23 @@ func TestOnDealSectorCommitted(t *testing.T) { }, expectedCBCallCount: 1, }, + "ignores unsuccessful prove-commit message": { + checkTsDeals: map[abi.DealID]*api.MarketDeal{ + startDealID: unfinishedDeal, + }, + matchStates: []matchState{ + { + msg: makeMessage(t, provider, miner.Methods.ProveCommitSector, &miner.ProveCommitSectorParams{ + SectorNumber: sectorNumber, + }), + deals: map[abi.DealID]*api.MarketDeal{ + startDealID: successDeal, + }, + receipt: &types.MessageReceipt{ExitCode: 1}, + }, + }, + expectedCBCallCount: 0, + }, "error on deal in check": { checkTsDeals: map[abi.DealID]*api.MarketDeal{}, searchMessageErr: errors.New("something went wrong"), @@ -361,7 +397,7 @@ func TestOnDealSectorCommitted(t *testing.T) { matchMessages[i] = matchMessage{ curH: 5, msg: ms.msg, - msgReceipt: nil, + msgReceipt: ms.receipt, ts: matchTs, } } @@ -397,8 +433,9 @@ func TestOnDealSectorCommitted(t *testing.T) { } type matchState struct { - msg *types.Message - deals map[abi.DealID]*api.MarketDeal + msg *types.Message + receipt *types.MessageReceipt + deals map[abi.DealID]*api.MarketDeal } type matchMessage struct { @@ -434,7 +471,11 @@ func (fe *fakeEvents) Called(check events.CheckFunc, msgHnd events.MsgHandler, r return err } if matched { - more, err := msgHnd(matchMessage.msg, matchMessage.msgReceipt, matchMessage.ts, matchMessage.curH) + receipt := matchMessage.msgReceipt + if receipt == nil { + receipt = &types.MessageReceipt{ExitCode: 0} + } + more, err := msgHnd(matchMessage.msg, receipt, matchMessage.ts, matchMessage.curH) if err != nil { return err } diff --git a/paychmgr/settler/settler.go b/paychmgr/settler/settler.go index 41aaca665..131cd25a7 100644 --- a/paychmgr/settler/settler.go +++ b/paychmgr/settler/settler.go @@ -73,6 +73,11 @@ func (pcs *paymentChannelSettler) check(ts *types.TipSet) (done bool, more bool, } func (pcs *paymentChannelSettler) messageHandler(msg *types.Message, rec *types.MessageReceipt, ts *types.TipSet, curH abi.ChainEpoch) (more bool, err error) { + // Ignore unsuccessful settle messages + if rec.ExitCode != 0 { + return true, nil + } + bestByLane, err := paychmgr.BestSpendableByLane(pcs.ctx, pcs.api, msg.To) if err != nil { return true, err From 446b1cc8836c4a1ba2e0846381c9445e4d26da0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 15 Dec 2020 16:55:57 +0000 Subject: [PATCH 016/246] tvx command to extract tipset vectors. --- cmd/tvx/extract.go | 444 ++----------------------------------- cmd/tvx/extract_many.go | 4 +- cmd/tvx/extract_message.go | 440 ++++++++++++++++++++++++++++++++++++ cmd/tvx/extract_tipset.go | 177 +++++++++++++++ conformance/driver.go | 45 +++- conformance/runner.go | 8 +- 6 files changed, 675 insertions(+), 443 deletions(-) create mode 100644 cmd/tvx/extract_message.go create mode 100644 cmd/tvx/extract_tipset.go diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index 894fa0fbc..61808882c 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -1,30 +1,8 @@ package main import ( - "bytes" - "compress/gzip" - "context" - "encoding/json" "fmt" - "io" - "log" - "os" - "path/filepath" - "github.com/fatih/color" - "github.com/filecoin-project/go-address" - - "github.com/filecoin-project/lotus/api" - "github.com/filecoin-project/lotus/chain/actors/builtin" - init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" - "github.com/filecoin-project/lotus/chain/actors/builtin/reward" - "github.com/filecoin-project/lotus/chain/types" - "github.com/filecoin-project/lotus/chain/vm" - "github.com/filecoin-project/lotus/conformance" - - "github.com/filecoin-project/test-vectors/schema" - - "github.com/ipfs/go-cid" "github.com/urfave/cli/v2" ) @@ -38,6 +16,7 @@ type extractOpts struct { block string class string cid string + tsk string file string retain string precursor string @@ -56,7 +35,7 @@ var extractCmd = &cli.Command{ &repoFlag, &cli.StringFlag{ Name: "class", - Usage: "class of vector to extract; other required flags depend on the; values: 'message'", + Usage: "class of vector to extract; values: 'message', 'tipset'", Value: "message", Destination: &extractFlags.class, }, @@ -79,9 +58,13 @@ var extractCmd = &cli.Command{ &cli.StringFlag{ Name: "cid", Usage: "message CID to generate test vector from", - Required: true, Destination: &extractFlags.cid, }, + &cli.StringFlag{ + Name: "tsk", + Usage: "tipset key to extract into a vector", + Destination: &extractFlags.tsk, + }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, @@ -114,413 +97,12 @@ var extractCmd = &cli.Command{ } func runExtract(_ *cli.Context) error { - return doExtract(extractFlags) -} - -func doExtract(opts extractOpts) error { - ctx := context.Background() - - mcid, err := cid.Decode(opts.cid) - if err != nil { - return err - } - - msg, execTs, incTs, err := resolveFromChain(ctx, FullAPI, mcid, opts.block) - if err != nil { - return fmt.Errorf("failed to resolve message and tipsets from chain: %w", err) - } - - // get the circulating supply before the message was executed. - circSupplyDetail, err := FullAPI.StateVMCirculatingSupplyInternal(ctx, incTs.Key()) - if err != nil { - return fmt.Errorf("failed while fetching circulating supply: %w", err) - } - - circSupply := circSupplyDetail.FilCirculating - - log.Printf("message was executed in tipset: %s", execTs.Key()) - log.Printf("message was included in tipset: %s", incTs.Key()) - log.Printf("circulating supply at inclusion tipset: %d", circSupply) - log.Printf("finding precursor messages using mode: %s", opts.precursor) - - // Fetch messages in canonical order from inclusion tipset. - msgs, err := FullAPI.ChainGetParentMessages(ctx, execTs.Blocks()[0].Cid()) - if err != nil { - return fmt.Errorf("failed to fetch messages in canonical order from inclusion tipset: %w", err) - } - - related, found, err := findMsgAndPrecursors(opts.precursor, mcid, msg.From, msgs) - if err != nil { - return fmt.Errorf("failed while finding message and precursors: %w", err) - } - - if !found { - return fmt.Errorf("message not found; precursors found: %d", len(related)) - } - - var ( - precursors = related[:len(related)-1] - precursorsCids []cid.Cid - ) - - for _, p := range precursors { - precursorsCids = append(precursorsCids, p.Cid()) - } - - log.Println(color.GreenString("found message; precursors (count: %d): %v", len(precursors), precursorsCids)) - - var ( - // create a read-through store that uses ChainGetObject to fetch unknown CIDs. - pst = NewProxyingStores(ctx, FullAPI) - g = NewSurgeon(ctx, FullAPI, pst) - ) - - driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{ - DisableVMFlush: true, - }) - - // this is the root of the state tree we start with. - root := incTs.ParentState() - log.Printf("base state tree root CID: %s", root) - - basefee := incTs.Blocks()[0].ParentBaseFee - log.Printf("basefee: %s", basefee) - - // on top of that state tree, we apply all precursors. - log.Printf("number of precursors to apply: %d", len(precursors)) - for i, m := range precursors { - log.Printf("applying precursor %d, cid: %s", i, m.Cid()) - _, root, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ - Preroot: root, - Epoch: execTs.Height(), - Message: m, - CircSupply: circSupplyDetail.FilCirculating, - BaseFee: basefee, - // recorded randomness will be discarded. - Rand: conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI), - }) - if err != nil { - return fmt.Errorf("failed to execute precursor message: %w", err) - } - } - - var ( - preroot cid.Cid - postroot cid.Cid - applyret *vm.ApplyRet - carWriter func(w io.Writer) error - retention = opts.retain - - // recordingRand will record randomness so we can embed it in the test vector. - recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI) - ) - - log.Printf("using state retention strategy: %s", retention) - switch retention { - case "accessed-cids": - tbs, ok := pst.Blockstore.(TracingBlockstore) - if !ok { - return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present") - } - - tbs.StartTracing() - - preroot = root - applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ - Preroot: preroot, - Epoch: execTs.Height(), - Message: msg, - CircSupply: circSupplyDetail.FilCirculating, - BaseFee: basefee, - Rand: recordingRand, - }) - if err != nil { - return fmt.Errorf("failed to execute message: %w", err) - } - accessed := tbs.FinishTracing() - carWriter = func(w io.Writer) error { - return g.WriteCARIncluding(w, accessed, preroot, postroot) - } - - case "accessed-actors": - log.Printf("calculating accessed actors") - // get actors accessed by message. - retain, err := g.GetAccessedActors(ctx, FullAPI, mcid) - if err != nil { - return fmt.Errorf("failed to calculate accessed actors: %w", err) - } - // also append the reward actor and the burnt funds actor. - retain = append(retain, reward.Address, builtin.BurntFundsActorAddr, init_.Address) - log.Printf("calculated accessed actors: %v", retain) - - // get the masked state tree from the root, - preroot, err = g.GetMaskedStateTree(root, retain) - if err != nil { - return err - } - applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ - Preroot: preroot, - Epoch: execTs.Height(), - Message: msg, - CircSupply: circSupplyDetail.FilCirculating, - BaseFee: basefee, - Rand: recordingRand, - }) - if err != nil { - return fmt.Errorf("failed to execute message: %w", err) - } - carWriter = func(w io.Writer) error { - return g.WriteCAR(w, preroot, postroot) - } - + switch extractFlags.class { + case "message": + return doExtractMessage(extractFlags) + case "tipset": + return doExtractTipset(extractFlags) default: - return fmt.Errorf("unknown state retention option: %s", retention) + return fmt.Errorf("unsupported vector class") } - - log.Printf("message applied; preroot: %s, postroot: %s", preroot, postroot) - log.Println("performing sanity check on receipt") - - // TODO sometimes this returns a nil receipt and no error ¯\_(ツ)_/¯ - // ex: https://filfox.info/en/message/bafy2bzacebpxw3yiaxzy2bako62akig46x3imji7fewszen6fryiz6nymu2b2 - // This code is lenient and skips receipt comparison in case of a nil receipt. - rec, err := FullAPI.StateGetReceipt(ctx, mcid, execTs.Key()) - if err != nil { - return fmt.Errorf("failed to find receipt on chain: %w", err) - } - log.Printf("found receipt: %+v", rec) - - // generate the schema receipt; if we got - var receipt *schema.Receipt - if rec != nil { - receipt = &schema.Receipt{ - ExitCode: int64(rec.ExitCode), - ReturnValue: rec.Return, - GasUsed: rec.GasUsed, - } - - reporter := new(conformance.LogReporter) - conformance.AssertMsgResult(reporter, receipt, applyret, "as locally executed") - if reporter.Failed() { - if opts.ignoreSanityChecks { - log.Println(color.YellowString("receipt sanity check failed; proceeding anyway")) - } else { - log.Println(color.RedString("receipt sanity check failed; aborting")) - return fmt.Errorf("vector generation aborted") - } - } else { - log.Println(color.GreenString("receipt sanity check succeeded")) - } - - } else { - receipt = &schema.Receipt{ - ExitCode: int64(applyret.ExitCode), - ReturnValue: applyret.Return, - GasUsed: applyret.GasUsed, - } - log.Println(color.YellowString("skipping receipts comparison; we got back a nil receipt from lotus")) - } - - log.Println("generating vector") - msgBytes, err := msg.Serialize() - if err != nil { - return err - } - - var ( - out = new(bytes.Buffer) - gw = gzip.NewWriter(out) - ) - if err := carWriter(gw); err != nil { - return err - } - if err = gw.Flush(); err != nil { - return err - } - if err = gw.Close(); err != nil { - return err - } - - version, err := FullAPI.Version(ctx) - if err != nil { - return err - } - - ntwkName, err := FullAPI.StateNetworkName(ctx) - if err != nil { - return err - } - - nv, err := FullAPI.StateNetworkVersion(ctx, execTs.Key()) - if err != nil { - return err - } - - codename := GetProtocolCodename(execTs.Height()) - - // Write out the test vector. - vector := schema.TestVector{ - Class: schema.ClassMessage, - Meta: &schema.Metadata{ - ID: opts.id, - // TODO need to replace schema.GenerationData with a more flexible - // data structure that makes no assumption about the traceability - // data that's being recorded; a flexible map[string]string - // would do. - Gen: []schema.GenerationData{ - {Source: fmt.Sprintf("network:%s", ntwkName)}, - {Source: fmt.Sprintf("message:%s", msg.Cid().String())}, - {Source: fmt.Sprintf("inclusion_tipset:%s", incTs.Key().String())}, - {Source: fmt.Sprintf("execution_tipset:%s", execTs.Key().String())}, - {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, - }, - Selector: schema.Selector{ - schema.SelectorMinProtocolVersion: codename, - }, - Randomness: recordingRand.Recorded(), - CAR: out.Bytes(), - Pre: &schema.Preconditions{ - Variants: []schema.Variant{ - {ID: codename, Epoch: int64(execTs.Height()), NetworkVersion: uint(nv)}, - }, - CircSupply: circSupply.Int, - BaseFee: basefee.Int, - StateTree: &schema.StateTree{ - RootCID: preroot, - }, - }, - ApplyMessages: []schema.Message{{Bytes: msgBytes}}, - Post: &schema.Postconditions{ - StateTree: &schema.StateTree{ - RootCID: postroot, - }, - Receipts: []*schema.Receipt{ - { - ExitCode: int64(applyret.ExitCode), - ReturnValue: applyret.Return, - GasUsed: applyret.GasUsed, - }, - }, - }, - } - - return writeVector(vector, opts.file) -} - -func writeVector(vector schema.TestVector, file string) (err error) { - output := io.WriteCloser(os.Stdout) - if file := file; file != "" { - dir := filepath.Dir(file) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("unable to create directory %s: %w", dir, err) - } - output, err = os.Create(file) - if err != nil { - return err - } - defer output.Close() //nolint:errcheck - defer log.Printf("wrote test vector to file: %s", file) - } - - enc := json.NewEncoder(output) - enc.SetIndent("", " ") - return enc.Encode(&vector) -} - -// resolveFromChain queries the chain for the provided message, using the block CID to -// speed up the query, if provided -func resolveFromChain(ctx context.Context, api api.FullNode, mcid cid.Cid, block string) (msg *types.Message, execTs *types.TipSet, incTs *types.TipSet, err error) { - // Extract the full message. - msg, err = api.ChainGetMessage(ctx, mcid) - if err != nil { - return nil, nil, nil, err - } - - log.Printf("found message with CID %s: %+v", mcid, msg) - - if block == "" { - log.Printf("locating message in blockchain") - - // Locate the message. - msgInfo, err := api.StateSearchMsg(ctx, mcid) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to locate message: %w", err) - } - - log.Printf("located message at tipset %s (height: %d) with exit code: %s", msgInfo.TipSet, msgInfo.Height, msgInfo.Receipt.ExitCode) - - execTs, incTs, err = fetchThisAndPrevTipset(ctx, api, msgInfo.TipSet) - return msg, execTs, incTs, err - } - - bcid, err := cid.Decode(block) - if err != nil { - return nil, nil, nil, err - } - - log.Printf("message inclusion block CID was provided; scanning around it: %s", bcid) - - blk, err := api.ChainGetBlock(ctx, bcid) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get block: %w", err) - } - - // types.EmptyTSK hints to use the HEAD. - execTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height+1, types.EmptyTSK) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get message execution tipset: %w", err) - } - - // walk back from the execTs instead of HEAD, to save time. - incTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height, execTs.Key()) - if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get message inclusion tipset: %w", err) - } - - return msg, execTs, incTs, nil -} - -// fetchThisAndPrevTipset returns the full tipset identified by the key, as well -// as the previous tipset. In the context of vector generation, the target -// tipset is the one where a message was executed, and the previous tipset is -// the one where the message was included. -func fetchThisAndPrevTipset(ctx context.Context, api api.FullNode, target types.TipSetKey) (targetTs *types.TipSet, prevTs *types.TipSet, err error) { - // get the tipset on which this message was "executed" on. - // https://github.com/filecoin-project/lotus/issues/2847 - targetTs, err = api.ChainGetTipSet(ctx, target) - if err != nil { - return nil, nil, err - } - // get the previous tipset, on which this message was mined, - // i.e. included on-chain. - prevTs, err = api.ChainGetTipSet(ctx, targetTs.Parents()) - if err != nil { - return nil, nil, err - } - return targetTs, prevTs, nil -} - -// findMsgAndPrecursors ranges through the canonical messages slice, locating -// the target message and returning precursors in accordance to the supplied -// mode. -func findMsgAndPrecursors(mode string, msgCid cid.Cid, sender address.Address, msgs []api.Message) (related []*types.Message, found bool, err error) { - // Range through canonicalised messages, selecting only the precursors based - // on selection mode. - for _, other := range msgs { - switch { - case mode == PrecursorSelectAll: - fallthrough - case mode == PrecursorSelectSender && other.Message.From == sender: - related = append(related, other.Message) - } - - // this message is the target; we're done. - if other.Cid == msgCid { - return related, true, nil - } - } - - // this could happen because a block contained related messages, but not - // the target (that is, messages with a lower nonce, but ultimately not the - // target). - return related, false, nil } diff --git a/cmd/tvx/extract_many.go b/cmd/tvx/extract_many.go index 048271456..081678a17 100644 --- a/cmd/tvx/extract_many.go +++ b/cmd/tvx/extract_many.go @@ -189,7 +189,7 @@ func runExtractMany(c *cli.Context) error { precursor: PrecursorSelectSender, } - if err := doExtract(opts); err != nil { + if err := doExtractMessage(opts); err != nil { log.Println(color.RedString("failed to extract vector for message %s: %s; queuing for 'all' precursor selection", mcid, err)) retry = append(retry, opts) continue @@ -206,7 +206,7 @@ func runExtractMany(c *cli.Context) error { log.Printf("retrying %s: %s", r.cid, r.id) r.precursor = PrecursorSelectAll - if err := doExtract(r); err != nil { + if err := doExtractMessage(r); err != nil { merr = multierror.Append(merr, fmt.Errorf("failed to extract vector for message %s: %w", r.cid, err)) continue } diff --git a/cmd/tvx/extract_message.go b/cmd/tvx/extract_message.go new file mode 100644 index 000000000..286910c38 --- /dev/null +++ b/cmd/tvx/extract_message.go @@ -0,0 +1,440 @@ +package main + +import ( + "bytes" + "compress/gzip" + "context" + "encoding/json" + "fmt" + "io" + "log" + "os" + "path/filepath" + + "github.com/fatih/color" + "github.com/filecoin-project/go-address" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin" + init_ "github.com/filecoin-project/lotus/chain/actors/builtin/init" + "github.com/filecoin-project/lotus/chain/actors/builtin/reward" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/vm" + "github.com/filecoin-project/lotus/conformance" + + "github.com/filecoin-project/test-vectors/schema" + + "github.com/ipfs/go-cid" +) + +func doExtractMessage(opts extractOpts) error { + ctx := context.Background() + + if opts.cid == "" { + return fmt.Errorf("missing message CID") + } + + mcid, err := cid.Decode(opts.cid) + if err != nil { + return err + } + + msg, execTs, incTs, err := resolveFromChain(ctx, FullAPI, mcid, opts.block) + if err != nil { + return fmt.Errorf("failed to resolve message and tipsets from chain: %w", err) + } + + // get the circulating supply before the message was executed. + circSupplyDetail, err := FullAPI.StateVMCirculatingSupplyInternal(ctx, incTs.Key()) + if err != nil { + return fmt.Errorf("failed while fetching circulating supply: %w", err) + } + + circSupply := circSupplyDetail.FilCirculating + + log.Printf("message was executed in tipset: %s", execTs.Key()) + log.Printf("message was included in tipset: %s", incTs.Key()) + log.Printf("circulating supply at inclusion tipset: %d", circSupply) + log.Printf("finding precursor messages using mode: %s", opts.precursor) + + // Fetch messages in canonical order from inclusion tipset. + msgs, err := FullAPI.ChainGetParentMessages(ctx, execTs.Blocks()[0].Cid()) + if err != nil { + return fmt.Errorf("failed to fetch messages in canonical order from inclusion tipset: %w", err) + } + + related, found, err := findMsgAndPrecursors(opts.precursor, mcid, msg.From, msgs) + if err != nil { + return fmt.Errorf("failed while finding message and precursors: %w", err) + } + + if !found { + return fmt.Errorf("message not found; precursors found: %d", len(related)) + } + + var ( + precursors = related[:len(related)-1] + precursorsCids []cid.Cid + ) + + for _, p := range precursors { + precursorsCids = append(precursorsCids, p.Cid()) + } + + log.Println(color.GreenString("found message; precursors (count: %d): %v", len(precursors), precursorsCids)) + + var ( + // create a read-through store that uses ChainGetObject to fetch unknown CIDs. + pst = NewProxyingStores(ctx, FullAPI) + g = NewSurgeon(ctx, FullAPI, pst) + ) + + driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{ + DisableVMFlush: true, + }) + + // this is the root of the state tree we start with. + root := incTs.ParentState() + log.Printf("base state tree root CID: %s", root) + + basefee := incTs.Blocks()[0].ParentBaseFee + log.Printf("basefee: %s", basefee) + + // on top of that state tree, we apply all precursors. + log.Printf("number of precursors to apply: %d", len(precursors)) + for i, m := range precursors { + log.Printf("applying precursor %d, cid: %s", i, m.Cid()) + _, root, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ + Preroot: root, + Epoch: execTs.Height(), + Message: m, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, + // recorded randomness will be discarded. + Rand: conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI), + }) + if err != nil { + return fmt.Errorf("failed to execute precursor message: %w", err) + } + } + + var ( + preroot cid.Cid + postroot cid.Cid + applyret *vm.ApplyRet + carWriter func(w io.Writer) error + retention = opts.retain + + // recordingRand will record randomness so we can embed it in the test vector. + recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI) + ) + + log.Printf("using state retention strategy: %s", retention) + switch retention { + case "accessed-cids": + tbs, ok := pst.Blockstore.(TracingBlockstore) + if !ok { + return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present") + } + + tbs.StartTracing() + + preroot = root + applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ + Preroot: preroot, + Epoch: execTs.Height(), + Message: msg, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, + Rand: recordingRand, + }) + if err != nil { + return fmt.Errorf("failed to execute message: %w", err) + } + accessed := tbs.FinishTracing() + carWriter = func(w io.Writer) error { + return g.WriteCARIncluding(w, accessed, preroot, postroot) + } + + case "accessed-actors": + log.Printf("calculating accessed actors") + // get actors accessed by message. + retain, err := g.GetAccessedActors(ctx, FullAPI, mcid) + if err != nil { + return fmt.Errorf("failed to calculate accessed actors: %w", err) + } + // also append the reward actor and the burnt funds actor. + retain = append(retain, reward.Address, builtin.BurntFundsActorAddr, init_.Address) + log.Printf("calculated accessed actors: %v", retain) + + // get the masked state tree from the root, + preroot, err = g.GetMaskedStateTree(root, retain) + if err != nil { + return err + } + applyret, postroot, err = driver.ExecuteMessage(pst.Blockstore, conformance.ExecuteMessageParams{ + Preroot: preroot, + Epoch: execTs.Height(), + Message: msg, + CircSupply: circSupplyDetail.FilCirculating, + BaseFee: basefee, + Rand: recordingRand, + }) + if err != nil { + return fmt.Errorf("failed to execute message: %w", err) + } + carWriter = func(w io.Writer) error { + return g.WriteCAR(w, preroot, postroot) + } + + default: + return fmt.Errorf("unknown state retention option: %s", retention) + } + + log.Printf("message applied; preroot: %s, postroot: %s", preroot, postroot) + log.Println("performing sanity check on receipt") + + // TODO sometimes this returns a nil receipt and no error ¯\_(ツ)_/¯ + // ex: https://filfox.info/en/message/bafy2bzacebpxw3yiaxzy2bako62akig46x3imji7fewszen6fryiz6nymu2b2 + // This code is lenient and skips receipt comparison in case of a nil receipt. + rec, err := FullAPI.StateGetReceipt(ctx, mcid, execTs.Key()) + if err != nil { + return fmt.Errorf("failed to find receipt on chain: %w", err) + } + log.Printf("found receipt: %+v", rec) + + // generate the schema receipt; if we got + var receipt *schema.Receipt + if rec != nil { + receipt = &schema.Receipt{ + ExitCode: int64(rec.ExitCode), + ReturnValue: rec.Return, + GasUsed: rec.GasUsed, + } + + reporter := new(conformance.LogReporter) + conformance.AssertMsgResult(reporter, receipt, applyret, "as locally executed") + if reporter.Failed() { + if opts.ignoreSanityChecks { + log.Println(color.YellowString("receipt sanity check failed; proceeding anyway")) + } else { + log.Println(color.RedString("receipt sanity check failed; aborting")) + return fmt.Errorf("vector generation aborted") + } + } else { + log.Println(color.GreenString("receipt sanity check succeeded")) + } + + } else { + receipt = &schema.Receipt{ + ExitCode: int64(applyret.ExitCode), + ReturnValue: applyret.Return, + GasUsed: applyret.GasUsed, + } + log.Println(color.YellowString("skipping receipts comparison; we got back a nil receipt from lotus")) + } + + log.Println("generating vector") + msgBytes, err := msg.Serialize() + if err != nil { + return err + } + + var ( + out = new(bytes.Buffer) + gw = gzip.NewWriter(out) + ) + if err := carWriter(gw); err != nil { + return err + } + if err = gw.Flush(); err != nil { + return err + } + if err = gw.Close(); err != nil { + return err + } + + version, err := FullAPI.Version(ctx) + if err != nil { + return err + } + + ntwkName, err := FullAPI.StateNetworkName(ctx) + if err != nil { + return err + } + + nv, err := FullAPI.StateNetworkVersion(ctx, execTs.Key()) + if err != nil { + return err + } + + codename := GetProtocolCodename(execTs.Height()) + + // Write out the test vector. + vector := schema.TestVector{ + Class: schema.ClassMessage, + Meta: &schema.Metadata{ + ID: opts.id, + // TODO need to replace schema.GenerationData with a more flexible + // data structure that makes no assumption about the traceability + // data that's being recorded; a flexible map[string]string + // would do. + Gen: []schema.GenerationData{ + {Source: fmt.Sprintf("network:%s", ntwkName)}, + {Source: fmt.Sprintf("message:%s", msg.Cid().String())}, + {Source: fmt.Sprintf("inclusion_tipset:%s", incTs.Key().String())}, + {Source: fmt.Sprintf("execution_tipset:%s", execTs.Key().String())}, + {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, + }, + Selector: schema.Selector{ + schema.SelectorMinProtocolVersion: codename, + }, + Randomness: recordingRand.Recorded(), + CAR: out.Bytes(), + Pre: &schema.Preconditions{ + Variants: []schema.Variant{ + {ID: codename, Epoch: int64(execTs.Height()), NetworkVersion: uint(nv)}, + }, + CircSupply: circSupply.Int, + BaseFee: basefee.Int, + StateTree: &schema.StateTree{ + RootCID: preroot, + }, + }, + ApplyMessages: []schema.Message{{Bytes: msgBytes}}, + Post: &schema.Postconditions{ + StateTree: &schema.StateTree{ + RootCID: postroot, + }, + Receipts: []*schema.Receipt{ + { + ExitCode: int64(applyret.ExitCode), + ReturnValue: applyret.Return, + GasUsed: applyret.GasUsed, + }, + }, + }, + } + + return writeVector(vector, opts.file) +} + +func writeVector(vector schema.TestVector, file string) (err error) { + output := io.WriteCloser(os.Stdout) + if file := file; file != "" { + dir := filepath.Dir(file) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("unable to create directory %s: %w", dir, err) + } + output, err = os.Create(file) + if err != nil { + return err + } + defer output.Close() //nolint:errcheck + defer log.Printf("wrote test vector to file: %s", file) + } + + enc := json.NewEncoder(output) + enc.SetIndent("", " ") + return enc.Encode(&vector) +} + +// resolveFromChain queries the chain for the provided message, using the block CID to +// speed up the query, if provided +func resolveFromChain(ctx context.Context, api api.FullNode, mcid cid.Cid, block string) (msg *types.Message, execTs *types.TipSet, incTs *types.TipSet, err error) { + // Extract the full message. + msg, err = api.ChainGetMessage(ctx, mcid) + if err != nil { + return nil, nil, nil, err + } + + log.Printf("found message with CID %s: %+v", mcid, msg) + + if block == "" { + log.Printf("locating message in blockchain") + + // Locate the message. + msgInfo, err := api.StateSearchMsg(ctx, mcid) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to locate message: %w", err) + } + + log.Printf("located message at tipset %s (height: %d) with exit code: %s", msgInfo.TipSet, msgInfo.Height, msgInfo.Receipt.ExitCode) + + execTs, incTs, err = fetchThisAndPrevTipset(ctx, api, msgInfo.TipSet) + return msg, execTs, incTs, err + } + + bcid, err := cid.Decode(block) + if err != nil { + return nil, nil, nil, err + } + + log.Printf("message inclusion block CID was provided; scanning around it: %s", bcid) + + blk, err := api.ChainGetBlock(ctx, bcid) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get block: %w", err) + } + + // types.EmptyTSK hints to use the HEAD. + execTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height+1, types.EmptyTSK) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get message execution tipset: %w", err) + } + + // walk back from the execTs instead of HEAD, to save time. + incTs, err = api.ChainGetTipSetByHeight(ctx, blk.Height, execTs.Key()) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get message inclusion tipset: %w", err) + } + + return msg, execTs, incTs, nil +} + +// fetchThisAndPrevTipset returns the full tipset identified by the key, as well +// as the previous tipset. In the context of vector generation, the target +// tipset is the one where a message was executed, and the previous tipset is +// the one where the message was included. +func fetchThisAndPrevTipset(ctx context.Context, api api.FullNode, target types.TipSetKey) (targetTs *types.TipSet, prevTs *types.TipSet, err error) { + // get the tipset on which this message was "executed" on. + // https://github.com/filecoin-project/lotus/issues/2847 + targetTs, err = api.ChainGetTipSet(ctx, target) + if err != nil { + return nil, nil, err + } + // get the previous tipset, on which this message was mined, + // i.e. included on-chain. + prevTs, err = api.ChainGetTipSet(ctx, targetTs.Parents()) + if err != nil { + return nil, nil, err + } + return targetTs, prevTs, nil +} + +// findMsgAndPrecursors ranges through the canonical messages slice, locating +// the target message and returning precursors in accordance to the supplied +// mode. +func findMsgAndPrecursors(mode string, msgCid cid.Cid, sender address.Address, msgs []api.Message) (related []*types.Message, found bool, err error) { + // Range through canonicalised messages, selecting only the precursors based + // on selection mode. + for _, other := range msgs { + switch { + case mode == PrecursorSelectAll: + fallthrough + case mode == PrecursorSelectSender && other.Message.From == sender: + related = append(related, other.Message) + } + + // this message is the target; we're done. + if other.Cid == msgCid { + return related, true, nil + } + } + + // this could happen because a block contained related messages, but not + // the target (that is, messages with a lower nonce, but ultimately not the + // target). + return related, false, nil +} diff --git a/cmd/tvx/extract_tipset.go b/cmd/tvx/extract_tipset.go new file mode 100644 index 000000000..c1ab8fe86 --- /dev/null +++ b/cmd/tvx/extract_tipset.go @@ -0,0 +1,177 @@ +package main + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "log" + + "github.com/filecoin-project/test-vectors/schema" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" + "github.com/filecoin-project/lotus/conformance" +) + +func doExtractTipset(opts extractOpts) error { + ctx := context.Background() + + if opts.tsk == "" { + return fmt.Errorf("tipset key cannot be empty") + } + + if opts.retain != "accessed-cids" { + return fmt.Errorf("tipset extraction only supports 'accessed-cids' state retention") + } + + ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk) + if err != nil { + return fmt.Errorf("failed to fetch tipset: %w", err) + } + + log.Printf("tipset block count: %d", len(ts.Blocks())) + + var blocks []schema.Block + for _, b := range ts.Blocks() { + msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid()) + if err != nil { + return fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err) + } + + log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids)) + + packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids)) + for _, m := range msgs.BlsMessages { + b, err := m.Serialize() + if err != nil { + return fmt.Errorf("failed to serialize message: %w", err) + } + packed = append(packed, b) + } + for _, m := range msgs.SecpkMessages { + b, err := m.Message.Serialize() + if err != nil { + return fmt.Errorf("failed to serialize message: %w", err) + } + packed = append(packed, b) + } + blocks = append(blocks, schema.Block{ + MinerAddr: b.Miner, + WinCount: b.ElectionProof.WinCount, + Messages: packed, + }) + } + + var ( + // create a read-through store that uses ChainGetObject to fetch unknown CIDs. + pst = NewProxyingStores(ctx, FullAPI) + g = NewSurgeon(ctx, FullAPI, pst) + ) + + driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{ + DisableVMFlush: true, + }) + + // this is the root of the state tree we start with. + root := ts.ParentState() + log.Printf("base state tree root CID: %s", root) + + basefee := ts.Blocks()[0].ParentBaseFee + log.Printf("basefee: %s", basefee) + + tipset := schema.Tipset{ + BaseFee: *basefee.Int, + Blocks: blocks, + } + + // recordingRand will record randomness so we can embed it in the test vector. + recordingRand := conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI) + + log.Printf("using state retention strategy: %s", extractFlags.retain) + + tbs, ok := pst.Blockstore.(TracingBlockstore) + if !ok { + return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present") + } + + tbs.StartTracing() + + params := conformance.ExecuteTipsetParams{ + Preroot: ts.ParentState(), + ParentEpoch: ts.Height() - 1, + Tipset: &tipset, + ExecEpoch: ts.Height(), + Rand: recordingRand, + } + result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params) + if err != nil { + return fmt.Errorf("failed to execute tipset: %w", err) + } + + accessed := tbs.FinishTracing() + + // write a CAR with the accessed state into a buffer. + var ( + out = new(bytes.Buffer) + gw = gzip.NewWriter(out) + ) + if err := g.WriteCARIncluding(gw, accessed, ts.ParentState(), result.PostStateRoot); err != nil { + return err + } + if err = gw.Flush(); err != nil { + return err + } + if err = gw.Close(); err != nil { + return err + } + + codename := GetProtocolCodename(ts.Height()) + nv, err := FullAPI.StateNetworkVersion(ctx, ts.Key()) + if err != nil { + return err + } + + vector := schema.TestVector{ + Class: schema.ClassTipset, + Meta: &schema.Metadata{ + ID: opts.id, + }, + Selector: schema.Selector{ + schema.SelectorMinProtocolVersion: codename, + }, + Randomness: recordingRand.Recorded(), + CAR: out.Bytes(), + Pre: &schema.Preconditions{ + Variants: []schema.Variant{ + {ID: codename, Epoch: int64(ts.Height()), NetworkVersion: uint(nv)}, + }, + BaseFee: basefee.Int, + StateTree: &schema.StateTree{ + RootCID: ts.ParentState(), + }, + }, + ApplyTipsets: []schema.Tipset{tipset}, + Post: &schema.Postconditions{ + StateTree: &schema.StateTree{ + RootCID: result.PostStateRoot, + }, + ReceiptsRoots: []cid.Cid{result.ReceiptsRoot}, + }, + } + + // do nothing with this for now. + var traces = make([]types.ExecutionTrace, 0, len(result.AppliedResults)) + for _, res := range result.AppliedResults { + vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{ + ExitCode: int64(res.ExitCode), + ReturnValue: res.Return, + GasUsed: res.GasUsed, + }) + + traces = append(traces, res.ExecutionTrace) + } + + return writeVector(vector, opts.file) +} diff --git a/conformance/driver.go b/conformance/driver.go index 833d50d7b..1d5450622 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -73,24 +73,43 @@ type ExecuteTipsetResult struct { AppliedResults []*vm.ApplyRet } +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 vm.Rand implementation to use. If nil, the driver + // will use a vm.Rand that returns a fixed value for all calls. + Rand vm.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. // -// parentEpoch is the last epoch in which an actual tipset was processed. This -// is used by Lotus for null block counting and cron firing. -// // 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, preroot cid.Cid, parentEpoch abi.ChainEpoch, tipset *schema.Tipset, execEpoch abi.ChainEpoch) (*ExecuteTipsetResult, error) { +func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, params ExecuteTipsetParams) (*ExecuteTipsetResult, error) { var ( + tipset = params.Tipset syscalls = vm.Syscalls(ffiwrapper.ProofVerifier) - vmRand = NewFixedRand() cs = store.NewChainStore(bs, bs, ds, syscalls, nil) sm = stmgr.NewStateManager(cs) ) + 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([]store.BlockMessages, 0, len(tipset.Blocks)) @@ -122,15 +141,23 @@ func (d *Driver) ExecuteTipset(bs blockstore.Blockstore, ds ds.Batching, preroot var ( messages []*types.Message results []*vm.ApplyRet - - basefee = abi.NewTokenAmount(tipset.BaseFee.Int64()) ) - postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), parentEpoch, preroot, blocks, execEpoch, vmRand, func(_ cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { + recordOutputs := func(_ cid.Cid, msg *types.Message, ret *vm.ApplyRet) error { messages = append(messages, msg) results = append(results, ret) return nil - }, basefee, nil) + } + postcid, receiptsroot, err := sm.ApplyBlocks(context.Background(), + params.ParentEpoch, + params.Preroot, + blocks, + params.ExecEpoch, + params.Rand, + recordOutputs, + params.BaseFee, + nil, + ) if err != nil { return nil, err diff --git a/conformance/runner.go b/conformance/runner.go index 6f9d73305..7ca7ea2c5 100644 --- a/conformance/runner.go +++ b/conformance/runner.go @@ -109,7 +109,13 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. for i, ts := range vector.ApplyTipsets { ts := ts // capture execEpoch := baseEpoch + abi.ChainEpoch(ts.EpochOffset) - ret, err := driver.ExecuteTipset(bs, tmpds, root, prevEpoch, &ts, execEpoch) + ret, err := driver.ExecuteTipset(bs, tmpds, ExecuteTipsetParams{ + Preroot: root, + ParentEpoch: prevEpoch, + Tipset: &ts, + ExecEpoch: execEpoch, + Rand: NewReplayingRand(r, vector.Randomness), + }) if err != nil { r.Fatalf("failed to apply tipset %d message: %s", i, err) } From 3fa51f08e1892354936c5be88b53179a0c0b6ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 15 Dec 2020 17:08:49 +0000 Subject: [PATCH 017/246] fix lint error; stamp metadata on vector. --- cmd/tvx/extract_tipset.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/cmd/tvx/extract_tipset.go b/cmd/tvx/extract_tipset.go index c1ab8fe86..20c35f8ac 100644 --- a/cmd/tvx/extract_tipset.go +++ b/cmd/tvx/extract_tipset.go @@ -10,7 +10,6 @@ import ( "github.com/filecoin-project/test-vectors/schema" "github.com/ipfs/go-cid" - "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/conformance" ) @@ -133,10 +132,24 @@ func doExtractTipset(opts extractOpts) error { return err } + version, err := FullAPI.Version(ctx) + if err != nil { + return err + } + + ntwkName, err := FullAPI.StateNetworkName(ctx) + if err != nil { + return err + } + vector := schema.TestVector{ Class: schema.ClassTipset, Meta: &schema.Metadata{ ID: opts.id, + Gen: []schema.GenerationData{ + {Source: fmt.Sprintf("network:%s", ntwkName)}, + {Source: fmt.Sprintf("tipset:%s", ts.Key())}, + {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, }, Selector: schema.Selector{ schema.SelectorMinProtocolVersion: codename, @@ -161,16 +174,12 @@ func doExtractTipset(opts extractOpts) error { }, } - // do nothing with this for now. - var traces = make([]types.ExecutionTrace, 0, len(result.AppliedResults)) for _, res := range result.AppliedResults { vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{ ExitCode: int64(res.ExitCode), ReturnValue: res.Return, GasUsed: res.GasUsed, }) - - traces = append(traces, res.ExecutionTrace) } return writeVector(vector, opts.file) From d2b7e7b2ab1e166bead438dc72d9eed90646a199 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Tue, 15 Dec 2020 18:39:50 +0100 Subject: [PATCH 018/246] v1.2.3 changelog, version bump --- CHANGELOG.md | 32 ++++++++++++++++++++++++++++++++ build/version.go | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd14f8622..7f87f06e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,37 @@ # Lotus changelog +# 1.2.3 / 2020-12-15 + +This is an optional Lotus release that introduces many performance improvements, bugfixes, and UX improvements. + +## Changes + +- When waiting for deal commit messages, ignore unsuccessful messages (https://github.com/filecoin-project/lotus/pull/5189) +- Bigger copy buffer size for stores (https://github.com/filecoin-project/lotus/pull/5177) +- Print MinPieceSize when querying ask (https://github.com/filecoin-project/lotus/pull/5178) +- Optimize miner info & sectors list loading (https://github.com/filecoin-project/lotus/pull/5176) +- Allow miners to filter (un)verified deals (https://github.com/filecoin-project/lotus/pull/5094) +- Fix curSealing out of MaxSealingSectors limit (https://github.com/filecoin-project/lotus/pull/5166) +- Add mpool pending from / to filter (https://github.com/filecoin-project/lotus/pull/5169) +- Add metrics for delayed blocks (https://github.com/filecoin-project/lotus/pull/5171) +- Fix PushUntrusted publishing -- the message is local (https://github.com/filecoin-project/lotus/pull/5173) +- Avoid potential hang in events API when starting event listener (https://github.com/filecoin-project/lotus/pull/5159) +- Show data transfer ID in list-deals (https://github.com/filecoin-project/lotus/pull/5150) +- Fix events API mutex locking (https://github.com/filecoin-project/lotus/pull/5160) +- Message pool refactors (https://github.com/filecoin-project/lotus/pull/5162) +- Fix lotus-shed cid output (https://github.com/filecoin-project/lotus/pull/5072) +- Use FundManager to withdraw funds, add MarketWithdraw API (https://github.com/filecoin-project/lotus/pull/5112) +- Add keygen outfile (https://github.com/filecoin-project/lotus/pull/5118) +- Update sr2 stat aggregation (https://github.com/filecoin-project/lotus/pull/5114) +- Fix miner control address lookup (https://github.com/filecoin-project/lotus/pull/5119) +- Fix send with declared nonce 0 (https://github.com/filecoin-project/lotus/pull/5111) +- Introduce memory watchdog; LOTUS_MAX_HEAP (https://github.com/filecoin-project/lotus/pull/5101) +- Miner control address config for (pre)commits (https://github.com/filecoin-project/lotus/pull/5103) +- Delete repeated call func (https://github.com/filecoin-project/lotus/pull/5099) +- lotus-shed ledger show command (https://github.com/filecoin-project/lotus/pull/5098) +- Log a message when there aren't enough peers for sync (https://github.com/filecoin-project/lotus/pull/5105) +- Miner code cleanup (https://github.com/filecoin-project/lotus/pull/5107) + # 1.2.2 / 2020-12-03 This is an optional Lotus release that introduces various improvements to the mining logic and deal-making workflow, as well as several new UX features. diff --git a/build/version.go b/build/version.go index 895d92a49..ce180f33f 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.2.2" +const BuildVersion = "1.2.3" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From 2af1283c65b6c1c5d4cccfe938e87e43eccad72f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Tue, 15 Dec 2020 17:44:56 +0000 Subject: [PATCH 019/246] tvx exec: flag to fallback to API blockstore. --- cmd/tvx/exec.go | 18 ++++++++++++++++-- cmd/tvx/main.go | 2 +- conformance/runner.go | 32 +++++++++++++++++++++++++++----- 3 files changed, 44 insertions(+), 8 deletions(-) diff --git a/cmd/tvx/exec.go b/cmd/tvx/exec.go index 89ad23913..f1ef91315 100644 --- a/cmd/tvx/exec.go +++ b/cmd/tvx/exec.go @@ -16,7 +16,8 @@ import ( ) var execFlags struct { - file string + file string + fallbackBlockstore bool } var execCmd = &cli.Command{ @@ -30,10 +31,23 @@ var execCmd = &cli.Command{ TakesFile: true, Destination: &execFlags.file, }, + &cli.BoolFlag{ + Name: "fallback-blockstore", + Usage: "sets the full node API as a fallback blockstore; use this if you're transplanting vectors and get block not found errors", + Destination: &execFlags.fallbackBlockstore, + }, }, } -func runExecLotus(_ *cli.Context) error { +func runExecLotus(c *cli.Context) error { + if execFlags.fallbackBlockstore { + if err := initialize(c); err != nil { + return fmt.Errorf("fallback blockstore was enabled, but could not resolve lotus API endpoint: %w", err) + } + defer destroy(c) //nolint:errcheck + conformance.FallbackBlockstoreGetter = FullAPI + } + if file := execFlags.file; file != "" { // we have a single test vector supplied as a file. file, err := os.Open(file) diff --git a/cmd/tvx/main.go b/cmd/tvx/main.go index 8de851ed5..8dae39958 100644 --- a/cmd/tvx/main.go +++ b/cmd/tvx/main.go @@ -102,7 +102,7 @@ func initialize(c *cli.Context) error { // Make the API client. var err error if FullAPI, Closer, err = lcli.GetFullNodeAPI(c); err != nil { - err = fmt.Errorf("failed to locate Lotus node; ") + err = fmt.Errorf("failed to locate Lotus node; err: %w", err) } return err } diff --git a/conformance/runner.go b/conformance/runner.go index 7ca7ea2c5..6eff25274 100644 --- a/conformance/runner.go +++ b/conformance/runner.go @@ -14,6 +14,7 @@ import ( "github.com/fatih/color" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" + blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" ds "github.com/ipfs/go-datastore" @@ -29,6 +30,14 @@ import ( "github.com/filecoin-project/lotus/lib/blockstore" ) +// FallbackBlockstoreGetter is a fallback blockstore to use for resolving CIDs +// unknown to the test vector. This is rarely used, usually only needed +// when transplanting vectors across versions. This is an interface tighter +// than ChainModuleAPI. It can be backed by a FullAPI client. +var FallbackBlockstoreGetter interface { + ChainReadObj(context.Context, cid.Cid) ([]byte, error) +} + // ExecuteMessageVector executes a message-class test vector. func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) { var ( @@ -38,7 +47,7 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema ) // Load the CAR into a new temporary Blockstore. - bs, err := LoadVectorCAR(vector.CAR) + bs, err := LoadBlockstore(vector.CAR) if err != nil { r.Fatalf("failed to load the vector CAR: %w", err) } @@ -95,7 +104,7 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. ) // Load the vector CAR into a new temporary Blockstore. - bs, err := LoadVectorCAR(vector.CAR) + bs, err := LoadBlockstore(vector.CAR) if err != nil { r.Fatalf("failed to load the vector CAR: %w", err) } @@ -117,7 +126,7 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. Rand: NewReplayingRand(r, vector.Randomness), }) if err != nil { - r.Fatalf("failed to apply tipset %d message: %s", i, err) + r.Fatalf("failed to apply tipset %d: %s", i, err) } for j, v := range ret.AppliedResults { @@ -254,8 +263,8 @@ func writeStateToTempCAR(bs blockstore.Blockstore, roots ...cid.Cid) (string, er return tmp.Name(), nil } -func LoadVectorCAR(vectorCAR schema.Base64EncodedBytes) (blockstore.Blockstore, error) { - bs := blockstore.NewTemporary() +func LoadBlockstore(vectorCAR schema.Base64EncodedBytes) (blockstore.Blockstore, error) { + bs := blockstore.Blockstore(blockstore.NewTemporary()) // Read the base64-encoded CAR from the vector, and inflate the gzip. buf := bytes.NewReader(vectorCAR) @@ -270,5 +279,18 @@ func LoadVectorCAR(vectorCAR schema.Base64EncodedBytes) (blockstore.Blockstore, if err != nil { return nil, fmt.Errorf("failed to load state tree car from test vector: %s", err) } + + if FallbackBlockstoreGetter != nil { + fbs := &blockstore.FallbackStore{Blockstore: bs} + fbs.SetFallback(func(ctx context.Context, c cid.Cid) (blocks.Block, error) { + b, err := FallbackBlockstoreGetter.ChainReadObj(ctx, c) + if err != nil { + return nil, err + } + return blocks.NewBlockWithCid(b, c) + }) + bs = fbs + } + return bs, nil } From 210216a94faef752078762b0fe55242992152c6f Mon Sep 17 00:00:00 2001 From: vyzo Date: Tue, 15 Dec 2020 19:46:34 +0200 Subject: [PATCH 020/246] lotus-shed blockmsgid command --- cmd/lotus-shed/blockmsgid.go | 70 ++++++++++++++++++++++++++++++++++++ cmd/lotus-shed/main.go | 1 + 2 files changed, 71 insertions(+) create mode 100644 cmd/lotus-shed/blockmsgid.go diff --git a/cmd/lotus-shed/blockmsgid.go b/cmd/lotus-shed/blockmsgid.go new file mode 100644 index 000000000..85b786ec0 --- /dev/null +++ b/cmd/lotus-shed/blockmsgid.go @@ -0,0 +1,70 @@ +package main + +import ( + "encoding/base64" + "fmt" + + blake2b "github.com/minio/blake2b-simd" + "github.com/urfave/cli/v2" + + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" +) + +var blockmsgidCmd = &cli.Command{ + Name: "blockmsgid", + Usage: "Print a block's pubsub message ID", + ArgsUsage: " ...", + Action: func(cctx *cli.Context) error { + api, closer, err := lcli.GetFullNodeAPI(cctx) + if err != nil { + return err + } + + defer closer() + ctx := lcli.ReqContext(cctx) + + for _, arg := range cctx.Args().Slice() { + blkcid, err := cid.Decode(arg) + if err != nil { + return fmt.Errorf("error decoding block cid: %w", err) + } + + blkhdr, err := api.ChainGetBlock(ctx, blkcid) + if err != nil { + return fmt.Errorf("error retrieving block header: %w", err) + } + + blkmsgs, err := api.ChainGetBlockMessages(ctx, blkcid) + if err != nil { + return fmt.Errorf("error retrieving block messages: %w", err) + } + + blkmsg := &types.BlockMsg{ + Header: blkhdr, + } + + for _, m := range blkmsgs.BlsMessages { + blkmsg.BlsMessages = append(blkmsg.BlsMessages, m.Cid()) + } + + for _, m := range blkmsgs.SecpkMessages { + blkmsg.SecpkMessages = append(blkmsg.SecpkMessages, m.Cid()) + } + + bytes, err := blkmsg.Serialize() + if err != nil { + return fmt.Errorf("error serializing BlockMsg: %w", err) + } + + msgId := blake2b.Sum256(bytes) + msgId64 := base64.StdEncoding.EncodeToString(msgId[:]) + + fmt.Println(msgId64) + } + + return nil + }, +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 19067f8c9..10b2b4d89 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -50,6 +50,7 @@ func main() { electionCmd, rpcCmd, cidCmd, + blockmsgidCmd, } app := &cli.App{ From 6c6711e638b3d836ceffdc07a932e9fe08f8b78f Mon Sep 17 00:00:00 2001 From: whyrusleeping Date: Tue, 15 Dec 2020 13:27:22 -0800 Subject: [PATCH 021/246] fix lock propose --- cli/multisig.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/multisig.go b/cli/multisig.go index 8abae5182..e18c48b8f 100644 --- a/cli/multisig.go +++ b/cli/multisig.go @@ -1178,7 +1178,7 @@ var msigLockProposeCmd = &cli.Command{ params, actErr := actors.SerializeParams(&msig2.LockBalanceParams{ StartEpoch: abi.ChainEpoch(start), UnlockDuration: abi.ChainEpoch(duration), - Amount: abi.NewTokenAmount(amount.Int64()), + Amount: big.Int(amount), }) if actErr != nil { From 1f754bdb78329d54c6c1b27deaa9f5c20cb443b7 Mon Sep 17 00:00:00 2001 From: Steven Allen Date: Mon, 14 Dec 2020 20:51:43 -0800 Subject: [PATCH 022/246] disable gas burning for window post messages While over-estimation fees and miner tips are still paid, gas is no longer burnt for direct, successful window PoSt messages. Usually, gas is burnt to prevent an attacker from spamming the network and to allow clients to "price" messages (using the base fee cap) based on how urgently they need them to be processed. However: 1. Window PoSt is already a "proof of work". 2. Miners need to submit WindowedPoSts on-time so all window post messages are urgent. 3. Work is already under way to move window post verification off-chain (making it effectively free). This change simply introduces the "free" part a bit earlier. --- build/params_2k.go | 1 + build/params_mainnet.go | 3 +++ build/params_testground.go | 1 + chain/vm/burn.go | 9 +++++++-- chain/vm/burn_test.go | 2 +- chain/vm/vm.go | 32 +++++++++++++++++++++++++++++++- 6 files changed, 44 insertions(+), 4 deletions(-) diff --git a/build/params_2k.go b/build/params_2k.go index c86de7ffa..1155488b4 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -24,6 +24,7 @@ var UpgradeLiftoffHeight = abi.ChainEpoch(-5) const UpgradeKumquatHeight = 15 const UpgradeCalicoHeight = 20 const UpgradePersianHeight = 25 +const UpgradeClausHeight = 30 var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/build/params_mainnet.go b/build/params_mainnet.go index 3d8f5e374..db7166eec 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -41,6 +41,9 @@ const UpgradeKumquatHeight = 170000 const UpgradeCalicoHeight = 265200 const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 60) +// 2020-12-22T02:00:00Z +const UpgradeClausHeight = 343200 + func init() { policy.SetConsensusMinerMinPower(abi.NewStoragePower(10 << 40)) diff --git a/build/params_testground.go b/build/params_testground.go index 0ee986a7c..dbfdf223c 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -90,6 +90,7 @@ var ( UpgradeKumquatHeight abi.ChainEpoch = -6 UpgradeCalicoHeight abi.ChainEpoch = -7 UpgradePersianHeight abi.ChainEpoch = -8 + UpgradeClausHeight abi.ChainEpoch = -9 DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, diff --git a/chain/vm/burn.go b/chain/vm/burn.go index 9f9b95755..a214d198b 100644 --- a/chain/vm/burn.go +++ b/chain/vm/burn.go @@ -67,7 +67,7 @@ func ComputeGasOverestimationBurn(gasUsed, gasLimit int64) (int64, int64) { return gasLimit - gasUsed - gasToBurn.Int64(), gasToBurn.Int64() } -func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi.TokenAmount) GasOutputs { +func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi.TokenAmount, chargeNetworkFee bool) GasOutputs { gasUsedBig := big.NewInt(gasUsed) out := ZeroGasOutputs() @@ -76,7 +76,12 @@ func ComputeGasOutputs(gasUsed, gasLimit int64, baseFee, feeCap, gasPremium abi. baseFeeToPay = feeCap out.MinerPenalty = big.Mul(big.Sub(baseFee, feeCap), gasUsedBig) } - out.BaseFeeBurn = big.Mul(baseFeeToPay, gasUsedBig) + + // If chargeNetworkFee is disabled, just skip computing the BaseFeeBurn. However, + // we charge all the other fees regardless. + if chargeNetworkFee { + out.BaseFeeBurn = big.Mul(baseFeeToPay, gasUsedBig) + } minerTip := gasPremium if big.Cmp(big.Add(baseFeeToPay, minerTip), feeCap) > 0 { diff --git a/chain/vm/burn_test.go b/chain/vm/burn_test.go index 58e133605..e4fc69aff 100644 --- a/chain/vm/burn_test.go +++ b/chain/vm/burn_test.go @@ -63,7 +63,7 @@ func TestGasOutputs(t *testing.T) { for _, test := range tests { test := test t.Run(fmt.Sprintf("%v", test), func(t *testing.T) { - output := ComputeGasOutputs(test.used, test.limit, baseFee, types.NewInt(test.feeCap), types.NewInt(test.premium)) + output := ComputeGasOutputs(test.used, test.limit, baseFee, types.NewInt(test.feeCap), types.NewInt(test.premium), true) i2s := func(i uint64) string { return fmt.Sprintf("%d", i) } diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 0919b8e8a..52af783ad 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -32,6 +32,7 @@ import ( "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/aerrors" "github.com/filecoin-project/lotus/chain/actors/builtin/account" + "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/builtin/reward" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/types" @@ -561,7 +562,13 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, if gasUsed < 0 { gasUsed = 0 } - gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium) + + burn, err := vm.shouldBurn(st, msg, errcode) + if err != nil { + return nil, xerrors.Errorf("deciding whether should burn failed: %w", err) + } + + gasOutputs := ComputeGasOutputs(gasUsed, msg.GasLimit, vm.baseFee, msg.GasFeeCap, msg.GasPremium, burn) if err := vm.transferFromGasHolder(builtin.BurntFundsActorAddr, gasHolder, gasOutputs.BaseFeeBurn); err != nil { @@ -599,6 +606,29 @@ func (vm *VM) ApplyMessage(ctx context.Context, cmsg types.ChainMsg) (*ApplyRet, }, nil } +func (vm *VM) shouldBurn(st *state.StateTree, msg *types.Message, errcode exitcode.ExitCode) (bool, error) { + // Check to see if we should burn funds. We avoid burning on successful + // window post. This won't catch _indirect_ window post calls, but this + // is the best we can get for now. + if vm.blockHeight > build.UpgradeClausHeight && errcode == exitcode.Ok && msg.Method == miner.Methods.SubmitWindowedPoSt { + // Ok, we've checked the _method_, but we still need to check + // the target actor. It would be nice if we could just look at + // the trace, but I'm not sure if that's safe? + if toActor, err := st.GetActor(msg.To); err != nil { + // If the actor wasn't found, we probably deleted it or something. Move on. + if !xerrors.Is(err, types.ErrActorNotFound) { + // Otherwise, this should never fail and something is very wrong. + return false, xerrors.Errorf("failed to lookup target actor: %w", err) + } + } else if builtin.IsStorageMinerActor(toActor.Code) { + // Ok, this is a storage miner and we've processed a window post. Remove the burn. + return false, nil + } + } + + return true, nil +} + func (vm *VM) ActorBalance(addr address.Address) (types.BigInt, aerrors.ActorError) { act, err := vm.cstate.GetActor(addr) if err != nil { From d04c01939d85600a6a8bf37a59c047904a26d23d Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 16 Dec 2020 00:28:29 -0500 Subject: [PATCH 023/246] Lotus version 1.3.0 --- CHANGELOG.md | 11 ++++++++++- build/version.go | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f87f06e1..95e77c03c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Lotus changelog +# 1.3.0 / 2020-12-16 + +This is a mandatory release of Lotus that introduces the third post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 343200, before which time all nodes must have updated to this release (or later). The change that breaks consensus is an implementation of FIP-0009(https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0009.md). + +## Changes + +- Disable gas burning for window post messages (https://github.com/filecoin-project/lotus/pull/5200) +- fix lock propose (https://github.com/filecoin-project/lotus/pull/5197) + # 1.2.3 / 2020-12-15 This is an optional Lotus release that introduces many performance improvements, bugfixes, and UX improvements. @@ -120,7 +129,7 @@ This is a very small release of Lotus that fixes an issue users are experiencing # 1.2.0 / 2020-11-18 -This is a mandatory release of Lotus that introduces the second post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 265200, before which time all nodes must have update to this release (or later). This release also bumps the required version of Go to 1.15. +This is a mandatory release of Lotus that introduces the second post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 265200, before which time all nodes must have updated to this release (or later). This release also bumps the required version of Go to 1.15. The changes that break consensus are: diff --git a/build/version.go b/build/version.go index ce180f33f..50f48a951 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.2.3" +const BuildVersion = "1.3.0" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From f6cf27c7815b446f63aedd3523589672b228113b Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Wed, 16 Dec 2020 10:15:38 -0300 Subject: [PATCH 024/246] improve mocked readpiece Signed-off-by: Ignacio Hagopian --- extern/sector-storage/mock/mock.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/sector-storage/mock/mock.go b/extern/sector-storage/mock/mock.go index 6179f40d7..d57fdf0fc 100644 --- a/extern/sector-storage/mock/mock.go +++ b/extern/sector-storage/mock/mock.go @@ -347,7 +347,7 @@ func generateFakePoSt(sectorInfo []proof2.SectorInfo, rpt func(abi.RegisteredSea } func (mgr *SectorMgr) ReadPiece(ctx context.Context, w io.Writer, sectorID storage.SectorRef, offset storiface.UnpaddedByteIndex, size abi.UnpaddedPieceSize, randomness abi.SealRandomness, c cid.Cid) error { - if len(mgr.sectors[sectorID.ID].pieces) > 1 || offset != 0 { + if offset != 0 { panic("implme") } From b8bba15ed22c3303b2f4c593fde71a732f89f6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 17 Dec 2020 11:53:11 +0100 Subject: [PATCH 025/246] markets: Fix deal expired panic --- markets/storageadapter/client.go | 2 +- markets/storageadapter/provider.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/markets/storageadapter/client.go b/markets/storageadapter/client.go index 4d00ab258..88a50931a 100644 --- a/markets/storageadapter/client.go +++ b/markets/storageadapter/client.go @@ -264,7 +264,7 @@ func (c *ClientNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID a // and the chain has advanced to the confidence height stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts2.Height() { + if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { onDealExpired(nil) return false, nil } diff --git a/markets/storageadapter/provider.go b/markets/storageadapter/provider.go index 79fd718f8..4ce32d2bf 100644 --- a/markets/storageadapter/provider.go +++ b/markets/storageadapter/provider.go @@ -344,7 +344,7 @@ func (n *ProviderNodeAdapter) OnDealExpiredOrSlashed(ctx context.Context, dealID // and the chain has advanced to the confidence height stateChanged := func(ts *types.TipSet, ts2 *types.TipSet, states events.StateChange, h abi.ChainEpoch) (more bool, err error) { // Check if the deal has already expired - if sd.Proposal.EndEpoch <= ts2.Height() { + if ts2 == nil || sd.Proposal.EndEpoch <= ts2.Height() { onDealExpired(nil) return false, nil } From fda3b152bdacf694980e382cc7218c46a2d0da07 Mon Sep 17 00:00:00 2001 From: Dan Shao Date: Thu, 17 Dec 2020 20:03:09 +0800 Subject: [PATCH 026/246] Skip checking terminated sectors provable --- cmd/lotus-storage-miner/proving.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/lotus-storage-miner/proving.go b/cmd/lotus-storage-miner/proving.go index 5d176a754..6f379e99c 100644 --- a/cmd/lotus-storage-miner/proving.go +++ b/cmd/lotus-storage-miner/proving.go @@ -446,7 +446,7 @@ var provingCheckProvableCmd = &cli.Command{ for parIdx, par := range partitions { sectors := make(map[abi.SectorNumber]struct{}) - sectorInfos, err := api.StateMinerSectors(ctx, addr, &par.AllSectors, types.EmptyTSK) + sectorInfos, err := api.StateMinerSectors(ctx, addr, &par.LiveSectors, types.EmptyTSK) if err != nil { return err } From 9892c60ed0bfaa9293babae79b5cf54c689a2425 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Thu, 17 Dec 2020 17:17:07 +0100 Subject: [PATCH 027/246] Show verified power in miner sector list --- cmd/lotus-storage-miner/sectors.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cmd/lotus-storage-miner/sectors.go b/cmd/lotus-storage-miner/sectors.go index 1c3e4858c..5cf12b991 100644 --- a/cmd/lotus-storage-miner/sectors.go +++ b/cmd/lotus-storage-miner/sectors.go @@ -248,6 +248,7 @@ var sectorsListCmd = &cli.Command{ tablewriter.Col("Events"), tablewriter.Col("Deals"), tablewriter.Col("DealWeight"), + tablewriter.Col("VerifiedPower"), tablewriter.NewLineCol("Error"), tablewriter.NewLineCol("RecoveryTimeout")) @@ -267,9 +268,11 @@ var sectorsListCmd = &cli.Command{ _, inSSet := commitedIDs[s] _, inASet := activeIDs[s] - dw := .0 + dw, vp := .0, .0 if st.Expiration-st.Activation > 0 { - dw = float64(big.Div(st.DealWeight, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + rdw := big.Add(st.DealWeight, st.VerifiedDealWeight) + dw = float64(big.Div(rdw, big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) + vp = float64(big.Div(big.Mul(st.VerifiedDealWeight, big.NewInt(9)), big.NewInt(int64(st.Expiration-st.Activation))).Uint64()) } var deals int @@ -308,6 +311,9 @@ var sectorsListCmd = &cli.Command{ if !fast && deals > 0 { m["DealWeight"] = units.BytesSize(dw) + if vp > 0 { + m["VerifiedPower"] = color.GreenString(units.BytesSize(vp)) + } } if st.Early > 0 { From f24d401b6156e2ab2df52d35af1e912be00a1b00 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 18 Dec 2020 17:46:49 +0800 Subject: [PATCH 028/246] add bench env print --- cmd/lotus-bench/main.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmd/lotus-bench/main.go b/cmd/lotus-bench/main.go index b246aedbb..551c32754 100644 --- a/cmd/lotus-bench/main.go +++ b/cmd/lotus-bench/main.go @@ -446,6 +446,17 @@ var sealBenchCmd = &cli.Command{ bo.VerifyWindowPostHot = verifyWindowpost2.Sub(verifyWindowpost1) } + fmt.Println("environment variable list:") + for _, envKey := range []string{"BELLMAN_NO_GPU", "FIL_PROOFS_MAXIMIZE_CACHING", "FIL_PROOFS_USE_GPU_COLUMN_BUILDER", + "FIL_PROOFS_USE_GPU_TREE_BUILDER", "FIL_PROOFS_USE_MULTICORE_SDR", "BELLMAN_CUSTOM_GPU"} { + envValue, found := os.LookupEnv(envKey) + if found { + fmt.Printf("%s=%s\n", envKey, envValue) + } else { + fmt.Printf("%s not set\n", envKey) + } + } + if c.Bool("json-out") { data, err := json.MarshalIndent(bo, "", " ") if err != nil { From 984e4e996374efd267114bbffa54cf3a8f55a5d5 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 18 Dec 2020 19:35:11 +0800 Subject: [PATCH 029/246] add limit and rate for chain noise --- cmd/chain-noise/main.go | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/cmd/chain-noise/main.go b/cmd/chain-noise/main.go index 81586e1b2..e85a468a6 100644 --- a/cmd/chain-noise/main.go +++ b/cmd/chain-noise/main.go @@ -27,6 +27,16 @@ func main() { Hidden: true, Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME }, + &cli.IntFlag{ + Name: "limit", + Usage: "spam transaction count limit, <= 0 is no limit", + Value: 0, + }, + &cli.IntFlag{ + Name: "rate", + Usage: "spam transaction rate, count per second", + Value: 5, + }, }, Commands: []*cli.Command{runCmd}, } @@ -52,11 +62,17 @@ var runCmd = &cli.Command{ defer closer() ctx := lcli.ReqContext(cctx) - return sendSmallFundsTxs(ctx, api, addr, 5) + rate := cctx.Int("rate") + if rate <= 0 { + rate = 5 + } + limit := cctx.Int("limit") + + return sendSmallFundsTxs(ctx, api, addr, rate, limit) }, } -func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Address, rate int) error { +func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Address, rate, limit int) error { var sendSet []address.Address for i := 0; i < 20; i++ { naddr, err := api.WalletNew(ctx, types.KTSecp256k1) @@ -66,9 +82,14 @@ func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Addre sendSet = append(sendSet, naddr) } + count := limit tick := build.Clock.Ticker(time.Second / time.Duration(rate)) for { + if count <= 0 && limit > 0 { + fmt.Printf("%d messages sent.\n", limit) + return nil + } select { case <-tick.C: msg := &types.Message{ @@ -81,6 +102,7 @@ func sendSmallFundsTxs(ctx context.Context, api api.FullNode, from address.Addre if err != nil { return err } + count-- fmt.Println("Message sent: ", smsg.Cid()) case <-ctx.Done(): return nil From 22884f18f11faab09b0f8279db3fbb835b429007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 19 Dec 2020 20:44:16 +0100 Subject: [PATCH 030/246] define v9 upgrade --- build/params_mainnet.go | 2 ++ chain/stmgr/forks.go | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/build/params_mainnet.go b/build/params_mainnet.go index db7166eec..195f0dd01 100644 --- a/build/params_mainnet.go +++ b/build/params_mainnet.go @@ -41,6 +41,8 @@ const UpgradeKumquatHeight = 170000 const UpgradeCalicoHeight = 265200 const UpgradePersianHeight = UpgradeCalicoHeight + (builtin2.EpochsInHour * 60) +const UpgradeOrangeHeight = 336458 + // 2020-12-22T02:00:00Z const UpgradeClausHeight = 343200 diff --git a/chain/stmgr/forks.go b/chain/stmgr/forks.go index 3bdf23f59..7fe12d2a8 100644 --- a/chain/stmgr/forks.go +++ b/chain/stmgr/forks.go @@ -96,6 +96,10 @@ func DefaultUpgradeSchedule() UpgradeSchedule { Height: build.UpgradePersianHeight, Network: network.Version8, Migration: nil, + }, { + Height: build.UpgradeOrangeHeight, + Network: network.Version9, + Migration: nil, }} if build.UpgradeActorsV2Height == math.MaxInt64 { // disable actors upgrade From 24dd6e01d7ececfbd6664138499b47df37dff9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 19 Dec 2020 20:53:08 +0100 Subject: [PATCH 031/246] disable SF for finality epochs --- chain/gen/slashfilter/slashfilter.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/chain/gen/slashfilter/slashfilter.go b/chain/gen/slashfilter/slashfilter.go index ee0435156..a73f3e2f6 100644 --- a/chain/gen/slashfilter/slashfilter.go +++ b/chain/gen/slashfilter/slashfilter.go @@ -2,6 +2,7 @@ package slashfilter import ( "fmt" + "github.com/filecoin-project/lotus/build" "golang.org/x/xerrors" @@ -26,6 +27,10 @@ func New(dstore ds.Batching) *SlashFilter { } func (f *SlashFilter) MinedBlock(bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error { + if bh.Height < build.UpgradeOrangeHeight+build.Finality { + return nil + } + epochKey := ds.NewKey(fmt.Sprintf("/%s/%d", bh.Miner, bh.Height)) { // double-fork mining (2 blocks at one epoch) From a3b325db5db1af0764a2dc67666dad3d1e51a099 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Sat, 19 Dec 2020 21:00:12 +0100 Subject: [PATCH 032/246] Disable consensus faults during upgrade orange Signed-off-by: Jakub Sztandera --- chain/gen/slashfilter/slashfilter.go | 5 ++++- chain/vm/syscalls.go | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/chain/gen/slashfilter/slashfilter.go b/chain/gen/slashfilter/slashfilter.go index a73f3e2f6..cb12ddb08 100644 --- a/chain/gen/slashfilter/slashfilter.go +++ b/chain/gen/slashfilter/slashfilter.go @@ -2,6 +2,7 @@ package slashfilter import ( "fmt" + "github.com/filecoin-project/lotus/build" "golang.org/x/xerrors" @@ -27,7 +28,9 @@ func New(dstore ds.Batching) *SlashFilter { } func (f *SlashFilter) MinedBlock(bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error { - if bh.Height < build.UpgradeOrangeHeight+build.Finality { + if bh.Height > build.UpgradeOrangeHeight-build.Finality && + bh.Height < build.UpgradeOrangeHeight+build.Finality { + // consenssus faults disabled during Upgrade Orange return nil } diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index ba6ee2f1d..b063e5f25 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -17,6 +17,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/crypto" "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/actors/adt" "github.com/filecoin-project/lotus/chain/actors/builtin/miner" "github.com/filecoin-project/lotus/chain/actors/policy" @@ -107,11 +108,20 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse return nil, xerrors.Errorf("cannot decode second block header: %f", decodeErr) } + // workaround chain halt + if blockA.Height > build.UpgradeOrangeHeight-build.Finality && + blockA.Height < build.UpgradeOrangeHeight+build.Finality { + return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") + } + if blockB.Height > build.UpgradeOrangeHeight-build.Finality && + blockB.Height < build.UpgradeOrangeHeight+build.Finality { + return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") + } + // are blocks the same? if blockA.Cid().Equals(blockB.Cid()) { return nil, fmt.Errorf("no consensus fault: submitted blocks are the same") } - // (1) check conditions necessary to any consensus fault // were blocks mined by same miner? From c1aafb7b71ffeb4470bf838b8f44b0867ffe1142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 19 Dec 2020 21:29:12 +0100 Subject: [PATCH 033/246] Add missing v9 to chain/actors/version --- chain/actors/version.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/actors/version.go b/chain/actors/version.go index 1cafa45c9..1d9f1f8a2 100644 --- a/chain/actors/version.go +++ b/chain/actors/version.go @@ -18,7 +18,7 @@ func VersionForNetwork(version network.Version) Version { switch version { case network.Version0, network.Version1, network.Version2, network.Version3: return Version0 - case network.Version4, network.Version5, network.Version6, network.Version7, network.Version8: + case network.Version4, network.Version5, network.Version6, network.Version7, network.Version8, network.Version9: return Version2 default: panic(fmt.Sprintf("unsupported network version %d", version)) From 594737ff6c5cd346cdca968e898e829e2b336d5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 19 Dec 2020 21:34:57 +0100 Subject: [PATCH 034/246] Upgrade specs-actors --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e6bc14a1b..f99e44de4 100644 --- a/go.mod +++ b/go.mod @@ -43,7 +43,7 @@ require ( github.com/filecoin-project/go-statestore v0.1.0 github.com/filecoin-project/go-storedcounter v0.0.0-20200421200003-1c99c62e8a5b github.com/filecoin-project/specs-actors v0.9.13 - github.com/filecoin-project/specs-actors/v2 v2.3.2 + github.com/filecoin-project/specs-actors/v2 v2.3.3 github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 github.com/filecoin-project/test-vectors/schema v0.0.5 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 diff --git a/go.sum b/go.sum index 644904013..847f66933 100644 --- a/go.sum +++ b/go.sum @@ -296,6 +296,8 @@ github.com/filecoin-project/specs-actors v0.9.13/go.mod h1:TS1AW/7LbG+615j4NsjMK github.com/filecoin-project/specs-actors/v2 v2.0.1/go.mod h1:v2NZVYinNIKA9acEMBm5wWXxqv5+frFEbekBFemYghY= github.com/filecoin-project/specs-actors/v2 v2.3.2 h1:2Vcf4CGa29kRh4JJ02m+FbvD/p3YNnLGsaHfw7Uj49g= github.com/filecoin-project/specs-actors/v2 v2.3.2/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= +github.com/filecoin-project/specs-actors/v2 v2.3.3 h1:5Pd6pjU7VjUye+Hz4gYBCPAFdBxtEbHsgGYvWmfc83w= +github.com/filecoin-project/specs-actors/v2 v2.3.3/go.mod h1:UuJQLoTx/HPvvWeqlIFmC/ywlOLHNe8SNQ3OunFbu2Y= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506 h1:Ur/l2+6qN+lQiqjozWWc5p9UDaAMDZKTlDS98oRnlIw= github.com/filecoin-project/specs-storage v0.1.1-0.20201105051918-5188d9774506/go.mod h1:nJRRM7Aa9XVvygr3W9k6xGF46RWzr2zxF/iGoAIfA/g= github.com/filecoin-project/test-vectors/schema v0.0.5 h1:w3zHQhzM4pYxJDl21avXjOKBLF8egrvwUwjpT8TquDg= From 420888aeaeb035d708c9dc2ca57182aff81c3362 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Sat, 19 Dec 2020 21:35:14 +0100 Subject: [PATCH 035/246] Add UpgradeOrange to params_2k and params_testground Signed-off-by: Jakub Sztandera --- build/params_2k.go | 1 + build/params_testground.go | 1 + 2 files changed, 2 insertions(+) diff --git a/build/params_2k.go b/build/params_2k.go index 1155488b4..bcce509d7 100644 --- a/build/params_2k.go +++ b/build/params_2k.go @@ -24,6 +24,7 @@ var UpgradeLiftoffHeight = abi.ChainEpoch(-5) const UpgradeKumquatHeight = 15 const UpgradeCalicoHeight = 20 const UpgradePersianHeight = 25 +const UpgradeOrangeHeight = 27 const UpgradeClausHeight = 30 var DrandSchedule = map[abi.ChainEpoch]DrandEnum{ diff --git a/build/params_testground.go b/build/params_testground.go index dbfdf223c..d0e54e3f2 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -91,6 +91,7 @@ var ( UpgradeCalicoHeight abi.ChainEpoch = -7 UpgradePersianHeight abi.ChainEpoch = -8 UpgradeClausHeight abi.ChainEpoch = -9 + UpgradeOrangeHeight abi.ChainEpoch = -10 DrandSchedule = map[abi.ChainEpoch]DrandEnum{ 0: DrandMainnet, From c7e3e9fa7f893da22a08678970a8a0058a7fb8c5 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Sat, 19 Dec 2020 21:51:07 +0100 Subject: [PATCH 036/246] Add IsNearUpgrade Signed-off-by: Jakub Sztandera --- chain/gen/slashfilter/slashfilter.go | 4 +--- chain/vm/syscalls.go | 6 ++---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/chain/gen/slashfilter/slashfilter.go b/chain/gen/slashfilter/slashfilter.go index cb12ddb08..5edcd5439 100644 --- a/chain/gen/slashfilter/slashfilter.go +++ b/chain/gen/slashfilter/slashfilter.go @@ -28,9 +28,7 @@ func New(dstore ds.Batching) *SlashFilter { } func (f *SlashFilter) MinedBlock(bh *types.BlockHeader, parentEpoch abi.ChainEpoch) error { - if bh.Height > build.UpgradeOrangeHeight-build.Finality && - bh.Height < build.UpgradeOrangeHeight+build.Finality { - // consenssus faults disabled during Upgrade Orange + if build.IsNearUpgrade(bh.Height, build.UpgradeOrangeHeight) { return nil } diff --git a/chain/vm/syscalls.go b/chain/vm/syscalls.go index b063e5f25..0bcfe10a7 100644 --- a/chain/vm/syscalls.go +++ b/chain/vm/syscalls.go @@ -109,12 +109,10 @@ func (ss *syscallShim) VerifyConsensusFault(a, b, extra []byte) (*runtime2.Conse } // workaround chain halt - if blockA.Height > build.UpgradeOrangeHeight-build.Finality && - blockA.Height < build.UpgradeOrangeHeight+build.Finality { + if build.IsNearUpgrade(blockA.Height, build.UpgradeOrangeHeight) { return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") } - if blockB.Height > build.UpgradeOrangeHeight-build.Finality && - blockB.Height < build.UpgradeOrangeHeight+build.Finality { + if build.IsNearUpgrade(blockB.Height, build.UpgradeOrangeHeight) { return nil, xerrors.Errorf("consensus reporting disabled around Upgrade Orange") } From e777b879a1e368bf71b83a05759cb27afcfa28f8 Mon Sep 17 00:00:00 2001 From: Jakub Sztandera Date: Sat, 19 Dec 2020 22:10:45 +0100 Subject: [PATCH 037/246] Add the missing file Signed-off-by: Jakub Sztandera --- build/isnearupgrade.go | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 build/isnearupgrade.go diff --git a/build/isnearupgrade.go b/build/isnearupgrade.go new file mode 100644 index 000000000..4273f0e9e --- /dev/null +++ b/build/isnearupgrade.go @@ -0,0 +1,9 @@ +package build + +import ( + "github.com/filecoin-project/go-state-types/abi" +) + +func IsNearUpgrade(epoch, upgradeEpoch abi.ChainEpoch) bool { + return epoch > upgradeEpoch-Finality && epoch < upgradeEpoch+Finality +} From c01d9f05df1c5794229d4a5ecd2ee1fd70574873 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Sat, 19 Dec 2020 15:44:35 -0500 Subject: [PATCH 038/246] Lotus version 1.4.0 --- CHANGELOG.md | 4 ++++ build/version.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e77c03c..19a23fcbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Lotus changelog +# 1.4.0 / 2020-12-19 + +This is a MANDATORY hotfix release of Lotus that resolves a chain halt at height 336,459 caused by nondeterminism in specs-actors. The fix is to update actors to 2.3.3 in order to incorporate this fix https://github.com/filecoin-project/specs-actors/pull/1334. + # 1.3.0 / 2020-12-16 This is a mandatory release of Lotus that introduces the third post-liftoff upgrade to the Filecoin network. The network upgrade occurs at height 343200, before which time all nodes must have updated to this release (or later). The change that breaks consensus is an implementation of FIP-0009(https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0009.md). diff --git a/build/version.go b/build/version.go index 50f48a951..140456581 100644 --- a/build/version.go +++ b/build/version.go @@ -29,7 +29,7 @@ func buildType() string { } // BuildVersion is the local build version, set by build system -const BuildVersion = "1.3.0" +const BuildVersion = "1.4.0" func UserVersion() string { return BuildVersion + buildType() + CurrentCommit From d806fafee846910feaecb021cc72b6205ff8b2b4 Mon Sep 17 00:00:00 2001 From: quanzhan lu Date: Tue, 22 Dec 2020 17:46:58 +0800 Subject: [PATCH 039/246] update weight canSeal and canStore when attach update weight canSeal and canStore when attach storage --- extern/sector-storage/stores/index.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/extern/sector-storage/stores/index.go b/extern/sector-storage/stores/index.go index eb3e7690f..9362a7504 100644 --- a/extern/sector-storage/stores/index.go +++ b/extern/sector-storage/stores/index.go @@ -155,6 +155,10 @@ func (i *Index) StorageAttach(ctx context.Context, si StorageInfo, st fsutil.FsS i.stores[si.ID].info.URLs = append(i.stores[si.ID].info.URLs, u) } + i.stores[si.ID].info.Weight = si.Weight + i.stores[si.ID].info.CanSeal = si.CanSeal + i.stores[si.ID].info.CanStore = si.CanStore + return nil } i.stores[si.ID] = &storageEntry{ From 461a3cdebcd93c10f26798d2b7ef411039384875 Mon Sep 17 00:00:00 2001 From: Lucas Molas Date: Tue, 22 Dec 2020 12:29:36 -0300 Subject: [PATCH 040/246] fix(sync): enforce ForkLengthThreshold for synced chain (#5182) --- chain/sync.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chain/sync.go b/chain/sync.go index 61c1d2094..23c591c50 100644 --- a/chain/sync.go +++ b/chain/sync.go @@ -1449,7 +1449,7 @@ func (syncer *Syncer) syncFork(ctx context.Context, incoming *types.TipSet, know return nil, ErrForkCheckpoint } - // TODO: Does this mean we always ask for ForkLengthThreshold blocks from the network, even if we just need, like, 2? + // TODO: Does this mean we always ask for ForkLengthThreshold blocks from the network, even if we just need, like, 2? Yes. // Would it not be better to ask in smaller chunks, given that an ~ForkLengthThreshold is very rare? tips, err := syncer.Exchange.GetBlocks(ctx, incoming.Parents(), int(build.ForkLengthThreshold)) if err != nil { @@ -1460,6 +1460,10 @@ func (syncer *Syncer) syncFork(ctx context.Context, incoming *types.TipSet, know if err != nil { return nil, xerrors.Errorf("failed to load next local tipset: %w", err) } + // Track the fork length on our side of the synced chain to enforce + // `ForkLengthThreshold`. Initialized to 1 because we already walked back + // one tipset from `known` (our synced head). + forkLengthInHead := 1 for cur := 0; cur < len(tips); { if nts.Height() == 0 { @@ -1476,6 +1480,13 @@ func (syncer *Syncer) syncFork(ctx context.Context, incoming *types.TipSet, know if nts.Height() < tips[cur].Height() { cur++ } else { + // Walk back one block in our synced chain to try to meet the fork's + // height. + forkLengthInHead++ + if forkLengthInHead > int(build.ForkLengthThreshold) { + return nil, ErrForkTooLong + } + // We will be forking away from nts, check that it isn't checkpointed if nts.Key() == chkpt { return nil, ErrForkCheckpoint From bb5a92e2f4c68b78ecac7dbc2190599a64c3ca71 Mon Sep 17 00:00:00 2001 From: Deep Kapur Date: Tue, 22 Dec 2020 22:48:25 -0500 Subject: [PATCH 041/246] Update SECURITY.md (#5246) Based on feedback from @zgfzgf and a few others - recommending that we make this change to offer more clarity on what is meant by security issues (which are not always "Critical" in severity). --- SECURITY.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 592206bc5..d53c2b920 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,11 +2,11 @@ ## Reporting a Vulnerability -For *critical* bugs, please consult our Security Policy and Responsible Disclosure Program information at https://github.com/filecoin-project/community/blob/master/SECURITY.md +For reporting security vulnerabilities/bugs, please consult our Security Policy and Responsible Disclosure Program information at https://github.com/filecoin-project/community/blob/master/SECURITY.md. Security vulnerabilities should be reported via our [Vulnerability Reporting channels](https://github.com/filecoin-project/community/blob/master/SECURITY.md#vulnerability-reporting) and will be eligible for a [Bug Bounty](https://security.filecoin.io/bug-bounty/). Please try to provide a clear description of any bugs reported, along with how to reproduce the bug if possible. More detailed bug reports (especially those with a PoC included) will help us move forward much faster. Additionally, please avoid reporting bugs that already have open issues. Take a moment to search the issue list of the related GitHub repositories before writing up a new report. -Here are some examples of bugs we would consider 'critical': +Here are some examples of bugs we would consider to be security vulnerabilities: * If you can spend from a `multisig` wallet you do not control the keys for. * If you can cause a miner to be slashed without them actually misbehaving. @@ -16,8 +16,8 @@ Here are some examples of bugs we would consider 'critical': * If you can craft a message that causes a persistent fork in the network. * If you can cause the total amount of Filecoin in the network to no longer be 2 billion. -This is not an exhaustive list, but should provide some idea of what we consider 'critical'. +This is not an exhaustive list, but should provide some idea of what we consider as a security vulnerability, . ## Reporting a non security bug -For non-critical bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md). +For non-security bugs, please simply file a GitHub [issue](https://github.com/filecoin-project/lotus/issues/new?template=bug_report.md). From e9cc697ee8dd72c27cb9f75d130da63d667575d6 Mon Sep 17 00:00:00 2001 From: liismn Date: Fri, 25 Dec 2020 15:05:01 +0800 Subject: [PATCH 042/246] Update sched_worker.go Avoid possible deadlock by sh.workersLk --- extern/sector-storage/sched_worker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/extern/sector-storage/sched_worker.go b/extern/sector-storage/sched_worker.go index 573aa623b..40cf2fcf4 100644 --- a/extern/sector-storage/sched_worker.go +++ b/extern/sector-storage/sched_worker.go @@ -57,6 +57,7 @@ func (sh *scheduler) runWorker(ctx context.Context, w Worker) error { log.Warnw("duplicated worker added", "id", wid) // this is ok, we're already handling this worker in a different goroutine + sh.workersLk.Unlock() return nil } From df973da74874711ac1dbde9808c3601a4befe01c Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Dec 2020 16:25:46 +0800 Subject: [PATCH 043/246] add fund sufficient check in send --- cli/send.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cli/send.go b/cli/send.go index 55ea8b028..842862180 100644 --- a/cli/send.go +++ b/cli/send.go @@ -6,6 +6,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + "golang.org/x/xerrors" "reflect" "github.com/urfave/cli/v2" @@ -15,6 +16,7 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors/builtin" "github.com/filecoin-project/lotus/chain/stmgr" "github.com/filecoin-project/lotus/chain/types" ) @@ -51,7 +53,7 @@ var sendCmd = &cli.Command{ &cli.Uint64Flag{ Name: "method", Usage: "specify method to invoke", - Value: 0, + Value: uint64(builtin.MethodSend), }, &cli.StringFlag{ Name: "params-json", @@ -61,6 +63,10 @@ var sendCmd = &cli.Command{ Name: "params-hex", Usage: "specify invocation parameters in hex", }, + &cli.BoolFlag{ + Name: "really-do-it", + Usage: "must be specified for the action to take effect if maybe SysErrInsufficientFunds etc", + }, }, Action: func(cctx *cli.Context) error { if cctx.Args().Len() != 2 { @@ -143,6 +149,23 @@ var sendCmd = &cli.Command{ Params: params, } + // Funds insufficient check + fromBalance, err := api.WalletBalance(ctx, msg.From) + if err != nil { + return err + } + totalCost := types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))) + if msg.Method == builtin.MethodSend { + totalCost = types.BigAdd(totalCost, msg.Value) + } + if fromBalance.LessThan(totalCost) { + fmt.Printf("From balance %s attoFIL less than total cost %s attoFIL\n", fromBalance, totalCost) + if !cctx.Bool("really-do-it") { + return xerrors.Errorf("--really-do-it must be specified for this action to have an effect; " + + "you have been warned") + } + } + if cctx.IsSet("nonce") { msg.Nonce = cctx.Uint64("nonce") sm, err := api.WalletSignMessage(ctx, fromAddr, msg) From 6b9daa4d1459d53aa2abc421be697d739651929b Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 25 Dec 2020 16:32:21 +0800 Subject: [PATCH 044/246] update return error --- cli/send.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cli/send.go b/cli/send.go index 842862180..965d2c9f9 100644 --- a/cli/send.go +++ b/cli/send.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "golang.org/x/xerrors" "reflect" "github.com/urfave/cli/v2" @@ -161,7 +160,7 @@ var sendCmd = &cli.Command{ if fromBalance.LessThan(totalCost) { fmt.Printf("From balance %s attoFIL less than total cost %s attoFIL\n", fromBalance, totalCost) if !cctx.Bool("really-do-it") { - return xerrors.Errorf("--really-do-it must be specified for this action to have an effect; " + + return fmt.Errorf("--really-do-it must be specified for this action to have an effect; " + "you have been warned") } } From f9aca39cafd87145a74a5a4b63d259857b9ee761 Mon Sep 17 00:00:00 2001 From: Ruslan Tushov Date: Fri, 25 Dec 2020 16:03:46 +0300 Subject: [PATCH 045/246] Buffered stream for chainxchg response --- chain/exchange/server.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/chain/exchange/server.go b/chain/exchange/server.go index 31eec46ca..7c1624e57 100644 --- a/chain/exchange/server.go +++ b/chain/exchange/server.go @@ -56,7 +56,11 @@ func (s *server) HandleStream(stream inet.Stream) { } _ = stream.SetDeadline(time.Now().Add(WriteResDeadline)) - if err := cborutil.WriteCborRPC(stream, resp); err != nil { + buffered := bufio.NewWriter(stream) + if err = cborutil.WriteCborRPC(buffered, resp); err == nil { + err = buffered.Flush() + } + if err != nil { _ = stream.SetDeadline(time.Time{}) log.Warnw("failed to write back response for handle stream", "err", err, "peer", stream.Conn().RemotePeer()) From 8613454d1d9381b948f7db7dae8682c348d54227 Mon Sep 17 00:00:00 2001 From: He Weidong Date: Sat, 26 Dec 2020 19:20:53 +0800 Subject: [PATCH 046/246] correct flag usages for replace cmd --- cli/mpool.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cli/mpool.go b/cli/mpool.go index a84865547..d74c7c9ba 100644 --- a/cli/mpool.go +++ b/cli/mpool.go @@ -362,15 +362,15 @@ var mpoolReplaceCmd = &cli.Command{ Flags: []cli.Flag{ &cli.StringFlag{ Name: "gas-feecap", - Usage: "gas feecap for new message", + Usage: "gas feecap for new message (burn and pay to miner, attoFIL/GasUnit)", }, &cli.StringFlag{ Name: "gas-premium", - Usage: "gas price for new message", + Usage: "gas price for new message (pay to miner, attoFIL/GasUnit)", }, &cli.Int64Flag{ Name: "gas-limit", - Usage: "gas price for new message", + Usage: "gas limit for new message (GasUnit)", }, &cli.BoolFlag{ Name: "auto", @@ -378,7 +378,7 @@ var mpoolReplaceCmd = &cli.Command{ }, &cli.StringFlag{ Name: "max-fee", - Usage: "Spend up to X FIL for this message (applicable for auto mode)", + Usage: "Spend up to X attoFIL for this message (applicable for auto mode)", }, }, ArgsUsage: " | ", From cd032d54187ce6e44227cf7eed2e2d498e31f9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Kripalani?= Date: Wed, 16 Dec 2020 21:40:37 +0000 Subject: [PATCH 047/246] tvx extract: more tipset extraction goodness. - ability to extract a tipset range into individual vectors. - ability to extract a tipset range and squash into a single multi-tipset vector. - mark statediff output deterministically, so it can be extracted by tooling. - ability to execute callbacks between tipsets in the driver. - implement save-balances callback. --- cmd/tvx/exec.go | 165 +++++++++++++++---- cmd/tvx/extract.go | 57 ++++++- cmd/tvx/extract_message.go | 26 +-- cmd/tvx/extract_tipset.go | 317 ++++++++++++++++++++++++------------- cmd/tvx/main.go | 16 ++ cmd/tvx/simulate.go | 2 +- cmd/tvx/stores.go | 11 ++ conformance/corpus_test.go | 4 +- conformance/driver.go | 3 + conformance/runner.go | 78 ++++++--- 10 files changed, 487 insertions(+), 192 deletions(-) diff --git a/cmd/tvx/exec.go b/cmd/tvx/exec.go index f1ef91315..e2fb787fb 100644 --- a/cmd/tvx/exec.go +++ b/cmd/tvx/exec.go @@ -1,33 +1,48 @@ package main import ( + "bufio" "encoding/json" "fmt" "io" "log" "os" + "path/filepath" + "strings" "github.com/fatih/color" + "github.com/filecoin-project/go-address" + cbornode "github.com/ipfs/go-ipld-cbor" "github.com/urfave/cli/v2" - "github.com/filecoin-project/lotus/conformance" - "github.com/filecoin-project/test-vectors/schema" + + "github.com/filecoin-project/lotus/chain/state" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/conformance" + "github.com/filecoin-project/lotus/lib/blockstore" ) var execFlags struct { file string + out string + driverOpts cli.StringSlice fallbackBlockstore bool } +const ( + optSaveBalances = "save-balances" +) + var execCmd = &cli.Command{ Name: "exec", - Description: "execute one or many test vectors against Lotus; supplied as a single JSON file, or a ndjson stdin stream", - Action: runExecLotus, + Description: "execute one or many test vectors against Lotus; supplied as a single JSON file, a directory, or a ndjson stdin stream", + Action: runExec, Flags: []cli.Flag{ + &repoFlag, &cli.StringFlag{ Name: "file", - Usage: "input file; if not supplied, the vector will be read from stdin", + Usage: "input file or directory; if not supplied, the vector will be read from stdin", TakesFile: true, Destination: &execFlags.file, }, @@ -36,10 +51,20 @@ var execCmd = &cli.Command{ Usage: "sets the full node API as a fallback blockstore; use this if you're transplanting vectors and get block not found errors", Destination: &execFlags.fallbackBlockstore, }, + &cli.StringFlag{ + Name: "out", + Usage: "output directory where to save the results, only used when the input is a directory", + Destination: &execFlags.out, + }, + &cli.StringSliceFlag{ + Name: "driver-opt", + Usage: "comma-separated list of driver options (EXPERIMENTAL; will change), supported: 'save-balances=', 'pipeline-basefee' (unimplemented); only available in single-file mode", + Destination: &execFlags.driverOpts, + }, }, } -func runExecLotus(c *cli.Context) error { +func runExec(c *cli.Context) error { if execFlags.fallbackBlockstore { if err := initialize(c); err != nil { return fmt.Errorf("fallback blockstore was enabled, but could not resolve lotus API endpoint: %w", err) @@ -48,30 +73,97 @@ func runExecLotus(c *cli.Context) error { conformance.FallbackBlockstoreGetter = FullAPI } - if file := execFlags.file; file != "" { - // we have a single test vector supplied as a file. - file, err := os.Open(file) - if err != nil { - return fmt.Errorf("failed to open test vector: %w", err) - } - - var ( - dec = json.NewDecoder(file) - tv schema.TestVector - ) - - if err = dec.Decode(&tv); err != nil { - return fmt.Errorf("failed to decode test vector: %w", err) - } - - return executeTestVector(tv) + path := execFlags.file + if path == "" { + return execVectorsStdin() } + fi, err := os.Stat(path) + if err != nil { + return err + } + + if fi.IsDir() { + // we're in directory mode; ensure the out directory exists. + outdir := execFlags.out + if outdir == "" { + return fmt.Errorf("no output directory provided") + } + if err := ensureDir(outdir); err != nil { + return err + } + return execVectorDir(path, outdir) + } + + // process tipset vector options. + if err := processTipsetOpts(); err != nil { + return err + } + + _, err = execVectorFile(new(conformance.LogReporter), path) + return err +} + +func processTipsetOpts() error { + for _, opt := range execFlags.driverOpts.Value() { + switch ss := strings.Split(opt, "="); { + case ss[0] == optSaveBalances: + filename := ss[1] + log.Printf("saving balances after each tipset in: %s", filename) + balancesFile, err := os.Create(filename) + if err != nil { + return err + } + w := bufio.NewWriter(balancesFile) + cb := func(bs blockstore.Blockstore, params *conformance.ExecuteTipsetParams, res *conformance.ExecuteTipsetResult) { + cst := cbornode.NewCborStore(bs) + st, err := state.LoadStateTree(cst, res.PostStateRoot) + if err != nil { + return + } + _ = st.ForEach(func(addr address.Address, actor *types.Actor) error { + _, err := fmt.Fprintln(w, params.ExecEpoch, addr, actor.Balance) + return err + }) + _ = w.Flush() + } + conformance.TipsetVectorOpts.OnTipsetApplied = append(conformance.TipsetVectorOpts.OnTipsetApplied, cb) + + } + + } + return nil +} + +func execVectorDir(path string, outdir string) error { + files, err := filepath.Glob(filepath.Join(path, "*")) + if err != nil { + return fmt.Errorf("failed to glob input directory %s: %w", path, err) + } + for _, f := range files { + outfile := strings.TrimSuffix(filepath.Base(f), filepath.Ext(f)) + ".out" + outpath := filepath.Join(outdir, outfile) + outw, err := os.Create(outpath) + if err != nil { + return fmt.Errorf("failed to create file %s: %w", outpath, err) + } + + log.Printf("processing vector %s; sending output to %s", f, outpath) + log.SetOutput(io.MultiWriter(os.Stderr, outw)) // tee the output. + _, _ = execVectorFile(new(conformance.LogReporter), f) + log.SetOutput(os.Stderr) + _ = outw.Close() + } + return nil +} + +func execVectorsStdin() error { + r := new(conformance.LogReporter) for dec := json.NewDecoder(os.Stdin); ; { var tv schema.TestVector switch err := dec.Decode(&tv); err { case nil: - if err = executeTestVector(tv); err != nil { + if _, err = executeTestVector(r, tv); err != nil { return err } case io.EOF: @@ -84,19 +176,30 @@ func runExecLotus(c *cli.Context) error { } } -func executeTestVector(tv schema.TestVector) error { +func execVectorFile(r conformance.Reporter, path string) (diffs []string, error error) { + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("failed to open test vector: %w", err) + } + + var tv schema.TestVector + if err = json.NewDecoder(file).Decode(&tv); err != nil { + return nil, fmt.Errorf("failed to decode test vector: %w", err) + } + return executeTestVector(r, tv) +} + +func executeTestVector(r conformance.Reporter, tv schema.TestVector) (diffs []string, err error) { log.Println("executing test vector:", tv.Meta.ID) for _, v := range tv.Pre.Variants { - r := new(conformance.LogReporter) - switch class, v := tv.Class, v; class { case "message": - conformance.ExecuteMessageVector(r, &tv, &v) + diffs, err = conformance.ExecuteMessageVector(r, &tv, &v) case "tipset": - conformance.ExecuteTipsetVector(r, &tv, &v) + diffs, err = conformance.ExecuteTipsetVector(r, &tv, &v) default: - return fmt.Errorf("test vector class %s not supported", class) + return nil, fmt.Errorf("test vector class %s not supported", class) } if r.Failed() { @@ -106,5 +209,5 @@ func executeTestVector(tv schema.TestVector) error { } } - return nil + return diffs, err } diff --git a/cmd/tvx/extract.go b/cmd/tvx/extract.go index 61808882c..a3d538abd 100644 --- a/cmd/tvx/extract.go +++ b/cmd/tvx/extract.go @@ -1,8 +1,14 @@ package main import ( + "encoding/json" "fmt" + "io" + "log" + "os" + "path/filepath" + "github.com/filecoin-project/test-vectors/schema" "github.com/urfave/cli/v2" ) @@ -21,6 +27,7 @@ type extractOpts struct { retain string precursor string ignoreSanityChecks bool + squash bool } var extractFlags extractOpts @@ -62,13 +69,13 @@ var extractCmd = &cli.Command{ }, &cli.StringFlag{ Name: "tsk", - Usage: "tipset key to extract into a vector", + Usage: "tipset key to extract into a vector, or range of tipsets in tsk1..tsk2 form", Destination: &extractFlags.tsk, }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, - Usage: "file to write test vector to", + Usage: "file to write test vector to, or directory to write the batch to", Destination: &extractFlags.file, }, &cli.StringFlag{ @@ -93,6 +100,12 @@ var extractCmd = &cli.Command{ Value: false, Destination: &extractFlags.ignoreSanityChecks, }, + &cli.BoolFlag{ + Name: "squash", + Usage: "when extracting a tipset range, squash all tipsets into a single vector", + Value: false, + Destination: &extractFlags.squash, + }, }, } @@ -106,3 +119,43 @@ func runExtract(_ *cli.Context) error { return fmt.Errorf("unsupported vector class") } } + +// writeVector writes the vector into the specified file, or to stdout if +// file is empty. +func writeVector(vector *schema.TestVector, file string) (err error) { + output := io.WriteCloser(os.Stdout) + if file := file; file != "" { + dir := filepath.Dir(file) + if err := os.MkdirAll(dir, 0755); err != nil { + return fmt.Errorf("unable to create directory %s: %w", dir, err) + } + output, err = os.Create(file) + if err != nil { + return err + } + defer output.Close() //nolint:errcheck + defer log.Printf("wrote test vector to file: %s", file) + } + + enc := json.NewEncoder(output) + enc.SetIndent("", " ") + return enc.Encode(&vector) +} + +// writeVectors writes each vector to a different file under the specified +// directory. +func writeVectors(dir string, vectors ...*schema.TestVector) error { + // verify the output directory exists. + if err := ensureDir(dir); err != nil { + return err + } + // write each vector to its file. + for _, v := range vectors { + id := v.Meta.ID + path := filepath.Join(dir, fmt.Sprintf("%s.json", id)) + if err := writeVector(v, path); err != nil { + return err + } + } + return nil +} diff --git a/cmd/tvx/extract_message.go b/cmd/tvx/extract_message.go index 286910c38..0c2fcff4a 100644 --- a/cmd/tvx/extract_message.go +++ b/cmd/tvx/extract_message.go @@ -4,12 +4,9 @@ import ( "bytes" "compress/gzip" "context" - "encoding/json" "fmt" "io" "log" - "os" - "path/filepath" "github.com/fatih/color" "github.com/filecoin-project/go-address" @@ -316,28 +313,7 @@ func doExtractMessage(opts extractOpts) error { }, }, } - - return writeVector(vector, opts.file) -} - -func writeVector(vector schema.TestVector, file string) (err error) { - output := io.WriteCloser(os.Stdout) - if file := file; file != "" { - dir := filepath.Dir(file) - if err := os.MkdirAll(dir, 0755); err != nil { - return fmt.Errorf("unable to create directory %s: %w", dir, err) - } - output, err = os.Create(file) - if err != nil { - return err - } - defer output.Close() //nolint:errcheck - defer log.Printf("wrote test vector to file: %s", file) - } - - enc := json.NewEncoder(output) - enc.SetIndent("", " ") - return enc.Encode(&vector) + return writeVector(&vector, opts.file) } // resolveFromChain queries the chain for the provided message, using the block CID to diff --git a/cmd/tvx/extract_tipset.go b/cmd/tvx/extract_tipset.go index 20c35f8ac..05e856aa1 100644 --- a/cmd/tvx/extract_tipset.go +++ b/cmd/tvx/extract_tipset.go @@ -6,10 +6,12 @@ import ( "context" "fmt" "log" + "strings" "github.com/filecoin-project/test-vectors/schema" "github.com/ipfs/go-cid" + "github.com/filecoin-project/lotus/chain/types" lcli "github.com/filecoin-project/lotus/cli" "github.com/filecoin-project/lotus/conformance" ) @@ -17,170 +19,259 @@ import ( func doExtractTipset(opts extractOpts) error { ctx := context.Background() - if opts.tsk == "" { - return fmt.Errorf("tipset key cannot be empty") - } - if opts.retain != "accessed-cids" { return fmt.Errorf("tipset extraction only supports 'accessed-cids' state retention") } - ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk) - if err != nil { - return fmt.Errorf("failed to fetch tipset: %w", err) + if opts.tsk == "" { + return fmt.Errorf("tipset key cannot be empty") } - log.Printf("tipset block count: %d", len(ts.Blocks())) - - var blocks []schema.Block - for _, b := range ts.Blocks() { - msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid()) + ss := strings.Split(opts.tsk, "..") + switch len(ss) { + case 1: // extracting a single tipset. + ts, err := lcli.ParseTipSetRef(ctx, FullAPI, opts.tsk) if err != nil { - return fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err) + return fmt.Errorf("failed to fetch tipset: %w", err) + } + v, err := extractTipsets(ctx, ts) + if err != nil { + return err + } + return writeVector(v, opts.file) + + case 2: // extracting a range of tipsets. + left, err := lcli.ParseTipSetRef(ctx, FullAPI, ss[0]) + if err != nil { + return fmt.Errorf("failed to fetch tipset %s: %w", ss[0], err) + } + right, err := lcli.ParseTipSetRef(ctx, FullAPI, ss[1]) + if err != nil { + return fmt.Errorf("failed to fetch tipset %s: %w", ss[1], err) } - log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids)) + // resolve the tipset range. + tss, err := resolveTipsetRange(ctx, left, right) + if err != nil { + return err + } - packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids)) - for _, m := range msgs.BlsMessages { - b, err := m.Serialize() + // are are squashing all tipsets into a single multi-tipset vector? + if opts.squash { + vector, err := extractTipsets(ctx, tss...) if err != nil { - return fmt.Errorf("failed to serialize message: %w", err) + return err } - packed = append(packed, b) + return writeVector(vector, opts.file) } - for _, m := range msgs.SecpkMessages { - b, err := m.Message.Serialize() - if err != nil { - return fmt.Errorf("failed to serialize message: %w", err) - } - packed = append(packed, b) + + // we are generating a single-tipset vector per tipset. + vectors, err := extractIndividualTipsets(ctx, tss...) + if err != nil { + return err } - blocks = append(blocks, schema.Block{ - MinerAddr: b.Miner, - WinCount: b.ElectionProof.WinCount, - Messages: packed, - }) + return writeVectors(opts.file, vectors...) + + default: + return fmt.Errorf("unrecognized tipset format") } +} +func resolveTipsetRange(ctx context.Context, left *types.TipSet, right *types.TipSet) (tss []*types.TipSet, err error) { + // start from the right tipset and walk back the chain until the left tipset, inclusive. + for curr := right; curr.Key() != left.Parents(); { + tss = append(tss, curr) + curr, err = FullAPI.ChainGetTipSet(ctx, curr.Parents()) + if err != nil { + return nil, fmt.Errorf("failed to get tipset %s (height: %d): %w", curr.Parents(), curr.Height()-1, err) + } + } + // reverse the slice. + for i, j := 0, len(tss)-1; i < j; i, j = i+1, j-1 { + tss[i], tss[j] = tss[j], tss[i] + } + return tss, nil +} + +func extractIndividualTipsets(ctx context.Context, tss ...*types.TipSet) (vectors []*schema.TestVector, err error) { + for _, ts := range tss { + v, err := extractTipsets(ctx, ts) + if err != nil { + return nil, err + } + vectors = append(vectors, v) + } + return vectors, nil +} + +func extractTipsets(ctx context.Context, tss ...*types.TipSet) (*schema.TestVector, error) { var ( // create a read-through store that uses ChainGetObject to fetch unknown CIDs. pst = NewProxyingStores(ctx, FullAPI) g = NewSurgeon(ctx, FullAPI, pst) + + // recordingRand will record randomness so we can embed it in the test vector. + recordingRand = conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI) ) + tbs, ok := pst.Blockstore.(TracingBlockstore) + if !ok { + return nil, fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present") + } + driver := conformance.NewDriver(ctx, schema.Selector{}, conformance.DriverOpts{ DisableVMFlush: true, }) + base := tss[0] + last := tss[len(tss)-1] + // this is the root of the state tree we start with. - root := ts.ParentState() + root := base.ParentState() log.Printf("base state tree root CID: %s", root) - basefee := ts.Blocks()[0].ParentBaseFee - log.Printf("basefee: %s", basefee) - - tipset := schema.Tipset{ - BaseFee: *basefee.Int, - Blocks: blocks, + codename := GetProtocolCodename(base.Height()) + nv, err := FullAPI.StateNetworkVersion(ctx, base.Key()) + if err != nil { + return nil, err } - // recordingRand will record randomness so we can embed it in the test vector. - recordingRand := conformance.NewRecordingRand(new(conformance.LogReporter), FullAPI) + version, err := FullAPI.Version(ctx) + if err != nil { + return nil, err + } - log.Printf("using state retention strategy: %s", extractFlags.retain) + ntwkName, err := FullAPI.StateNetworkName(ctx) + if err != nil { + return nil, err + } - tbs, ok := pst.Blockstore.(TracingBlockstore) - if !ok { - return fmt.Errorf("requested 'accessed-cids' state retention, but no tracing blockstore was present") + vector := schema.TestVector{ + Class: schema.ClassTipset, + Meta: &schema.Metadata{ + ID: fmt.Sprintf("@%d..@%d", base.Height(), last.Height()), + Gen: []schema.GenerationData{ + {Source: fmt.Sprintf("network:%s", ntwkName)}, + {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, + // will be completed by extra tipset stamps. + }, + Selector: schema.Selector{ + schema.SelectorMinProtocolVersion: codename, + }, + Pre: &schema.Preconditions{ + Variants: []schema.Variant{ + {ID: codename, Epoch: int64(base.Height()), NetworkVersion: uint(nv)}, + }, + StateTree: &schema.StateTree{ + RootCID: base.ParentState(), + }, + }, + Post: &schema.Postconditions{ + StateTree: new(schema.StateTree), + }, } tbs.StartTracing() - params := conformance.ExecuteTipsetParams{ - Preroot: ts.ParentState(), - ParentEpoch: ts.Height() - 1, - Tipset: &tipset, - ExecEpoch: ts.Height(), - Rand: recordingRand, - } - result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params) - if err != nil { - return fmt.Errorf("failed to execute tipset: %w", err) + roots := []cid.Cid{base.ParentState()} + for i, ts := range tss { + log.Printf("tipset %s block count: %d", ts.Key(), len(ts.Blocks())) + + var blocks []schema.Block + for _, b := range ts.Blocks() { + msgs, err := FullAPI.ChainGetBlockMessages(ctx, b.Cid()) + if err != nil { + return nil, fmt.Errorf("failed to get block messages (cid: %s): %w", b.Cid(), err) + } + + log.Printf("block %s has %d messages", b.Cid(), len(msgs.Cids)) + + packed := make([]schema.Base64EncodedBytes, 0, len(msgs.Cids)) + for _, m := range msgs.BlsMessages { + b, err := m.Serialize() + if err != nil { + return nil, fmt.Errorf("failed to serialize message: %w", err) + } + packed = append(packed, b) + } + for _, m := range msgs.SecpkMessages { + b, err := m.Message.Serialize() + if err != nil { + return nil, fmt.Errorf("failed to serialize message: %w", err) + } + packed = append(packed, b) + } + blocks = append(blocks, schema.Block{ + MinerAddr: b.Miner, + WinCount: b.ElectionProof.WinCount, + Messages: packed, + }) + } + + basefee := base.Blocks()[0].ParentBaseFee + log.Printf("tipset basefee: %s", basefee) + + tipset := schema.Tipset{ + BaseFee: *basefee.Int, + Blocks: blocks, + EpochOffset: int64(i), + } + + params := conformance.ExecuteTipsetParams{ + Preroot: roots[len(roots)-1], + ParentEpoch: ts.Height() - 1, + Tipset: &tipset, + ExecEpoch: ts.Height(), + Rand: recordingRand, + } + + result, err := driver.ExecuteTipset(pst.Blockstore, pst.Datastore, params) + if err != nil { + return nil, fmt.Errorf("failed to execute tipset: %w", err) + } + + roots = append(roots, result.PostStateRoot) + + // update the vector. + vector.ApplyTipsets = append(vector.ApplyTipsets, tipset) + vector.Post.ReceiptsRoots = append(vector.Post.ReceiptsRoots, result.ReceiptsRoot) + + for _, res := range result.AppliedResults { + vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{ + ExitCode: int64(res.ExitCode), + ReturnValue: res.Return, + GasUsed: res.GasUsed, + }) + } + + vector.Meta.Gen = append(vector.Meta.Gen, schema.GenerationData{ + Source: "tipset:" + ts.Key().String(), + }) } accessed := tbs.FinishTracing() + // + // ComputeBaseFee(ctx, baseTs) + // write a CAR with the accessed state into a buffer. var ( out = new(bytes.Buffer) gw = gzip.NewWriter(out) ) - if err := g.WriteCARIncluding(gw, accessed, ts.ParentState(), result.PostStateRoot); err != nil { - return err + if err := g.WriteCARIncluding(gw, accessed, roots...); err != nil { + return nil, err } if err = gw.Flush(); err != nil { - return err + return nil, err } if err = gw.Close(); err != nil { - return err + return nil, err } - codename := GetProtocolCodename(ts.Height()) - nv, err := FullAPI.StateNetworkVersion(ctx, ts.Key()) - if err != nil { - return err - } + vector.Randomness = recordingRand.Recorded() + vector.Post.StateTree.RootCID = roots[len(roots)-1] + vector.CAR = out.Bytes() - version, err := FullAPI.Version(ctx) - if err != nil { - return err - } - - ntwkName, err := FullAPI.StateNetworkName(ctx) - if err != nil { - return err - } - - vector := schema.TestVector{ - Class: schema.ClassTipset, - Meta: &schema.Metadata{ - ID: opts.id, - Gen: []schema.GenerationData{ - {Source: fmt.Sprintf("network:%s", ntwkName)}, - {Source: fmt.Sprintf("tipset:%s", ts.Key())}, - {Source: "github.com/filecoin-project/lotus", Version: version.String()}}, - }, - Selector: schema.Selector{ - schema.SelectorMinProtocolVersion: codename, - }, - Randomness: recordingRand.Recorded(), - CAR: out.Bytes(), - Pre: &schema.Preconditions{ - Variants: []schema.Variant{ - {ID: codename, Epoch: int64(ts.Height()), NetworkVersion: uint(nv)}, - }, - BaseFee: basefee.Int, - StateTree: &schema.StateTree{ - RootCID: ts.ParentState(), - }, - }, - ApplyTipsets: []schema.Tipset{tipset}, - Post: &schema.Postconditions{ - StateTree: &schema.StateTree{ - RootCID: result.PostStateRoot, - }, - ReceiptsRoots: []cid.Cid{result.ReceiptsRoot}, - }, - } - - for _, res := range result.AppliedResults { - vector.Post.Receipts = append(vector.Post.Receipts, &schema.Receipt{ - ExitCode: int64(res.ExitCode), - ReturnValue: res.Return, - GasUsed: res.GasUsed, - }) - } - - return writeVector(vector, opts.file) + return &vector, nil } diff --git a/cmd/tvx/main.go b/cmd/tvx/main.go index 8dae39958..94a656c3e 100644 --- a/cmd/tvx/main.go +++ b/cmd/tvx/main.go @@ -113,3 +113,19 @@ func destroy(_ *cli.Context) error { } return nil } + +func ensureDir(path string) error { + switch fi, err := os.Stat(path); { + case os.IsNotExist(err): + if err := os.MkdirAll(path, 0755); err != nil { + return fmt.Errorf("failed to create directory %s: %w", path, err) + } + case err == nil: + if !fi.IsDir() { + return fmt.Errorf("path %s is not a directory: %w", path, err) + } + default: + return fmt.Errorf("failed to stat directory %s: %w", path, err) + } + return nil +} diff --git a/cmd/tvx/simulate.go b/cmd/tvx/simulate.go index 82b2bc118..7a33707dc 100644 --- a/cmd/tvx/simulate.go +++ b/cmd/tvx/simulate.go @@ -202,7 +202,7 @@ func runSimulateCmd(_ *cli.Context) error { }, } - if err := writeVector(vector, simulateFlags.out); err != nil { + if err := writeVector(&vector, simulateFlags.out); err != nil { return fmt.Errorf("failed to write vector: %w", err) } diff --git a/cmd/tvx/stores.go b/cmd/tvx/stores.go index 4f574c175..e160929da 100644 --- a/cmd/tvx/stores.go +++ b/cmd/tvx/stores.go @@ -149,3 +149,14 @@ func (pb *proxyingBlockstore) Put(block blocks.Block) error { pb.lk.Unlock() return pb.Blockstore.Put(block) } + +func (pb *proxyingBlockstore) PutMany(blocks []blocks.Block) error { + pb.lk.Lock() + if pb.tracing { + for _, b := range blocks { + pb.traced[b.Cid()] = struct{}{} + } + } + pb.lk.Unlock() + return pb.Blockstore.PutMany(blocks) +} diff --git a/conformance/corpus_test.go b/conformance/corpus_test.go index a09f9a8d3..b9ba062cc 100644 --- a/conformance/corpus_test.go +++ b/conformance/corpus_test.go @@ -11,7 +11,7 @@ import ( "github.com/filecoin-project/test-vectors/schema" ) -var invokees = map[schema.Class]func(Reporter, *schema.TestVector, *schema.Variant){ +var invokees = map[schema.Class]func(Reporter, *schema.TestVector, *schema.Variant) ([]string, error){ schema.ClassMessage: ExecuteMessageVector, schema.ClassTipset: ExecuteTipsetVector, } @@ -133,7 +133,7 @@ func TestConformance(t *testing.T) { for _, variant := range vector.Pre.Variants { variant := variant t.Run(variant.ID, func(t *testing.T) { - invokee(t, &vector, &variant) + _, _ = invokee(t, &vector, &variant) //nolint:errcheck }) } }) diff --git a/conformance/driver.go b/conformance/driver.go index 1d5450622..98436cf96 100644 --- a/conformance/driver.go +++ b/conformance/driver.go @@ -71,6 +71,9 @@ type ExecuteTipsetResult struct { 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 { diff --git a/conformance/runner.go b/conformance/runner.go index 6eff25274..8ced484c9 100644 --- a/conformance/runner.go +++ b/conformance/runner.go @@ -14,6 +14,7 @@ import ( "github.com/fatih/color" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/exitcode" + "github.com/hashicorp/go-multierror" blocks "github.com/ipfs/go-block-format" "github.com/ipfs/go-blockservice" "github.com/ipfs/go-cid" @@ -38,8 +39,19 @@ var FallbackBlockstoreGetter interface { ChainReadObj(context.Context, cid.Cid) ([]byte, error) } +var TipsetVectorOpts struct { + // PipelineBaseFee pipelines the basefee in multi-tipset vectors from one + // tipset to another. Basefees in the vector are ignored, except for that of + // the first tipset. UNUSED. + PipelineBaseFee bool + + // OnTipsetApplied contains callback functions called after a tipset has been + // applied. + OnTipsetApplied []func(bs blockstore.Blockstore, params *ExecuteTipsetParams, res *ExecuteTipsetResult) +} + // ExecuteMessageVector executes a message-class test vector. -func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) { +func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) (diffs []string, err error) { var ( ctx = context.Background() baseEpoch = variant.Epoch @@ -88,14 +100,16 @@ func ExecuteMessageVector(r Reporter, vector *schema.TestVector, variant *schema // Once all messages are applied, assert that the final state root matches // the expected postcondition root. if expected, actual := vector.Post.StateTree.RootCID, root; expected != actual { - r.Errorf("wrong post root cid; expected %v, but got %v", expected, actual) - dumpThreeWayStateDiff(r, vector, bs, root) - r.FailNow() + ierr := fmt.Errorf("wrong post root cid; expected %v, but got %v", expected, actual) + r.Errorf(ierr.Error()) + err = multierror.Append(err, ierr) + diffs = dumpThreeWayStateDiff(r, vector, bs, root) } + return diffs, err } // ExecuteTipsetVector executes a tipset-class test vector. -func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) { +func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema.Variant) (diffs []string, err error) { var ( ctx = context.Background() baseEpoch = abi.ChainEpoch(variant.Epoch) @@ -107,6 +121,7 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. bs, err := LoadBlockstore(vector.CAR) if err != nil { r.Fatalf("failed to load the vector CAR: %w", err) + return nil, err } // Create a new Driver. @@ -118,15 +133,22 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. for i, ts := range vector.ApplyTipsets { ts := ts // capture execEpoch := baseEpoch + abi.ChainEpoch(ts.EpochOffset) - ret, err := driver.ExecuteTipset(bs, tmpds, ExecuteTipsetParams{ + params := ExecuteTipsetParams{ Preroot: root, ParentEpoch: prevEpoch, Tipset: &ts, ExecEpoch: execEpoch, Rand: NewReplayingRand(r, vector.Randomness), - }) + } + ret, err := driver.ExecuteTipset(bs, tmpds, params) if err != nil { r.Fatalf("failed to apply tipset %d: %s", i, err) + return nil, err + } + + // invoke callbacks. + for _, cb := range TipsetVectorOpts.OnTipsetApplied { + cb(bs, ¶ms, ret) } for j, v := range ret.AppliedResults { @@ -136,7 +158,9 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. // Compare the receipts root. if expected, actual := vector.Post.ReceiptsRoots[i], ret.ReceiptsRoot; expected != actual { - r.Errorf("post receipts root doesn't match; expected: %s, was: %s", expected, actual) + ierr := fmt.Errorf("post receipts root doesn't match; expected: %s, was: %s", expected, actual) + r.Errorf(ierr.Error()) + err = multierror.Append(err, ierr) } prevEpoch = execEpoch @@ -146,10 +170,12 @@ func ExecuteTipsetVector(r Reporter, vector *schema.TestVector, variant *schema. // Once all messages are applied, assert that the final state root matches // the expected postcondition root. if expected, actual := vector.Post.StateTree.RootCID, root; expected != actual { - r.Errorf("wrong post root cid; expected %v, but got %v", expected, actual) - dumpThreeWayStateDiff(r, vector, bs, root) - r.FailNow() + ierr := fmt.Errorf("wrong post root cid; expected %v, but got %v", expected, actual) + r.Errorf(ierr.Error()) + err = multierror.Append(err, ierr) + diffs = dumpThreeWayStateDiff(r, vector, bs, root) } + return diffs, err } // AssertMsgResult compares a message result. It takes the expected receipt @@ -169,7 +195,7 @@ func AssertMsgResult(r Reporter, expected *schema.Receipt, actual *vm.ApplyRet, } } -func dumpThreeWayStateDiff(r Reporter, vector *schema.TestVector, bs blockstore.Blockstore, actual cid.Cid) { +func dumpThreeWayStateDiff(r Reporter, vector *schema.TestVector, bs blockstore.Blockstore, actual cid.Cid) []string { // check if statediff exists; if not, skip. if err := exec.Command("statediff", "--help").Run(); err != nil { r.Log("could not dump 3-way state tree diff upon test failure: statediff command not found") @@ -178,7 +204,7 @@ func dumpThreeWayStateDiff(r Reporter, vector *schema.TestVector, bs blockstore. r.Log("$ cd statediff") r.Log("$ go generate ./...") r.Log("$ go install ./cmd/statediff") - return + return nil } tmpCar, err := writeStateToTempCAR(bs, @@ -188,6 +214,7 @@ func dumpThreeWayStateDiff(r Reporter, vector *schema.TestVector, bs blockstore. ) if err != nil { r.Fatalf("failed to write temporary state CAR: %s", err) + return nil } defer os.RemoveAll(tmpCar) //nolint:errcheck @@ -202,28 +229,43 @@ func dumpThreeWayStateDiff(r Reporter, vector *schema.TestVector, bs blockstore. d3 = color.New(color.FgGreen, color.Bold).Sprint("[Δ3]") ) - printDiff := func(left, right cid.Cid) { + diff := func(left, right cid.Cid) string { cmd := exec.Command("statediff", "car", "--file", tmpCar, left.String(), right.String()) b, err := cmd.CombinedOutput() if err != nil { r.Fatalf("statediff failed: %s", err) } - r.Log(string(b)) + return string(b) } bold := color.New(color.Bold).SprintfFunc() + r.Log(bold("-----BEGIN STATEDIFF-----")) + // run state diffs. r.Log(bold("=== dumping 3-way diffs between %s, %s, %s ===", a, b, c)) r.Log(bold("--- %s left: %s; right: %s ---", d1, a, b)) - printDiff(vector.Post.StateTree.RootCID, actual) + diffA := diff(vector.Post.StateTree.RootCID, actual) + r.Log(bold("----------BEGIN STATEDIFF A----------")) + r.Log(diffA) + r.Log(bold("----------END STATEDIFF A----------")) r.Log(bold("--- %s left: %s; right: %s ---", d2, c, b)) - printDiff(vector.Pre.StateTree.RootCID, actual) + diffB := diff(vector.Pre.StateTree.RootCID, actual) + r.Log(bold("----------BEGIN STATEDIFF B----------")) + r.Log(diffB) + r.Log(bold("----------END STATEDIFF B----------")) r.Log(bold("--- %s left: %s; right: %s ---", d3, c, a)) - printDiff(vector.Pre.StateTree.RootCID, vector.Post.StateTree.RootCID) + diffC := diff(vector.Pre.StateTree.RootCID, vector.Post.StateTree.RootCID) + r.Log(bold("----------BEGIN STATEDIFF C----------")) + r.Log(diffC) + r.Log(bold("----------END STATEDIFF C----------")) + + r.Log(bold("-----END STATEDIFF-----")) + + return []string{diffA, diffB, diffC} } // writeStateToTempCAR writes the provided roots to a temporary CAR that'll be From a7a6f24cebfbc91fb6b713861a4812829020f6a3 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 28 Dec 2020 11:08:19 +0800 Subject: [PATCH 048/246] add value for all method --- cli/send.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cli/send.go b/cli/send.go index 965d2c9f9..592989e52 100644 --- a/cli/send.go +++ b/cli/send.go @@ -153,10 +153,8 @@ var sendCmd = &cli.Command{ if err != nil { return err } - totalCost := types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))) - if msg.Method == builtin.MethodSend { - totalCost = types.BigAdd(totalCost, msg.Value) - } + totalCost := types.BigAdd(types.BigMul(msg.GasFeeCap, types.NewInt(uint64(msg.GasLimit))), msg.Value) + if fromBalance.LessThan(totalCost) { fmt.Printf("From balance %s attoFIL less than total cost %s attoFIL\n", fromBalance, totalCost) if !cctx.Bool("really-do-it") { From 7583c4399ad2933a5e61b5354faa56b7da8a940e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 28 Dec 2020 16:36:19 +0800 Subject: [PATCH 049/246] add verbose for list transfers --- cli/client.go | 28 +++++++++++++++++++--------- cmd/lotus-storage-miner/market.go | 10 ++++++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/cli/client.go b/cli/client.go index 129ede94b..7a6831fe5 100644 --- a/cli/client.go +++ b/cli/client.go @@ -1943,6 +1943,11 @@ var clientListTransfers = &cli.Command{ Name: "list-transfers", Usage: "List ongoing data transfers for deals", Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "print verbose transfer details", + }, &cli.BoolFlag{ Name: "color", Usage: "use color in display output", @@ -1974,6 +1979,7 @@ var clientListTransfers = &cli.Command{ return err } + verbose := cctx.Bool("verbose") completed := cctx.Bool("completed") color := cctx.Bool("color") watch := cctx.Bool("watch") @@ -1989,7 +1995,7 @@ var clientListTransfers = &cli.Command{ tm.MoveCursor(1, 1) - OutputDataTransferChannels(tm.Screen, channels, completed, color, showFailed) + OutputDataTransferChannels(tm.Screen, channels, verbose, completed, color, showFailed) tm.Flush() @@ -2014,13 +2020,13 @@ var clientListTransfers = &cli.Command{ } } } - OutputDataTransferChannels(os.Stdout, channels, completed, color, showFailed) + OutputDataTransferChannels(os.Stdout, channels, verbose, completed, color, showFailed) return nil }, } // OutputDataTransferChannels generates table output for a list of channels -func OutputDataTransferChannels(out io.Writer, channels []lapi.DataTransferChannel, completed bool, color bool, showFailed bool) { +func OutputDataTransferChannels(out io.Writer, channels []lapi.DataTransferChannel, verbose, completed, color, showFailed bool) { sort.Slice(channels, func(i, j int) bool { return channels[i].TransferID < channels[j].TransferID }) @@ -2050,7 +2056,7 @@ func OutputDataTransferChannels(out io.Writer, channels []lapi.DataTransferChann tablewriter.Col("Voucher"), tablewriter.NewLineCol("Message")) for _, channel := range sendingChannels { - w.Write(toChannelOutput(color, "Sending To", channel)) + w.Write(toChannelOutput(color, "Sending To", channel, verbose)) } w.Flush(out) //nolint:errcheck @@ -2064,7 +2070,7 @@ func OutputDataTransferChannels(out io.Writer, channels []lapi.DataTransferChann tablewriter.Col("Voucher"), tablewriter.NewLineCol("Message")) for _, channel := range receivingChannels { - w.Write(toChannelOutput(color, "Receiving From", channel)) + w.Write(toChannelOutput(color, "Receiving From", channel, verbose)) } w.Flush(out) //nolint:errcheck } @@ -2085,9 +2091,13 @@ func channelStatusString(useColor bool, status datatransfer.Status) string { } } -func toChannelOutput(useColor bool, otherPartyColumn string, channel lapi.DataTransferChannel) map[string]interface{} { - rootCid := ellipsis(channel.BaseCID.String(), 8) - otherParty := ellipsis(channel.OtherPeer.String(), 8) +func toChannelOutput(useColor bool, otherPartyColumn string, channel lapi.DataTransferChannel, verbose bool) map[string]interface{} { + rootCid := channel.BaseCID.String() + otherParty := channel.OtherPeer.String() + if !verbose { + rootCid = ellipsis(rootCid, 8) + otherParty = ellipsis(otherParty, 8) + } initiated := "N" if channel.IsInitiator { @@ -2095,7 +2105,7 @@ func toChannelOutput(useColor bool, otherPartyColumn string, channel lapi.DataTr } voucher := channel.Voucher - if len(voucher) > 40 { + if len(voucher) > 40 && !verbose { voucher = ellipsis(voucher, 37) } diff --git a/cmd/lotus-storage-miner/market.go b/cmd/lotus-storage-miner/market.go index 1e5057f5d..51454fd04 100644 --- a/cmd/lotus-storage-miner/market.go +++ b/cmd/lotus-storage-miner/market.go @@ -744,6 +744,11 @@ var transfersListCmd = &cli.Command{ Name: "list", Usage: "List ongoing data transfers for this miner", Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"v"}, + Usage: "print verbose transfer details", + }, &cli.BoolFlag{ Name: "color", Usage: "use color in display output", @@ -775,6 +780,7 @@ var transfersListCmd = &cli.Command{ return err } + verbose := cctx.Bool("verbose") completed := cctx.Bool("completed") color := cctx.Bool("color") watch := cctx.Bool("watch") @@ -790,7 +796,7 @@ var transfersListCmd = &cli.Command{ tm.MoveCursor(1, 1) - lcli.OutputDataTransferChannels(tm.Screen, channels, completed, color, showFailed) + lcli.OutputDataTransferChannels(tm.Screen, channels, verbose, completed, color, showFailed) tm.Flush() @@ -815,7 +821,7 @@ var transfersListCmd = &cli.Command{ } } } - lcli.OutputDataTransferChannels(os.Stdout, channels, completed, color, showFailed) + lcli.OutputDataTransferChannels(os.Stdout, channels, verbose, completed, color, showFailed) return nil }, } From 72c1fa81db91a68e74f0439900d1f1e1de40bc9c Mon Sep 17 00:00:00 2001 From: "eben.xie" Date: Thu, 31 Dec 2020 11:41:51 +0800 Subject: [PATCH 050/246] when use lotus state call method, in lotus/cli/state.go 1620 line, the paramObj needs struct type not a ptr type, if use a ptr type, the program will panic in line 1621(paramObj.NumField()) --- cli/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/state.go b/cli/state.go index bef864dd2..5899dfdb1 100644 --- a/cli/state.go +++ b/cli/state.go @@ -1617,7 +1617,7 @@ func parseParamsForMethod(act cid.Cid, method uint64, args []string) ([]byte, er return nil, fmt.Errorf("unknown method %d for actor %s", method, act) } - paramObj := methodMeta.Params + paramObj := methodMeta.Params.Elem() if paramObj.NumField() != len(args) { return nil, fmt.Errorf("not enough arguments given to call that method (expecting %d)", paramObj.NumField()) } From 4afef16ee6261d57363d0760073d0bbac894b9ae Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 31 Dec 2020 18:43:14 +0800 Subject: [PATCH 051/246] update NewestNetworkVersion --- build/params_shared_vals.go | 2 +- build/params_testground.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build/params_shared_vals.go b/build/params_shared_vals.go index 994c32934..5e2d18ff1 100644 --- a/build/params_shared_vals.go +++ b/build/params_shared_vals.go @@ -25,7 +25,7 @@ const UnixfsLinksPerLevel = 1024 // Consensus / Network const AllowableClockDriftSecs = uint64(1) -const NewestNetworkVersion = network.Version8 +const NewestNetworkVersion = network.Version9 const ActorUpgradeNetworkVersion = network.Version4 // Epochs diff --git a/build/params_testground.go b/build/params_testground.go index d0e54e3f2..618b0d184 100644 --- a/build/params_testground.go +++ b/build/params_testground.go @@ -97,7 +97,7 @@ var ( 0: DrandMainnet, } - NewestNetworkVersion = network.Version8 + NewestNetworkVersion = network.Version9 ActorUpgradeNetworkVersion = network.Version4 Devnet = true From 9334e733969021610b669665a4150443fd7714a1 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Wed, 30 Dec 2020 10:04:00 +0000 Subject: [PATCH 052/246] Snake a context through the Chain-blockstore creation --- chain/gen/gen.go | 2 +- cmd/lotus-shed/balances.go | 4 ++-- cmd/lotus-shed/export.go | 2 +- cmd/lotus-shed/import-car.go | 9 +++++++-- cmd/lotus-shed/pruning.go | 2 +- cmd/lotus/daemon.go | 12 ++++++------ node/modules/chain.go | 2 +- node/repo/fsrepo.go | 3 ++- node/repo/interface.go | 3 ++- node/repo/memrepo.go | 3 ++- 10 files changed, 25 insertions(+), 17 deletions(-) diff --git a/chain/gen/gen.go b/chain/gen/gen.go index 8e749095c..9ef959336 100644 --- a/chain/gen/gen.go +++ b/chain/gen/gen.go @@ -139,7 +139,7 @@ func NewGeneratorWithSectors(numSectors int) (*ChainGen, error) { return nil, xerrors.Errorf("failed to get metadata datastore: %w", err) } - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(context.TODO(), repo.BlockstoreChain) if err != nil { return nil, err } diff --git a/cmd/lotus-shed/balances.go b/cmd/lotus-shed/balances.go index 3280a63e5..591917bd6 100644 --- a/cmd/lotus-shed/balances.go +++ b/cmd/lotus-shed/balances.go @@ -175,7 +175,7 @@ var chainBalanceStateCmd = &cli.Command{ defer lkrepo.Close() //nolint:errcheck - bs, err := lkrepo.Blockstore(repo.BlockstoreChain) + bs, err := lkrepo.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return fmt.Errorf("failed to open blockstore: %w", err) } @@ -396,7 +396,7 @@ var chainPledgeCmd = &cli.Command{ defer lkrepo.Close() //nolint:errcheck - bs, err := lkrepo.Blockstore(repo.BlockstoreChain) + bs, err := lkrepo.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return xerrors.Errorf("failed to open blockstore: %w", err) } diff --git a/cmd/lotus-shed/export.go b/cmd/lotus-shed/export.go index 9b0703445..83567add3 100644 --- a/cmd/lotus-shed/export.go +++ b/cmd/lotus-shed/export.go @@ -72,7 +72,7 @@ var exportChainCmd = &cli.Command{ defer fi.Close() //nolint:errcheck - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return fmt.Errorf("failed to open blockstore: %w", err) } diff --git a/cmd/lotus-shed/import-car.go b/cmd/lotus-shed/import-car.go index 9fa853728..7f3fa7c89 100644 --- a/cmd/lotus-shed/import-car.go +++ b/cmd/lotus-shed/import-car.go @@ -1,6 +1,7 @@ package main import ( + "context" "encoding/hex" "fmt" "io" @@ -24,6 +25,8 @@ var importCarCmd = &cli.Command{ return xerrors.Errorf("opening fs repo: %w", err) } + ctx := context.TODO() + exists, err := r.Exists() if err != nil { return err @@ -44,7 +47,7 @@ var importCarCmd = &cli.Command{ return xerrors.Errorf("opening the car file: %w", err) } - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return err } @@ -99,6 +102,8 @@ var importObjectCmd = &cli.Command{ return xerrors.Errorf("opening fs repo: %w", err) } + ctx := context.TODO() + exists, err := r.Exists() if err != nil { return err @@ -113,7 +118,7 @@ var importObjectCmd = &cli.Command{ } defer lr.Close() //nolint:errcheck - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return fmt.Errorf("failed to open blockstore: %w", err) } diff --git a/cmd/lotus-shed/pruning.go b/cmd/lotus-shed/pruning.go index 8728163c9..5def0c6b2 100644 --- a/cmd/lotus-shed/pruning.go +++ b/cmd/lotus-shed/pruning.go @@ -131,7 +131,7 @@ var stateTreePruneCmd = &cli.Command{ defer lkrepo.Close() //nolint:errcheck - bs, err := lkrepo.Blockstore(repo.BlockstoreChain) + bs, err := lkrepo.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return fmt.Errorf("failed to open blockstore: %w", err) } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 581238d4b..006da822a 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -235,7 +235,7 @@ var DaemonCmd = &cli.Command{ issnapshot = true } - if err := ImportChain(r, chainfile, issnapshot); err != nil { + if err := ImportChain(ctx, r, chainfile, issnapshot); err != nil { return err } if cctx.Bool("halt-after-import") { @@ -370,7 +370,7 @@ func importKey(ctx context.Context, api api.FullNode, f string) error { return nil } -func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { +func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) (err error) { var rd io.Reader var l int64 if strings.HasPrefix(fname, "http://") || strings.HasPrefix(fname, "https://") { @@ -413,7 +413,7 @@ func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { } defer lr.Close() //nolint:errcheck - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(ctx, repo.BlockstoreChain) if err != nil { return xerrors.Errorf("failed to open blockstore: %w", err) } @@ -454,7 +454,7 @@ func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { return xerrors.Errorf("flushing validation cache failed: %w", err) } - gb, err := cst.GetTipsetByHeight(context.TODO(), 0, ts, true) + gb, err := cst.GetTipsetByHeight(ctx, 0, ts, true) if err != nil { return err } @@ -468,13 +468,13 @@ func ImportChain(r repo.Repo, fname string, snapshot bool) (err error) { if !snapshot { log.Infof("validating imported chain...") - if err := stm.ValidateChain(context.TODO(), ts); err != nil { + if err := stm.ValidateChain(ctx, ts); err != nil { return xerrors.Errorf("chain validation failed: %w", err) } } log.Infof("accepting %s as new head", ts.Cids()) - if err := cst.ForceHeadSilent(context.Background(), ts); err != nil { + if err := cst.ForceHeadSilent(ctx, ts); err != nil { return err } diff --git a/node/modules/chain.go b/node/modules/chain.go index 095bb501c..782e0b32f 100644 --- a/node/modules/chain.go +++ b/node/modules/chain.go @@ -77,7 +77,7 @@ func MessagePool(lc fx.Lifecycle, sm *stmgr.StateManager, ps *pubsub.PubSub, ds } func ChainRawBlockstore(lc fx.Lifecycle, mctx helpers.MetricsCtx, r repo.LockedRepo) (dtypes.ChainRawBlockstore, error) { - bs, err := r.Blockstore(repo.BlockstoreChain) + bs, err := r.Blockstore(helpers.LifecycleCtx(mctx, lc), repo.BlockstoreChain) if err != nil { return nil, err } diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index e4d9b3239..b57fb64af 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -2,6 +2,7 @@ package repo import ( "bytes" + "context" "encoding/json" "fmt" "io" @@ -299,7 +300,7 @@ func (fsr *fsLockedRepo) Close() error { } // Blockstore returns a blockstore for the provided data domain. -func (fsr *fsLockedRepo) Blockstore(domain BlockstoreDomain) (blockstore.Blockstore, error) { +func (fsr *fsLockedRepo) Blockstore(ctx context.Context, domain BlockstoreDomain) (blockstore.Blockstore, error) { if domain != BlockstoreChain { return nil, ErrInvalidBlockstoreDomain } diff --git a/node/repo/interface.go b/node/repo/interface.go index 4ae68f880..564fc46c9 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -1,6 +1,7 @@ package repo import ( + "context" "errors" "github.com/filecoin-project/lotus/lib/blockstore" @@ -54,7 +55,7 @@ type LockedRepo interface { Datastore(namespace string) (datastore.Batching, error) // Blockstore returns an IPLD blockstore for the requested domain. - Blockstore(domain BlockstoreDomain) (blockstore.Blockstore, error) + Blockstore(ctx context.Context, domain BlockstoreDomain) (blockstore.Blockstore, error) // Returns config in this repo Config() (interface{}, error) diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index 88d4eccd3..06937c052 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -1,6 +1,7 @@ package repo import ( + "context" "encoding/json" "io/ioutil" "os" @@ -244,7 +245,7 @@ func (lmem *lockedMemRepo) Datastore(ns string) (datastore.Batching, error) { return namespace.Wrap(lmem.mem.datastore, datastore.NewKey(ns)), nil } -func (lmem *lockedMemRepo) Blockstore(domain BlockstoreDomain) (blockstore.Blockstore, error) { +func (lmem *lockedMemRepo) Blockstore(ctx context.Context, domain BlockstoreDomain) (blockstore.Blockstore, error) { if domain != BlockstoreChain { return nil, ErrInvalidBlockstoreDomain } From 446a24ef3fbb2c8864c14400e4f7863f498c2bc4 Mon Sep 17 00:00:00 2001 From: Peter Rabbitson Date: Mon, 4 Jan 2021 15:36:36 +0100 Subject: [PATCH 053/246] Fix forgotten test-call --- chain/store/store_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chain/store/store_test.go b/chain/store/store_test.go index 5f1f336f3..d41d7ac44 100644 --- a/chain/store/store_test.go +++ b/chain/store/store_test.go @@ -52,7 +52,7 @@ func BenchmarkGetRandomness(b *testing.B) { b.Fatal(err) } - bs, err := lr.Blockstore(repo.BlockstoreChain) + bs, err := lr.Blockstore(context.TODO(), repo.BlockstoreChain) if err != nil { b.Fatal(err) } From 1bd2ae034f05e4d259ce017fbbbb1810db3acf2b Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 5 Jan 2021 00:32:15 -0500 Subject: [PATCH 054/246] Create a calibnet build option --- Makefile | 3 + build/bootstrap.go | 26 ++----- build/bootstrap/calibnet.pi | 4 ++ .../{bootstrappers.pi => mainnet.pi} | 0 build/genesis.go | 2 +- build/genesis/calibnet.car | Bin 0 -> 1970863 bytes build/genesis/{devnet.car => mainnet.car} | Bin build/params_2k.go | 3 + build/params_calibnet.go | 64 ++++++++++++++++++ build/params_mainnet.go | 5 ++ build/params_testground.go | 3 + 11 files changed, 89 insertions(+), 21 deletions(-) create mode 100644 build/bootstrap/calibnet.pi rename build/bootstrap/{bootstrappers.pi => mainnet.pi} (100%) create mode 100644 build/genesis/calibnet.car rename build/genesis/{devnet.car => mainnet.car} (100%) create mode 100644 build/params_calibnet.go diff --git a/Makefile b/Makefile index aa7575698..98f9d2f2c 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,9 @@ debug: lotus lotus-miner lotus-worker lotus-seed 2k: GOFLAGS+=-tags=2k 2k: lotus lotus-miner lotus-worker lotus-seed +calibnet: GOFLAGS+=-tags=calibnet +calibnet: lotus lotus-miner lotus-worker lotus-seed + lotus: $(BUILD_DEPS) rm -f lotus go build $(GOFLAGS) -o lotus ./cmd/lotus diff --git a/build/bootstrap.go b/build/bootstrap.go index 80c1529ff..98a3f5b0a 100644 --- a/build/bootstrap.go +++ b/build/bootstrap.go @@ -2,11 +2,9 @@ package build import ( "context" - "os" "strings" "github.com/filecoin-project/lotus/lib/addrutil" - "golang.org/x/xerrors" rice "github.com/GeertJohan/go.rice" "github.com/libp2p/go-libp2p-core/peer" @@ -17,24 +15,12 @@ func BuiltinBootstrap() ([]peer.AddrInfo, error) { return nil, nil } - var out []peer.AddrInfo - b := rice.MustFindBox("bootstrap") - err := b.Walk("", func(path string, info os.FileInfo, err error) error { - if err != nil { - return xerrors.Errorf("failed to walk box: %w", err) - } - if !strings.HasSuffix(path, ".pi") { - return nil - } - spi := b.MustString(path) - if spi == "" { - return nil - } - pi, err := addrutil.ParseAddresses(context.TODO(), strings.Split(strings.TrimSpace(spi), "\n")) - out = append(out, pi...) - return err - }) - return out, err + spi := b.MustString(BootstrappersFile) + if spi == "" { + return nil, nil + } + + return addrutil.ParseAddresses(context.TODO(), strings.Split(strings.TrimSpace(spi), "\n")) } diff --git a/build/bootstrap/calibnet.pi b/build/bootstrap/calibnet.pi new file mode 100644 index 000000000..cb3a2efbd --- /dev/null +++ b/build/bootstrap/calibnet.pi @@ -0,0 +1,4 @@ +/dns4/bootstrap-0.calibration.fildev.network/tcp/1347/p2p/12D3KooWK1QYsm6iqyhgH7vqsbeoNoKHbT368h1JLHS1qYN36oyc +/dns4/bootstrap-1.calibration.fildev.network/tcp/1347/p2p/12D3KooWKDyJZoPsNak1iYNN1GGmvGnvhyVbWBL6iusYfP3RpgYs +/dns4/bootstrap-2.calibration.fildev.network/tcp/1347/p2p/12D3KooWJRSTnzABB6MYYEBbSTT52phQntVD1PpRTMh1xt9mh6yH +/dns4/bootstrap-3.calibration.fildev.network/tcp/1347/p2p/12D3KooWQLi3kY6HnMYLUtwCe26zWMdNhniFgHVNn1DioQc7NiWv diff --git a/build/bootstrap/bootstrappers.pi b/build/bootstrap/mainnet.pi similarity index 100% rename from build/bootstrap/bootstrappers.pi rename to build/bootstrap/mainnet.pi diff --git a/build/genesis.go b/build/genesis.go index dc4ded273..812f5a9df 100644 --- a/build/genesis.go +++ b/build/genesis.go @@ -14,7 +14,7 @@ func MaybeGenesis() []byte { log.Warnf("loading built-in genesis: %s", err) return nil } - genBytes, err := builtinGen.Bytes("devnet.car") + genBytes, err := builtinGen.Bytes(GenesisFile) if err != nil { log.Warnf("loading built-in genesis: %s", err) } diff --git a/build/genesis/calibnet.car b/build/genesis/calibnet.car new file mode 100644 index 0000000000000000000000000000000000000000..f8af3987840553cbff0f159c9167fdd60826f320 GIT binary patch literal 1970863 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 z zVr^f1UGr{JZC<96YCl@ZT78p!)!y_A;*qW4w z0uQ7y2#%5>>Y8i|@I2GMskZ^KP#nmtWIu87*5(F_r5KvS{e{RkO|SyHbt1*$h=xCR zMVm(mfmkRGWEOt`qOys2|I>y++Oqgei>_Q1z4;k2-%i2x_&07`iW;7Z|Cj~E{bSZY z*K*KmL0xTKR?9Y*(UkPoQk+1|Q+2&;7@TbEZmMf5Nl2C7T9ZDCsQ3cIhvGnLbxmz3 zsCY;2^il`T1l}u+cd&9KSgyAop?5N@;-Trf4#YxnAhjx}!UN*`1=U}vCT=*ev->VF zTOAYUj>vo`Aqs)-*l7h~p*WCQd1+3@F9mC5o45(@ZbioX7i8s(Y#NC@wbTFlSRmQ_ z{16Mpfz*dR*D>z2kzIfB>ik#Q|#lx@Lz~3+k%+vRXAh^dTuB-#)dV z7i_aX@F$FV0<)`Ze$V$&dZDxEc6&WAd?*g2mX%$@adndV!jtz0yg9vcQG+$=i%kzu zi17R=nN35@XMk8J4y0CA>w1(C9B=UlbTwJ?=zd|wB2NS7YiCET8%~I0d~*6gEEES) zi(25mw>nm2oeG-p1RV$KLqWWz(vA;ygqXL`@bSVixq(!e|GDmzI%x8hseJNq|kJ`tugWJC3O7 zrz3puYm$IkP#mDvuX{6SwV>{vE~}+ay67htkZ8NXs7}wKCwqhD<;saSR-aZ|DtCe= zMu-G3t56(Bt*iIZqMy(W7i%nPM|j;T2%_i>3NCwtZ0P+>O=PokR|JTK;y`Mh{=AMU zfEu1Vh%1~iJdg4G&WqzaDk{h zKr9poQVWN#Z}XEO2e0wy^mD2!LOT4Fe$wH$_rJ3_7)?Zu#r*)(g5m(Re%*sZs|9tJ zd|9n~SraAU8rCB?==X@N8QewiT8uhvI10P2&0EGg3Yb;`!-wKPY7ru0-7ShJg_ZI< zvLrPg{kS)LFB%(0K!~S|OG|p-=v*x*4x|=wW7k7=0pGSR0X%Li^KZ@8AC_k2G_?i^ zJJ|5?Jaf{4t%c%1YLUk-v7d?;c)JY2y-dcf`bJ;a9;x-Jh-F<^U2;#A={67x#evjf z;7-w7zmLy;_R%M8`eA0-b3{yH`d~o7Q3j z2JNoC3asBsXVk3SwM16~h7ZMo)GG5#=)%CZ{UnKY@ zeSQG3P#j3DalP-TG99lR;nW3*Jmy6CT*4U1pGu zM33s{?H&ncjN*Ey03a5M1F2>B^QCf+1mfp=S;Ws&u%|6H3m%}=GkM1!eL;Ubva!}J*e@uT3uJ0KQ{1F3~s zVyWi(G4oYvmQbJ}HwKeD#-ks%X%pWRGAV~T2e<2b{&(F9iu;dR5(Kb7BL?bGi;Inz zfL`gfc1gRs>KxbFn37<%4eru#JgKV`@1+lgfc^zm1sI|2ATvlFu7463PU>_eG|n<&=YNHdTFBW5>ONJCpyW zFO;yXDJ*SPWTiiS{`=1h+XS_wcWEi`Kb;sca3>~JP@wqnAhUt(wz_@VlMYP1u2lik z1e(q1_iBNYDoh`Lp4t~dsHz>;I=G361%uSX>*2Ry1U-D8X z=CR~@B%Gli?rmiEGIjAzWXZWozBa7V`-G37~`(3gL`N#yg3D+Cm~Sj(k$yr{uIEEES4i}?M^w+=#ft2~O; zOf3Xo!ZaSvo2j{RWimZDC9DZPH3MRyIFMMcUC@bCf86QR{<$`>Pku`2ZGUGv)@|>Y zX_R$#PlJ7Swt5vRNytRP?GCNO;bKA&pc;PuyKQYpOfFMka#4jzkyA7*x-lw7}xtWX`t(d)&2fdYakYi1DOTe zrUrxYyp2!!spT`I+7B?6k_Ye;D>sElJncg={L)K+SSSu;7M3RGxD&2Mz~{pd_a%G+ zNizKkyuFX>cCs!grDT44Pk~q{4rJDWt4zD_Y&L-}n#A{=S(57-b2SYJS`-yRB0`B9 z)VSn8EEESa>#M4SY6am=#Pwr2!?uKcYP=IF^F&?-u{~=Zgo5I1Sm1siiu=c`f3D7; z)q=Wyx~vv%q&TWlA)VLVWQtPT=J0#li4G%J6?h&91V65_*z=vQq(N~Ywdj7n%$xW$ z+Mi({g8ze4HclvB$jr~A>b*>t2MkTz7!xq7P#j3DT(b4hydu_>$eUA-J(RT^Bqv%| z#NyeI-x4#TWwKRe0kKdVNUg%T*hBBp3E7JNPp75C+^?B&8`M%rW+q?uawDx%l%1;u z#evi+NWdyct;EsEIkJB0eVNt>?3?uUYK?wV*gat$){Bfx1qW;q%u)I zmmfVg522+-yLs{nma}h#$~_ktJ`@L1D~2yzWU_YhbP01aNj_gRdGk@B4sz*-p>;=D z{<%F@93U2o1F03AK~(zjvB6fp8y>n6&%SKU+`?D~@`D%QSFPTOP*>~$u}~aHt%DEA zN{yv?!e|7i-|7wrUZ~92ec*H9vX%+>YHG5DNdUw`aUiv<>PujJqucvFH$O}eKMcli zk+8k7v{NpvkYnnRRTp}G5evnE)H;NF(&6ksF(yWiVX>x;#D_lFEMoVQ8(qa#X75ws z<|v>R6bGpF?|Lgx_d}P}+9`8d$XOIc^Vd{9N&QjHLv!mkRn6^!Oe6Yx51pixhk#jy z;y`L)bAB_RRBg;|zd^ZWq~I_^E8{{(kvtd5yxS(gbX0eKFofbjYV}#FMsWHQJ$#R` zl_;}(t_k z*rBnVR3gkfub(R!0BS*TfLi~qw*s}kaak>WwyE1v{5Gdct_WA<8<*3r#XcL}VTQSS z($4uw&GpOqb7xQ-NUhO2!iFDo?`UOSmS zDf0a<-{CuukVGeIZ{&E66h&nMu}~aHt=qHncPDfwHMUtpi1-gHXr?gY$kHlP`X|U2 zI9IvR=YUu!4y4wi%<*n8T^K6!pmodZ<{7%CmI}(A5q`O)*zxZ!6IN<~T2LII*1zkm zK&@k6R%@EfMaDsi9etjGjT4Qm%baDVVXN)k%AHO3o3;;l-{}CuhvGnLZ7w`4(A4qd zlb$cDOP>8k9n*WXie4fwOlnx->&d!w{$vOg2U6>*H{Ow_2wr9N4|!vh&Wl=`7o&&@} zaUivbL^Ff$@Jst87J57ku6Xxw*leF}iaGZ)g$WAD9`D3kAQp-PsfGB^NboIVY!^!F zJfhK|(cb-g+lG%G--tLnbcHIu5-taND^T2j)Vk1Hf%w(0-Y zTRB_Q`MsNSf_q5KbvGWa=z8b0+2_eO*bY}c`$z^V@2omK_u{e){o9ICo|k$nv;w)o zOZH_l;{)OA{6do=m_rR{5APh2iuZp>*d~x={ri%adMiYjl{0$g<}ht91qErmDDKmS z8p^DM=3TYNl(G0k!xaDbB`@_>I8r_{9Emh~RES1xTp>n3VH&4ED9XHsbI@RxGG7gvB zblc^0#|R!uw6Pq?SFwt8(U2_V*<(^;RaN`@inwWzE0N>R^1;7^5Q0eCWY~ zCi5~!Kalpb8~x;8mi%vT1!_;cd>SH(q$GQgYCLG&7)!)TR?8mk?KAM`kjmm|Iree*>Rsx#mEi7z@s!jz)ai%Q9Pz%h5%8yi;y_|OAo^D7TEHTs^5uP}U?=8@53)+O+MzT&a)v|AQp-PO#<>nd>*1hR5cnCV!w|zV)rVgjKqgE?*0aJIOSWeZa08fC=Mi65~aFq zZZl)5u@hsvl5WL9>LT~LY+JWq3G={WS3lloptl0W{Ues-ud_C^Sy1QI%Vyn)d9t-@ z$Cpj6vyGtiLFcZMOB74StJC*nog&`;SmEa@X;2(!QY&3LUMROdO@&F_BCph}BNEv2 z5UVcDW#C*B@v3fkybDY!6bCZPD^2Y?yO!rzf|k z0gZSwm`&7d3?+NurVNVv$E<&@&Y;zTx_-K>)*hAjcc-6QmZv*}4Scrj^@GmON4`1< zIZjW-?jt&3umi(~;y|;?N2f|P{q$tI_fx&NV~ryXn|8ORC_<(xuQA0@IpuwHAQp-P zsU`b<-k@7i&?Inc&R5C2rmS3xq){>NM4e{$Ir0rODbd(!ZwA>L5D(q`86I;GW?*nQMX#Xb*PI`h7>T9moS%!|beAoLS>Td0D-_3)3&ZV_28IvCfo7GQM7Ul6 z^S+jvPs7y==U8lf8k|74C&xF|m7lGgLEi zK3=HOM;g1k4FyWyi-1@t4y2Yl1I5*nhM@+_==*|h6K|Py+OG9$YP_yq4H@I&YjwU3 z#6oePN!Ux0dX>f8@lAb#$1+iEE*Pc~0iNtDBaKdv9^piH{kgGF97wIotLdRcWo-%E z9FF3cVWvqmZ}j=mR30Hby)W}^%i}2)pcWJdsP*r9D^T}Cm(}WL%DDdoyGcK9eSyVv zdJC_YNVyub@5L2{*N<*c_hTOcvkJw5W|b~$>RnF7tr-6JN>(OklB|((w~s&Le{6Ye{>Uwh2lVJ5e0YJk5v79r+THH*2ABOiEz#4*4h`hPYLq z>Zlm*U#eWhRp_ep8ZQu{tW_%U`}F$ws^mt9Fu&<}?-GgwO+r1_NCaB1d7(gwsxdKM z_d;F}8q2tAlRmXvCWm_P!WCc=pg53P5+m(N_7No=8oQ>L*YS{Qb*8I({EdaJ7W3w? z8WA0B0JWevK&^k*TYn!lC8MOmxS!N|Mc`vN`s9X#)cwx8 z6;K>#R=*SfJn?Nrrp8+K93Ry|N07Y)(Qt0_398er+R&x*UKDJ1IJ>y@`&XSRt zl4YCh7<-Z+zeCdqJ0eb?zX+%W#evj%0h7%bqr9!8?$u;7sIga-gmY53*-k57!c?2P zjgHF!#6oc(wMwhER$HawPH0%?!=HHg1j-{dYqnLS6gFt|5mE7~!T_;Q9B2}@;asoE zK4jTK+CAlF!@hxxte;6a#8Oae!L?uD1fUj(u6JX(84cmW2A2kv`$P#j3DU6zH618-Gg}8tnK|s&$A%nkH^fb7nk6qzpY|_> zNa5#)yuG-94)gd80#EOj(K~kv>(StC`b77(57rJa?pk)%Uth^n$%C3DP|5#mmh^#| zB`ExJIXS9zhiDsGqg*t3EZVqYG;L-{GN=VSo|Tndhkv;c>@V32ZkCkv!Q9uTe}~-m zy7Yr|I(ZV4;${xr1IN}DL)^3$63Okqtti!esaZ0QPA3{8Wy*F(>lxOI~W0>_@(o_wijBWefG@wfsUqe2vJxqb$N}g?Cx?x@3p=-*+yW zFEvZ{kSsrhP}1DtEHL>o)^@A)TNPndd~Zi$FPU*K@~A2L-;eTAvxJB{po~+uVnzP@ z_>wVg2;*C^M}ar?)-uVOIqW#En#})w$xF==3Wtegno`zC??DwsW*AQFts!~xEx1TK zZsda~+EmoPe+yajrDjRVgLfy)nJR(|5<&=gYkfm(r&z%cWu9@dXRh+FP~RVB48Mg`B`6xKl03OFL$7~^nVz#vZIOe!HCBr@Zm#6;MfPnfexvg2dGpU zJkphi$+mD;)UQWYn&ov0|NJx*GZ-K}7&xR1#6oeP^Gf+EP z7_KJ;AWOfY@!=3-;9Et;-SH1>@)D{(s)m%w*!1hn~dAZ)2qaO@ozh|pWUJ1*> zaei$9#epUPWlmX@x4de6+AVt+MD&W`5L>yA(d+{Ee7iZEn>9R_R z1jIsdAhDP=b}Pw{IyFX}6*%VblwQJ#4jft}KJemWR4r6ZmPrF*p*YYaFwV`+JpMpf zWmO=^qi`y>$hw!B17luk9L}MVAA&>k5Qv51K(QK)wF+9sAE&qlj426J-rMh8L)P&< zWrE%NoIP9_k#-wsmOyd;i1nYdHndq#=he$*$%?m_ZgbSjDId9EZSq7%koPiR$k*EF zo3_9&ni^A00HcTEK%;*frAqm-Eh^5k(wawxT_)R+U|7=Xj`UjpJ}0WFX5jhF8x#jJ z%M@9qVJnfF6iXxx)4X~A7}syIJiZly7g@Z19f|d!7O=HY9LOx!X}N_sQvauSc*ch7 zx1;s+IUkScF`JzCBB40YB<#5vAg<>Hh-364uBXb4aMCH@D>TOV1}QyR z`I1Q?fBpy;6bFj+Z4v$G@1Yb*6o*$c_^s@uwa!KK%{+XkPf5@boxEB-aF+$e{bSZY zS7*>_L0vyxR%?%V?FQ}Vm+u5k1q9|kWy{$_OZdIUv@)Mro;~{_@bLK~r%)Vd_!;D{ zn2qE+Cs)fDvb}0=_3|C>njC8#)A%B=xS|Bn5(5rEaUiu6(kAJtHI-BQ;H0W0JoYf) zXuKp@5AVVIX^_;1O0;(Xu}~aHE&qbJM~GBzb7=!chz4`Kd?lw-`Y!B#{Jq;ABOf%o ztAJQ24m1fVLq$RfNP6avl8ng7i82UhO=6bX)IEN(5E#;QulS$8d=!cU#jYW^)Z5S; z?cO!|P-J5R<8vja?p2eUNrVcw1)3=vlPsVX6bGpF@0ul0*U6XF`VzPlgEp&^Df_9y z4BotT6XyG>zN$sX>j{?m`sF~M8emqTIMDEq4ooOxl}?k7XfeMD|=28TyO%!LUEv2jqt@D(ZUwp`Tg?tH|AcLq3YI8ts>Bj^Ww7| zd8@E<0JWevK&^k*EP=Wox~vupkBUV3_euWPHZ3%!0$Ayxg*$Lr3%A$VHwfeD7`VxS z;X`qt;rAEb?ChJrCbTDjVxF}b6g$U|c;|_6A?s|#$ZGOwj0_M9#evjfBW07F_e@|; zCpUnfBz4lvbB+Eq@>t1tzMa10`FP_OAQp-Psa3_=7PzQ=$K!qJ7iO$cO=6GtHsw?> z8#j3G1}Z+OJ#hkJp*YYauvt#*CYaZ~u0XV#Hy)%SO_l3W8L0I$f2{ZgHamiF28e~? zK(R2-zD~`rdlFa<@6(S*Y46kB9jPodL7@#kBB)m%)iwmwg5m(R{#~;K>c01~T7EH6 zRoNqC*II6DuAfM@Ay;}(y(pzU+*jRg4Nj=hx(f^+iUSS*%LY-z{V7tBBDn_(tSZss z<<$wDbI#mVd@bDpk;GQbKr9poQtRE%7an)@QaA>TWletER6}8VEawkjxJ~p_G&q7# ztZ5jCh2lVJjTsxR_a_P)2!CDa^wt01=_nf_llMK&9gb)DV_Ybj4-gB*fhOUA7Z%Rx zheG4nHgjjT9(xGx=JT3FhEAo3!z+&lZeKm`7(sEM*Z=}8M(03yy?7f>D*koOXy4Q= zfwq3nuUF`EeV(%;ou3e)I6$p`*DQfr-?*$6y-~v!15V3IdTHHDZ~HWgS*CfJw%%O4 zO_inhV#S;1FQS6tK*J};yT2H)Rqr#$8swAvXo_(Rpf)GGCV~XZSE=l4mdRB zsZTRb9a?4qW1%=uYzD&X9iye~BhT~%8U!rfZyTc0WiZd;$t;VFg&yAFI|7bcP#mDv zziXC2tz%zSD}&`(*=zo`vJ@U!_6`LW)ar2NU@XMopm(7eRo=(3jKJ`rIMDF91?g=V za%977UdkBP*=PrUAKzUz9YP*+EtI8O!izZnJvkHyQj0l1C6p(hN?Lg3NNLrM?UX#s zqV=Gzd85z>A&OhJ*b&%TC=R5S_Q`#o^?Q2k9;|H-s(stF$O9W@v=YX&G^epK=ELJ% zfmkRGGzq%ILo4tC%{n5c*O%wXJQ(I@kN6fNSPs&anGlU^Y0hJzI8ZFq4@}ORB~afd zyVxvY_`o~&qf3ttp7i81vv$o(xKcth`TNKtN4v`(UWBz?{4i0|cbhZRy)M85u~^y8s?ZCQQ32Br?` z>|-XKb|0emb!Ba*!+*IDeD?hmMbIxxG~@i92NG<}(W&_VOgBA5$Se?-opu%rS9Ive z6T+!I{o9ICFoI_<>-+5^G3+?0qWkV|L>`deo!p5b*Z)FKdiPcIeagjLUv|V)H}StO zsRk}7@l|K2v!g0}`E#9PKmj#hQjAIl42rL z)8GRN&l?;9jOX7axlkX4#ccZK&c6Hmk{;la?y|^o#i$s%Cb!a1Eu_P6`Uf14#xd*bEuN*g0r&(z!? z9d1DS*Eta?5*O8Fk+UN|lAM7Qg3_+!HQT$`C_;p z&6=gjUUH3C3Mw2rye5JQ8anwOFBRPi&zIhyIMCd7k03pLF&slbwVWiXMq46?vcDIu zNx;#M(6Bl1N>1-S&@6%C&L-hkv*he(4!!OOb-=wWR(b@<3qBHe7~#@=WdpS1>${>H zG7p?I)}4qQpBoNlX#jsJC=N7ub|FI!rSfudHIGfc*6eb-)0!SaJk=rM>89t;Snbn2 zfmkRGWIzf<-KYC%(fS@PnT`Y-7$(WhrVnWCD4*qs%&Y2fKKlm5LUEv2Isd2AvC<#4 zCkiMz%efxVa`BjlAuw7#zX9`(kGEFO5Qv51Kw@RfUyn_G`10*4XC@OhIRnG9RT}{g z#V6b)Et(TQn+-L~1*-hUo38P@E%I%GnU90T=w2@}^!5(Oq zKym+w^`Emgv{_K+)yrlnzGEHxvgl3)P11s^a2usoZvuO#(?NedWWE!LNn#?3^E92MKi~&+iGLIMCd} z68qnR4>Z=@oEe?UN5H0gpoKLD+_Ilv*io zjym*@1KSv{=n#^q&g%Z}!$?pZ$N`hcPu7_Qx4fRfI(^_-6T#C;@)ScK<+v$<^S;|7iNTSRdre=H(jqL=o<>W zV%m21IdeFxfk-tld?*ey{4tgrs2vT7=BGun*tfYA4m_oW3@A)wq_Fl*n+n9Etbtf4 z4&=bX6lF%QyjyK+h{m+Y&U90&s#VVg{I2c}^70B03&nwAIU1~+l@M`P-!jxD z$tDbzSg%N3D?73YZiJ(w53N=_KfFM3Ahnze_hw1!yx#6=apEV-Q{j8egrp$fD$>sh zoo71iDH8{_7K#JSt>ZgM{;8m{kxXa#Org@zLA>z7z+9FN+*@1YDy^vxVga?FI6$pm zcU#bELER5sR%?vrMO|y2B~7Nc$ANW@{jEAPN7gF|LU!2_7WKJAr0c-&p*Ya+z1$J_ zSOR$$Dv1&qM*SCSxcdvkc^^077|gcKSulE?za0~b136$5Ai)~udDQA5Va%^*BV&rC zukisPHuGabtfw?5yx=WhYoRz$Y?A86Bl}y!6tZS{uB#lVa9>a=l1#N(>bR@z(}bg5 zo$m-x97ruc*?Vs?>u(TZc#q_@O&RBrI6F)|iN?h3Nk(A7ackcLwib#5&23tj&dZO; zy;z|pIYPr7CnR*Q%2d6l_s377k1RfW1qA?VL2-auzwWA`)q=Y3y{wk+s&LkP-c$3Y z^{MxsJMc~u%09c9!mxg586h{?R7(E5a}33S zV)b;hU10d+oGM0W%yBIE-i*1_%Vhe-`e*b+6WNWEo}Un*IFMRXgt!f3V;|Fv0$%Fd z6&fnGHE>M^aI6#=2pQ?5Ab?@RMcYfliqG z#-NOrhSHBl7K97;i^(KFtkoZM@D{ z^S~Q`15g}j_?GXQrhCS9yv&6ly;G zxOG3HLhq|5e~4BYqHWDejVw34vVGz>UAYAou(ePeC{|WAZJ;$^64~s?)Fm!X%#d~u zh3+T$wDV7Ik98qEEl(g8iUX;Yi`UnQvUk=Z^9mi$KIXfpF>1k!t-I38>fHRHX*Ajf zh=t-nb88%k#??WlbOPHu`;C%`P}lNfeY8^PJ-0`t%i>&cDd$HmC=O8T*D4vbT2SlQ zm(_ZhC-Jc#ran3}jBD(@0owKXn)M#nk3T&25pF!}RBb=+YCv(I;orU^gnl)aP>BL* zLUFlD0qLV^r!3L-TW|l~9TAOJ$hg3)LUAAmaA}aNfAA1>h`INyQGMCKzaBUZn?y1! zoj1dlHMeDY{wNg`2a0V9TY2Isn#yeDWJ7^gWx{57Bf~iaW(qb`Wl`4F8-^d)S||>r z7F(ZsO_)y@;qc;*+Y;O`Lql*HT(v9{!RyUJVtM|9=P%2E;y`ozuYXxmbZB;i1hL_p z``TWm^*x$|IO}oh_~I2wT3Y9>b<_837n>z6uNxg2v@P&A9jPKzc5eyh@NSbnpKYId z#+uei?$h_|!aA=SB_9iYur|uo3@UHD7C3gGW(idC|C%Mr zpk~R=V+ST;T=PLP`VYo!C=9XA&#tUrS->NIeKL3J6k)L7!Z!WiUc)5@Zk9;0&)03} z5g3QVW1C%jzvV?Lcr>f@BIKIdV#k9A6`TTpTTx2vQnTbb{i$B7mW#rN%F2Le!OD{g z6pyrPJK&_ubOy|1wh(XqeaTDB5?!Ce;eL;Z)M*?ff^3?@T!ct(OMzOvhSLDw^&Ygo z^1m;6sae8$xK?+|i9C7sPH5hwv=0sQ9jB|F2bjD|>BL%047R_Vg}%%md^@_mXn(Sz_wx&OZ8rDjRxsnzq)Z4;HMs8jo##dDG*&~u`mbxd2RZ?KU~;$vm-OEMJNA3tmW|fx zw<}ldG7FndOj5$4guRsHfmkRG6dRrl>ukC^!z9^RbXwfr^PP&~zIZ20Rtb*%RFGDx z3>y#&#eqJF^*s|iSG-qg0v5R_v4k;%e|$ZlHX=_sPc^@#J2!^-{2m^P1HEK&ZSHo$ zBnD4T8VZINbv#yqXW;%%BA3q1NmjN}mAKc-K(hpjJNuLUYL=WG&7m(?pbof~#p-(D zkbovU7@mfe%n(E2wM(dRZ5e+@I03c!;0s^}-(KhI4!zEfT({T!y*7=K8=g&Wn2gQNJBAY#HlQ-K5mYhlt`zELA^t@HE zM_|3C>1XWc+OqIw2xyi-asP-V`Rl9=Z5Gsd^|D!>aPJh7^f9T+;a|!3KYmyFWdQX( zr^o2VT={()p^#AlKmjNYWR`7BB#gj!-(p^)(C7pXL+*m>E7sWLEi_sx>jqRo2j@?` zLUAAk$b%2fjGiD~XX3*-de|IIQcY@yoV|muNvxD1H~Q5i4%k{K4iwv9c*DuAWQm!d zu=lS1OF7oey>y%iSgQN6S0##eoN&)yY6QiB%*t`ZtKmL&BV1V3CtIb8Z(d34{^mv4eRsCYNF7!K>nFVIP%_=OOP2fg5-z z?jN)MxjKVZ3+npmvRVq|+Qj!CK8;wQVCxN_Hz@Dksp?OeRMsc>6zg7_CUSNt_~+|U zP#j3DDy>D-=kU>;FQ%ntQ}Y`HmMO}3vflNlgY{Trss87!9ViZ@ z*3Op)?`-SawQ$Ll4u`VkJ@HeNDTf^w3_lhmXMG`KQvlS0;sCY&U9$x0I{C6%bIhl* z5)X{(i4J$z4>4?hEKtb6AXX4oKkeoFuKD@?`A@;1IFMSCb7t5(SWg`9@n14+8 zRDZSCIYBOB?dT}Zx0fdj_+vqFAP3$(l4>cLxI)9iqho6R4c{fZ3`soT4L7r&QNTc3 za`kf{7K#JKDhD?l_v0)Cx>=uUkyQC^1wF%r<1L!AFK^om)xG`f1rQ6xfz4V1 z8&yEqZJ{t`Dz|;zI!KTjGp7A$zJSQEfBgKS0Ez>tMV7u1f!H6u!hf^DcV^-(&hQOI zH7opMhm>*gkSlqrF@Rc79H7>}YnDLW4_#KPKh=43lsYnN@;yu`RfN6mv9pE^g#n#N zg}{)W^6X7aU{;|xkXi?p!W?|G#baHx%#8;)Jse+bYH54+B`q>Ua^ycD!JogO7m5Qp zKuKoK@Ag#UdhxUbPGzEG&W|)W20r|33b|*xmO4eg=P!MH*O5h^>*}qR#v&*&--aPcsNH8Tf zW;Vhf#2$(lptCw}@j`K+Sk2AgY{Ms8bZU0=Zc|myoP@mNP|cyQi_0tlz@PABPHF9fFOv^-oJgX z>)!vxi}AR9-k-;3X3ce8Yi7-N#jDFgV;_EzNZRBt))_py^i1bR+ypOc} z(GKb@h=Zv`c@bBC&Hv4%@bVQYmX{5=s2%8x6oz8hIY)yRZ1Jkbp;!h*lS=`3buj3u;iDl6d&`y!d&ZtUqgN3KI^fjCI5-z%1Y_OZ{ag*S(TQ7p=qV_H7O zaMF-*G13$7VK)IOftHJv#HC>tL}d%a!PLrpIm7obocg05cISTV{bAbC#C0?)^MJ!4 zmIvy$?usBoqYC0+4%{dhk;#hAin(U>HXjrV;$X41 zFH>xO4#hrgz~sqF!P2LWcbMdEDl#+Zxx0&j8u5G+iUn~nwJPfHl`M}a>LyBi*bN?J z^&cNU3mGKWxW3(V{5g4sQxb{=aWJ)h`^l1lH_w@tExx{7uF~P$(E2KCzjfi)&Ns#E z#WQU6E2ym||1Oqr?9Xvj=@l2~=iWO(O)3Al_tM*_$75*6PB=A$<0T)>zcyVTYVXAB zsCc0vu~0EG-gcjg8p&60#$vBy8D}Cv?ZHHY6-z+L|1Flpz=|bNofvxR+(Il4&8fVl zu@$1#)Z4FUedy)%oLDRJ7|Z{7?^)unKjHuTm`6t$kH4*ififS(60KqktAzYVo}4%B zXP@g~Yc=<48^#5lg1( zi(`M@pXYkB_8vp$J`e|+SM=*s9;FD}bR4Ft zLZ416Cw(P5D!=|Z)QW?=Z|XvH?gSJI;$RbiX%N>-p7Mef_a+gt%y)K=H5t*X^=9IR zNm+-)Gjg7KL$M$Z7Mn5YnXg=s@&?aQa5R#Zj!6l<@ug5@>dO!joYI@j=;Tl=h=V-{ zd9|LnBzryXqhV4rN{`Vzm32Z(z}MxX`=Y4~^n6P>}w3E$+VnG~Ctot75tsmsSjR~iS(H+v+ zcO5g{4e>_iyHGQ!-YmmKUkJs5IG9-8k*^dCSU>8TT1I_{Bc(Mh>q-vPkE1fRRc=Ie zh+X^+6-z+eKVtpgtPM5`G_RgF3+W*B^PQ4d60F%Q%nHWKyK2TmWuWuCrlE>IhAZ>H>G`4%BR%7b+bm;&oQUUO zKpf1hh!Q&)FAL^)Q(lzLQ;MO(ZSx^>sp64-drI=tGJ8*A=qd}u{bSZYt23}#p!L&v zwYJk}2SfDlw^EPNlkInykyO3;sd6k_sbN#i;L{^QlLL(^h=ZvWX`ktjubg*AdXKp^ zJ8C5P4*9IJsgPar5Cckq*YzI6YaT%y%z;mOp~5DnlXZQ2ay#m^Vi*gTUdUgHK;mG3 zV(ZI%^7b>-TM!3}#aVyYz^tv%)Ynt7K=r=v(V=LQ5XOB%zAKouRTD?Vh`Fv zkuwU}&&KOGu&(?-yiXs*!PF`(XxA{!a$*u(48e{2R7t4thV)I z;8Rx|`|$5QDPvJmH35C>Ds-gD=W>(anhs!qwM*kHkD0nRH{%t^!ZC0C5=Z$9pigVX|X zkXpZ2ECF2)omZ>5A?esiHQC3yU%a$GBS^h9+4j`b%bOzh7P7-N+V@z{KMTac)Cx`y zPp$BtmT10;ALDIwu>9=kaQDS^)t{41g)-sc$c|7fh=Vz>o^O+v>M%@P6OAUDu>Urd z_vFF@D_+B2Lc&ywvM`Kwg?4M9_y~o=-ebUIg6$xBxOBHlym#y@ z7O~g{aWJ(KQsT4qg!X>ENLTTi%?~Hh*p%jZAXa9xtsb@CmL-o^Y=AhJTAxR?NNhe< z`X0(}EWW>bf24jVXOA~fVVvu}fA#z!mnWnah=bJnyHS}Ze#R3Zh5TH zv|~dj)>}G0O<@zL7@*p&v>axT2mM$e4yIPzbtd$3D=QNMtut)5NAYgF?^M$|bj+*{ zneRoES4JW3AOUeO2QGG?oH1b*&YgLqrKqYbp$AosG*fs9E1BpY3F~g*5JSBMaj@7+ za&NpI@VX#7d7}Ra9~JyLefRquQoxHMk<#_}@Gb#FF%rbV)Vk9vth820ryu5cz}b@= zdiC}5>LdK=lO$SVXT7}LniQzFAP%NhVoL3qfp)Fo_>LBpG}$(ZL)y5Pun%tzI$FQ& z(XSv`NG%Wtsr7rs643s}d9_T#Lo3j>FF${;n9cHb?nTOnC$rxY@vuI5Eyc|9Z)gic zhY#XlYDF?!SNQdIccb$A9=6P4-Eo{ zgQ=DK;31>kQRrTY;PtyGxFg#QDxOsd*?c$76yxOI|)#9>U z>@3?zx$=CYYnYA78CAW!kSgj^y0n40ux68PKca9B;$UjA2i!k#zt}aJ&fv^K5FXc_ zDrCWgq^yT?bkNST;8!aOjVg$PIq>7Y=$6?$)3sZ5t>1_&2UUiMmIt!UEPj&M^|6K- z;dnr?APyG$W;1O5;Q4FUv3f!U4-u0-N4?!=Y%Ys;D1MOzsFAQYK(QbWrdFF}+=E;9 zefyb%E^|;T2PS@%9aG$p-*N0^yCnF@8hZ(f1#vL7e*4Li)#k_K8lQE@3ya5M&REtu z_b1}RLPHMM-BTIszS8~J`ggHpQHG3av+-K9N|^e|YrVljBp>7O(mZ!$@3kCCPZO*c z|Jqtc$ug5^=PImWmy2b03!w?pBMgov8oh;L?ZQLJ$!xs=E0%zg|645SgB42}63jQX zR9`-tuFTPx z*7oAvyF^6%=S`j~mOQ*)41sQ)m(dE?F|L*Ck@ilQT?Ulf1d zJ*FDel%+S~9&e5@VlJDgDxzBb^P@ahEOB;vBqQJ)L;QgL#u(+h_r!q#=*B-xUf#&z zNco2QB=rA>FSDF0mYB`YzG}K_KCaRtLqmI;cAR9ft6;ohaKz5&hr^NtAIcvd<$sDL zpnu}|d8LiY`j)KL(>>UNMGUMKoDEx}8DF4S z5C@CJN1pR^+hASGr=%|24S6u}gnuHktvReMz8lv9;|;MT6bs@4#jg7Q%a2prauxGO zUNlF$*D3l`ulH+Z2q0OvpX7xETe^wSUY&x9B_QsvpX~p>S@Ju@63_|vyjWYs0Vtvz zM5HEt*MBZoBrC)Z%*K4X{vN$iW;v?w749%}@E{H*R!pbBknfX-iFB*vfyE~MkmrQX zMe+a#ZEZ#G*9VRt#2Gryq02)t8Ox)9gQ?rZ9EvlpnyTh<`=*_~SSP+z+R#ez84 zZ{@R-5k`mfnEhw_nYS9}fgP5R(^8Ms#=G{&xWP3`0YrBVt4!C9TzU5ssZ9)vHXktorY8P%n}zko6$AAlcb*~rXDj+305s{ zI&eknO+vi|aWJ!jZ~GN$-nwR=r|W|MC3EF^lhTU0LiwfmUC%puZq&0q&{YLF$W_qzIC>F%Qek^Lq)C5oZ`Z`>TixgEhNq1+i ze^#_5Ubn^pTDmMnzC>K^!c$j4bH`Q`#q9q`}lCd-8;~ zFOfB#W&~>+=&u5$aW+j6x8{I2m|7pXTIrRC-aHbzr>@ZyF_jp1fv)t%_5w|H4Y}P9yjJ8MIrw`&_KbHBC<-v;fI<9wB zdyrX$Of)_XU#_*I51XTPnq~f7x)z@N5F}dl9O!2p%Q11p(;rK^#miZCRNzF6q=CEPiwTB&cDUB>EF- z&-mMo_**J6k+q-_4YTB~c!4?X;jO(ux1;v6mSZon5u7HNCs}gVK zoARK<5~)x0I!%1Jp-fL)T1d(k#}Tn04yG1L!#X;l`~Vg&Ps9MDc#7A@NGauSNz9W* zvh{n62kD60|3Mt2*6$TdK>Hi#)!I;tvyGYKS983Cu{kD@eDTHA198dMGE->9&#osb zksU*$3gTdD+0fWL2zLB-cA64U+o0w8c~m|!LR$B|GM_{;m7vyD8YmXT!G5fXz{-B= z`rv!2)KqI*e00XYk@D?qX+ke|xVrFBhTPmxdxLIxe(M4h z3*umE`7_m;=X)AHu-fk12(TZME=qfmM@aP3?YXzagTmR)QRu7%;vlvD-X#OCwLtsW z=hX_(43f23k+@e~Ww`e0&YB>Bl)x7+$;ztvgd{r0PA+cf@If3*twzkFe9tz5;Dx0J zR#6wE?Nrg^&0MrcZ4!dkRO6d3mO`;04)$Z^*SljetA7aE*EOciS_@UM&7l&G-V@gu z&}2T6Dd)Zi#ez6kY*hWWetMo4i)b=c=$oVX(pI*&2Z}2m7=;+?d3ZgQtWYe7gT-E! zkrfKMW`)N*|12|4$rUG5;NakCbsj2_Y~*5_VAl&M7R15S`t2u67_@IRSgw*XB@o!X z!n=O+zHWY0tOM(6n@8fUe*Y+eZ~rcqYzW3<&Lvo_PlPE>Tb763YFJ4p9GKtddy9)j z-N2nD_^(a11i6>FEL5My2yb`LarF>i)}t@4dN6ok75OEybW(Ed{(p)kEg1j2S^$)c zD3(YdiNhZBSZCXm9_T55L;Bia_5+Hdkb1HZgXgY~cI7~1nA*;r=l|-||5hzQQi8P@ z%ANdlMUm{rdy4|a{N-?DWg?!=9$M8q%gGTPqTCXuf7+rX68~R+e!x zpC6?Uyvfuwo}XuUkGKgEwPUM03$GbnamKO9HO!OCa<#}4tNifiO{T(|yyVb-I!Y$6 z>-{x!Z#OsgiM-G$Ia^)AuvZoGQnmtB_@6iV9^PcdlGeM}XNF_LHJvmVQ*pEr@L$jLd z;#qTpPJSQ`HnEhH_zQKG-a(qOxEL>FXT)Z;c74=qvNH3kz`a_glGqg}7R13$)wuf- zKT(ZMg`G9pgM2CO-lIfD9t~~D#w)YWyEpGrj@Tv!aj;l-36nB*6Y21l$q`gH8;UXg zrfaeEZJLcNZq8mzCSiz)1;ho4;s3j=aVPPrPfP8o_PZCCvcr)nvP|5BlU@a=Xul2_ zrIny?MNHZt4mPnA`Ui{`(RiS2$ccR?95SCx$kj)9rq~Z`Yw%5C@A5H;!JBQKs3eMk6B3Jgani>UzO+xiy)Ppvn4K zN}54G6bs^DW-UvTs_!>15j9Y~o9PW~?)Qm*;v&1Vs7)R$#Dmk$eF(*ZIG95Yw0y}0?clbm>~KXz#^xK{ zG0aalD!I+De<1cVK^#m0yXDE98RqS@a#xI3v9*?rqnmqIGR8qIJ_kwy0;$X4Nd&LQf!UwETRM+B`gSI^> z2@SGuXvOK{@UR+4y8qIEVnG~Ctp^9Gk+f3TR~zoT+jvy>s}s`jzR_}*(|3^)S)+}( zG7ZIoIG9>8r}51Z)6T*}U)Aj-kY#%PO}2cwxXAl9v&Me3Jx6DP)BEvL^?PAVF~lvG6WsafiY$NF-YCQgg$^IY!PL4!#7(QME>=or z@YAw`xl_pZvZhpJR9EA3i~>?a_PGrx7R13E&~6(dW*o0PGh0mw7Z&*Fy0i22Jr>IG zGe%@7n-9pYhzIRJ94xl2Qs{eb>bTO{W4kWt{5ye;JudhXEfOCTFvA<K6I}ZdF32u$KdhlAPdXU^)8{2TTbgVDSLV;pI989eQH}#D5tDDFDinzG0 z*}S@qq{@$9y)ZjwLDr8G)q-L{94z+hq#j#c zWOc^JRTK30@gh8PVcC`H`imA}IJX3H8N(3weStWbTC=vFD>2(-q~AL!r$4PfvI(+mO(CUO4xX^t{BAM4j{oZY2z2-$ z4yKkx*zvV4%!e#2x*_4$t2A_ zwJWd1$);YH7u)h{tg!J%kqj7fN*~*3-qpS!vIdPRh=ZvW*07p1$Y-MP@kSgGA-a>_ zw_7$d?yNOaGx(MG&vc5XpjZ$Gb09j4EG9ZFZ(mGv)LaEq!r#p`^J=6umBBB^`;#|F zvdExV5C@CJe0|%3aLKrzyGDve@dpdb<$GUWW7ndmxO_K3mZLvKs0HF+YAG^Rs{VT1 zVE+B9ePy#{Y6y|p3RNvKl|4(IK5f?A5TMC^k8&dr2DGR zQs*7!#e9N}4TaE|A+(__AE!dURQGWcvb(<>$$J3*XA#Yc?i?2RhoS#A&q zb3h5x%_(76cjXoyK8XNTQjAimC4)QuzG_REpmakBc^TAO5C@ATb=J`P7!xe`giqOu z$FoW(+1O>OhX<`m0kg$31qBarYcYs}sTJYI)w}3=PcidL>r2#X;R{Oh&bw!9X~}b4 zABj|PE+C$U0&y_4e*4)HgBgd%2`BUZC&Gr(M|IeO#%mU?F`4BAd(dP$n0-|Iav$Cry>HCt*2Wap*DQ=q|JrrEuXgcNyf+~ugGPs`d-`S& zc`o5JQzg#MXXiMAtbNTPSh)n0{NHkkD7;)UCL}wSoY71C#?wcdFhcoL5dnK<>Wyn3 zEy!ke9+Q(!|Ep8~Te(COUM>;t;bO7te)IVOTPW@nGTlR^jJBH=z6-rNQo@n;uD=5R zv_(nLbLEn}1;OkcDU3DpP+atC$Ga`*ly%}-cb|S^d0!J6Emr)eAL)J~DSED4l0(Y- zGuq*bv)IzFbUm#s zbVmQY$#dnB!uA7w3a#DFF-ke(lh4Ow=GT)I0yln*mN?vA)w%zS_0O9;S1z&qxjqoN zjaq)m;RRp22vTv0@aT8V@jR|c^;fzVt=v}ru*v_FOF;j`^AihITANCXjRB*k3hmX%_(q~W-_NPxEwg(( zVs|Zu-vzM}1aYt#Aj;S1N(%Ab1=(!!!U!N6Qsg#*61|Y zB*bikWkOUQ^z#+6Vd9@j*%5bUfjHQF^}6p4cM9JJWw zQM`AfBZ4-yLEK+I+27@ozfb4jB@5_`d)_R)_l6ycp|1*aye=4AxjMbQF?AAm|1J@IK9~fnebhd){(b zlt=6ESG|byvQ0^Df#b+c=!rDqmPZf=GwWDrR+S!W0qb$a%^Wh4eA*lRZMcs!j83xo z8?qL}7^R`H1#vL5tdO+yW13!OXG+`^>t|Fo5x7vBBsa@Pl-Q? zo_%L@ed6X4{T6a++wPh1G%}{RYy|eLZp5A&h=VDxLV8^gd4%Wa<&KT)sQo}IH4C1g z$md?s&wZtt8-zWGd-XvaEcPg>Q(~NDuC&T)&n{S-<-PWCqFSn?jwV(yKIV#VJmQ=M z;$UXgBp29GDM<+u^;hI4)?U#05i00$_D--diR>jqNePAsG`1iPW)`a&8rJf}5Ffu# zvXK6AlzENo%7@{;!TD8PtBYtW;mXi;7Kr=Dtbdkg;FA_;0d-!j=lPcpCcDwpWM`vt zdwtg@#Y8gwlWD%qlTny<|3W83oU}k3Of4REkAlh!P~zN2$9pYtn9cTRq%I7u@xGR57vG$fU!{OXSeALAe67K@A-icU>Hu^bcYg6OLAOLXbI(zZB!!{d_BS}(JMc&87DgQ<1PPAK<-T6D_WL+Nrk<}ZOw zhBXnLa+KA9Yj&sJv*L*RNI@LTfsAU!%iosdNwpmp0%u<1c9SMHDg;} z(*pXjKpZS~WTMdTXzkUQKZ+YcSl&luZyWrb)iUj2#sNpRC%4fMt8EYmQ;RP|w=b5x zvpg?G_F>V`_Gz1_DF+iym~P?mgAWgKzy5@J3*umErTSV1Y=tUNxbtl}YfdpfZZ@Zt zNpil)|1$bZR6@TY1*8^;gVg%HatY|d_q4j;t9)OvLq!@bS(^Bs?vWinHGyxwW9^QKf(UEEazCx@%dP%Map#qusFy5X8E$hsOko79Ir z6@ISj)|+Rha#^xI)`Mv47ov6y;$Uk1P(E%D3AM?u$tWZ$RY0=2JGVeBD}zS=^TI{6 zZm!f2sJ9>vrdDv5BHj0->jF3qd)i5?ji^x-ca@)539>y*e>XlJ$FKvb1>zvJey>~t z+Tb{^)`6o{ht};Sv>%+)uX?n?Z_Djy7(U9ULX~=a>!aXIA>uW~AP%P1EHcuXy*Jql z_n1W+vK`07Bi**TL+MZb z^+k1}?ME5zNB-MTEQo`}-Wwt*kril7U)S>RQ zEnRU#mq+zs;254}d+G8txAH!QJ){P+DgXD*Hvk7yi?zRuzntmQOLpB^LQ)p(o0*?} z3>%;xBsU^I&LLEMBLVdm#KF}1?WarlCW)elNi*Hd&@N(UtG+3_AdY%@d$czuv)I#g zBcm$(-{lfqg6)d z$h8bL8skq}l=M4SE@2G|Q>aOKjD4C#uBXOGsrNv#cQ0L!zc95`)>B9iIJ*YtnSEWCm?m7h=?D)y&Z_+fOEDtIP>RCo-3C` zU3kvb&}^rrt-|dwivRtU_c~9P>vhKIw^lZ9lKndW^x>rcluJPW#PbtN^6q%*#wQ<+ zA<1qripQ%Z75_n$sN>#QGA%!OZfi{6K*D<%hwy`sd?oCM#<)1qOqK7q35wzof{jC-i{> z8dMMmQvj>gERlA#R12pWX;!yMYUkTp+#{Bk1fdlv9}xGWfH+tzItjaVw|V5X zcGBZda}@(h#{tA|i}F6x2Jy%;&`NwqyvhN@!ORk4cwC0ZNiR{Ck?)dPy6}a_u=JJw zPw&1j7~T3f9TnryAb>cSSqd>LLn|UVE|2#ZknNJEFDY=mk#m!)9nkNip{#H~bArqQ zagbTRRW1Qdtmn<@meZLcT~dpgyGf;g{1Trx7(e#t_V$h`p#|<&?CLYb(Ikyo?LP!D2lZoda7YDSp{$#ChbE-ndr8pQeAk$+dc*BM$4E z&4MB?;$Eg^=L_ViJ2W7S;xXutC3nsrWk6HgL&%h@w&;shbT3!`=9w?(1 zDK?R>TeXQ)+{5zoFe^fBB&!lB9Mxx5WP%PK#KG0Vjo4tC?^9SLUFR%K3<=eEz&=W0 zj6dZsGW~@mf3ypVl>~4w2OjMGK)TiJpnO=i{Ymm|$jr4`>B1W^+(=BK(QbWrk3kN9qN~_j~Y!V z%MO*WwN_C3tDiL=lfCFATOYTo6wZKRK^#o2{+T{(*L&~oG}ig@<(d1Z{>;%!)roC> zOLsH&OO`V$C!`jLgVg%HatUamd|s{hZZ$JpgR2v9Ttb-dK6T}we&M%sZRwr7B!rP_ zU5gqA9X^PIsa2^kGw!28f2|{e#X|71?yppms2>LY_r7M_f0tz1A$<>u1#vJ3s=kJl zwOEX_^eT^9K8;~RZ8gA_6>U%u({sws>iqU;4vGbFu-N^LkuGMGjm5_#LHRU+gG@`b zC-@!~(%ac7`d5C>C>h0eet?evjb*@CUZ%O*{X9o8@c3;ympfiEd78MKy& zK>%?uwR|LrBzDNfM_+k=SH$zYKxl53^nF)kO1SlP6`p0$pbVrIh=bJny>bcYg6OzV>-_C~dyg zKvw-#IzlZF2a9D-APEf`EJOdnVXfeB%)cO}7ol2q1BX{X@6y+UXAK8XZ$TVPtt?f? zg7A43`ss#6ORt+X!it_+^IZ!Rl$rVqF3}cFh)pLD2UF`oJng4WnWl?ahr(JqH&;9ngx)AO^+Uir*N8>@)Z@x?EBAxf|#ev}*PEr^4~O1`>F+ITE_~~F|AhtDy|G}M$L%O*0w#-R7%rkYp?9$FmHe8#{zLMwXRqTH`L(7 zg|Sjn$Xah;E#Ia#%=huG=#0jgv^!&j zi;R(ve-C-vtWA%2k{86mVz(l8@4cp7UEJ)({WO@lZ)AZ%|COgG$(vETyT0$GDWbO^ z4yM+HAB#19x32NI@7_|Z$*uQUm?JXuyt9ONVzREaQosBK8UzprQ_C`ugH!I~$=>}B z)!tOr9!7Lyr^_eb!}Unz_ux_ zKlO}bn!`86_Rbixwu0t7#LNZaU~1_;^mdR}=?s@uAU|}qjKNe>Ikvftc4d#AW+_+F zDH~CQ0&y@0s1uE3!+WFfJxiL1SvscssOypDM^M2_kLyQXnIqT9ypK2z#CVyx$ zn^G%Kv44|^Cu1l>{1HGLOs(I3x&&7-wUZTDDQk@{uWmuX<}@Q@x3*?vzY*;QZI<^6 zP4>UbCHf(|QkSo~-l%WllYFBOHiGK4QN$A|psK}t5B zE0;8vP$#AwHRAHSyC@K#AiwuD_e7@<@nX->!gA@Vbv6F;CeM{iv<_%T2B+do>|ak# zdf(ma64ONHpa1ruw&SVKQ{QJeul~HrbLEn;0gIHqs<{EiVgDF{SRUT3{z3!Zrvmy9 zGs&2;?ezcrQIE!RPf667Gf8zOxC5QE%kec|3 zyY1Hi$p>dEXFYltu{2VSz0Auh1;~|#h@1669Bg8FLx1!zawP1j@|^Tc)oY>{+nduk zHy+4lCmnw>H46KHcytxS!DaxNy5J8EPhYHBbq~0{d)%HCqfp>V%3qWApcWqu+4w3W zGy{M*SnTzna$&`#;?LJR`!Bxl$UA&ZG`vM|;61_a-1Yd^o(JLeEf4%XXaV+XvU+g?X(#+w99M?q0W+==`3 zl5x58z9iA*4ZRGeOS8x9=m;8M?2k$(B&ba5z%KOy5hva5(EqR!+p)zsHi14dB z2DZO0m*GZ20R5u?CxU z+pw2tS}Rw+7h|V()V5laD9xh=84I{SV-eO|x1`2yk+9>l>0b$95B!V|P4(+h6v*f>ty zB)Ff-2)(qNVkH=z(mA?E5qCa=I9P04)nSI(@@toY%Lj9WSLY51*J2)QlOX@pip8qC zv9X;8DFEVNgYeO+mcn{G(K3yJ`_Y@|?yf7fG-An4??jDW+#4?=re%X-K^&~NDZ300 z@44MV#@S_jBQasHs#SzKJJ;fv$Va%$+y7}98M@8_asQb0&+-g>(gH1@&a3sHJaKg~ zg#!2VQr)DlE-^ae9lV&A0Y%-T&$GVlK37M4DHg=RM)mdymZ+P*$If)SZTrdd?UjJH zdZcviYZ^{J6K7=U>=Cc&263=al_Ip`n)n#~%b561nLw6`;PU04#7UFe8Numg6h`df zz0i*Z;$X4bu@g9CX_0S@v|Wijs!N+0xmNN&gs~HTIly3Org$a`#ez84AUHI+PqX1I zHZu`3Y)_D#T^f?tw(LF@vTpC#8N5o*gt$)|#KC&2sWRsL9h0*`y{_(>Du3u%{1fr- z`?;EdZOVT86`EpXkXj%PQtS81C7^}!d9~hX1}-x`R`9-#xAt47)K$}=SmaYD zyOVyhpezh%R6!hURBr}Pr=+Zuaff)9v0hA5lVvfH=HKumx_81a)vsyV*9XOdIM}Ef z5I*q!X}|4jnYSD#GZe>NiGFd%`I9@Ai&wIOcwRZ8WCr43vGZ>CJ|5aNVnnV)cIKOSLku#arCArjh331wIw8Cm%}aUo5K*Mofqx4mPS{jW}K2byqC~ zv#vB=j&9&yUVDr*i+*8lua9gxb}S#UhzD`7Q7z@M9}{G`Ldr(ogW6NF_A_U|it=QG z+%7C;$X2-4!rl0$MUSYE0u>;qt?Y7jV@8S&rrphUu(YNO0R_|^n*Cq zAjHHOF7B;`5unl;HRrfe*M2em@g{`nL;8aJ#P zo^GWzIgqGF#b%63chOxoBZ;N8g46!}ty;{XKiaqwTEMKtXhml(s zHGkp~H0&xYYh~6_o@nAihY#Xlql#ji-td#s`-i`jf)Rbnsc9>R&Lz~QvnvFd6#9Th}tTqrNeJJ1qX$jW(s!c2nxMyVj+MYAlF@#mZ#oC*kYp zwN6^^cHHDot(IFa`zFD6wy90QllT*RX%*@%h=UEn_p@uUjg?%_-Wkfpbd`mYORwe4KYzZw~h2>^HQ}9LQ?xNvB_DEi+^u_qHBiwBI2I~;$Wj% z?XSFGz!+>i>p(Yz8>U1o`$GkusY*Z&PwN_H*$R^~p=bctt z_ZBV?rBnCklyKen-W$Sgz_|=GLd17E>u-&g4Oh zc5fsThGIb+Y!HMvq&KQb>_VmMc_U9hiW^c2ZZ~p|KTN_J?CE5R@|}lbK^&~N;(?cs z*Pf71)Q`CJ7Zbi3zTBJH#H(!m^qr(!rQS}EFQgWTgVg%HatUZB`@C8)iXnE{A+&n$ z?9J9lgz2x_YZxc-pqb0o+w0aa*==eSYap4K# zV6okMx~vO@W`h{^En{&ZrWs@mGB$IWt>OeoKU6cG2Tf;iY9WPX+7${7sVONp#A3N9xyK!gcwq*JgZ1{epDsZzZavfzZ^9w!JJ$M=wK9Cb>d?L# zTI8dl@mgoOwk6}=<&p@>8d>V+7m4`ko{djt)1-BLIx0J~FHtS96#jHTM)&V;R9l)c zZVz1J_!2ztYixISW27;aVx08IvzK9;w#ReAZA8rLdkw8+0waC8qlH0R-m+a%3KI-AO1PvZ> zLhYTIU7^dx?0I?Ky0U-Tq9l^QUvJg>f4*HJY|?!BorXTI>Y?$#C92sygUkZd_?@;~ z=Up-DW6@!{KW|bK-el54?^XWRYK*z>b=T+D$?i>rwe!lSwsz`0dCES&8CC!1O*+Aw zyjjQ3Ym}(r-1wcwz$3WZ%+i|5-fnwNT1TlL?{kvj!Jjwj3vW_b^vi@t@P=_kv~v3L zhsnl)%a5#E#w`;`W<}(qUZbP@d6Q}ICVRwuI-mG;lS#;ZM0>?}D55A#@0lz_msaqK z#Z^m5K>g2~tb{lD$g|jZglO{4WDT9b+0%phUiUH*w2>wDJ(4c}im|5yNKg)j6?VI3DxQN}8SQL6% z;nmr`Jr#G~G5ChkdHJhLn~_= zV&@UW!ORj1wDoovLmwwtpp?bFvn);Z6PZC$K>O_q`n6P9E-P%Pw;&E?RzX?TH+zE- zlh*x+fK^dGiE2^#Joj#l*WLysS-taph@D3e2O9*G*N;_ZZ_2a>7&y!0^OY7TRpZxc zY&jRz(h~BiISx`mW`Q`!tiPshuvwsq^}Jb2j+e!-Utx{Tur@hM&P0DHmL7Qcics8( zaQl7&r-5fQG^ijBX4WKk&JDr-cUSf0!gnjhI1Nn1uE!tCJ``ooRue*+OhKFqK^$yQ zBfU9i*e)Ja#~RMuEi>9S7;#HZ3~tPM&uyn7UhjE^c;^6!gPB!jMp@F8!;aaUnmw`Y z#WCTbd-WY#)BMsKtD|_jN9_F2Ab>cSS$D!O#yZ{?X8DL^PfFzQOqdJ%kx=xwCtZdXj)wE?!W)zTa;eA z9(oKt`S3%GZ&Ek?zc+e;IG9?)w9V>IpOIYLc|hb|HhME@b{M-qgF>}nhxG~0g7izo zekO>6sU=MkQPPXOhFESIG9=mWI;IM?MmF=T)xLzwUb+x+6a5pitNq) z^5ir$HefrnXYWTy*1;SE%b<-CVa<^KZMCW8$L$M$ZHmat%6yID+ z*^VeIi&a|`daOQee|JOKthInVF zw;&ES2u~A2XHJMpoL9?kd)~|naYlBeqmaw|QFtnicPVtstOrsH#6fEPeOV1w3v}Uo zUac*REAsxbUdtF^Qg6MiiC0dCbjMz(ezfkGWOWUm(oBR7AH>1b(zct_8JjZ6pt*8< zp;2p-%mrzVf^=eShvDKgxncR#ASf2Z!A2E3sR-A}ip;vP`c`9oaq$z`#7JN2k z2p6KK3sObAga^dI)S}UU#w1+YR#<}S88@+)t@-ka8MUosySc=`_&ZA*&LgO|AP%Nh zW?y`sstKll?sG3Yb`6;qYjN&XjIQPZSWk$9ZCBqQZc6}hut9J)XUiAxV|Q86L3co1 zYbN$D>e3VzDgTLjILI{d;x6K|I3NyE>+kIouv(xEj`L~_ecT^PvY{JfQI^KWG{0L& zR+qQ+wZN~nF|tCO`um0gG^!vDrWUc;;bZcT==}%vBp)P&H&vwH$~r#w6_c0EiM)YK zmn;Ouf;iZy;#)>N(D5HJQ2AnP_M;1(qqe@V+dM-y%KglHaNX}J0~8D5U}~XkD2eyc z=k84}eX5AxY$m6`_OdJ8oxLZMl8}qsKWYxef;gC30l00{60>_1KP$8+XSuZI%V^|| zRq_vZqhpBudb`&RpjZ$G8-#?3F0@(Q4h4-8)ti)KlcZVN26jc!RF{uBBYEn0-k(Be zEf5E(_4hUzSS`>__Ib4~zr2KPERpDOxsSyN+nCV$-Z*yI)Ae%hU`I9i@uj?N=W*>aMYlwvOcFP6sxN z6>Xg>uDk5pFFi5tW<;<1rc8P`PNCj{IG9@B1;m;hTi4gW1ef1_xAt7!IS|+WKF@*p zr3$*)k0+lHyILR)rq-A%Qtap_ZohryuFupB7DNRc1=ma_-$!({DEIo^-$R_bK^$xl ze*5VX%7iBy&m>UvXFEDVahabjYXtSZP{FOg{NgRi{SA(G+kcl!O5OO=y`Kdg8Zu%L z9oAsahKj{d7d#3O^iuP@#u(0i{I6Zt1;?Yz7jL;sq7|@SbW}bmcR&}Z)ktP~fBb_* zQsw(+T3ERRl>Favi732WQi(%Ww?&UWx8OqLVc)vD(otDTCf7fU^X^$0WuMxv{=Yi) zzdcz+2l$eqm70m4gv{>~`TbLEoy|Btk@46ABe|2CksgrtPh z-EGj_-6$De&_lyy#9Q@uV=)% z*Bo<>-;7TMWX-h~&`1XH<-01#j`hw z6qnRp6fNq)XLak&qrb3so{kRB)}kTA??G>HL&(^Llv0&5Hu=A}1oTftURX%SUT=>R zoE}a}wo~GY4NN%*wvCDYlv7wc)hBVNeHZ~9szDrVVd=ZO<=2Ytsx0utr~Yv+m1k#5 zFx$4(Wg=oZJu^99nrf&lh=UzdP5f?rqjfZ{e;D>z7GusZRbM%cFiOFa*^GdaY0`|k z94ZUqU<-@j?e1#B9wD??iO}PLi=}ILVn{Z zo{%3eN1FOE=30Ey`*i-*JL(=v_T2&{O3gy zle-z;DkJym;;m=0pC*=-WKVaYO>GeO*H8A}Z~&p!u8_S zu7G?jKA)M7+y%gXXyg7cGgJSIcI+Zwu2?L z__WA58At&T2Qv$0)3@PZhnu{@BWvEwK6B`Xf7b8!mLeev&neN^agr*avLFs-){XMz zH4lN$JG$YSpY_vUR9N40GMe+anU%@e8)}28t>wvKL>Ty7<)YSh=)%S5h{dgwQvQ_b z2LX^-APzF?kKz*0!isFx+6H5i(W*+{V4t$Qg^m(a*1FZ}pweIHIr@_Vq%ZvtUjYDd zFthALXUBzam%5G)74OtN@E`)<-Xh>b@O2Qw?rbmiTT3SPJCdNW=3gJs^$C2;4v?!CqQ z?PI@_bgEGV^%lgz%u0%()0jASu`SZBsv0*_Q_``8XIb)Qy22Nn$MG$t87@#+5C=2s z-glc7xzS`20J_csaetfj_vsn2B9EPWA78_6LGg9lw%yMixGNm_9@uiF~U*-DsHXNU~s|lm!<<&=4*@lm0&tKu#`%N6jRE8Qv zHu%B;afk(RFtzjt6Vbj6*?6OpWaQA(6DDIM3CU$R(LN72x%ut;5D#KM6U4#Pis_I? z=PDe0#Fe$a94Wk) `LQcP}l^Lg?PjRei!MMy0W2UCld#%Qqv zBk5bUmygPnt~dlaeG}1!)B*-GKyilIZ zq3Ntf`Tg?48C_`jAP%Nhygtg3=x%*L{w(I#Z05i2Ppb(znR`mxOvs(P`FX4tv5f=b zU}}YK(hTJ{-}g$Y_GCoAyhdPi_-Y>y{Zph&T8Digx9A?!TM!3R>uPmx%7Gg3uxMob zb#I<{oF(jST8($V22LwdYh&*59YSS6989eQou#65=ID`3(%_?@8-t>D>+f%D-rGQD z34E=9Pt=Wg_XvoCsl^%QT1h@@*0EoDF~G&f$G6ABdmB?;!9=V(W&4-t6U0{lKpdpj zpT#Ah3nFB-v_GGAF&7eUmXr#VaMBXQbU#qgYm@BU>A6kb%2|bm1Q+#q z_(Aa9Ql05rERl}Xi%(w5ynaGJ!sgNbh$YhxDhuLZY870-29#R_TOn zO${97b~SG6HEEY_t0Q7l5C>E1$zri{i+I`aL-~EcQ?YjK| zC%%t*mr=%@waAhrH1E&*NmBCAF3;wp~nsQ=okvnA+J z=SXr?w0mr}MFeJ$VuQv3ES7l zu;O28LEQ8N;$Ujoya->K`NeTm7f$jtfXQQ>gHEOWt;#X+Io$-ZVH0n}#Q}(esbzvi zpF;cc?(3H3M<`4|ihcIPs!mnwwfBBU?#hl+K1A$if;gC3wRB4p<{D)*L72DP55~rBwOs_Is~`@h)(wRVsb9;p@1yNc zs!K*>`VBa5Dlm-h=Zy1>C;x*weuGCCfCZ>HWZci7{p{1YRn?h zzYNCljkMg!gvx?Am|DS}416lXwN?^uvK5|?&#}M3oOl&*Yj^<9QSRWS;fqzMEQo`t zWg@0_8lJ|cLn0(1>*ZmYZ_o5I`rGKin5b7@oKh916{Hr3gVg%7xCFG5jjYx=3Ek{t zoaigOwsoR!&yAV6<&+R!NUaoo=_`eG@r6F(aZ3;fQ;VQ8oAlFx@U-0XpFKpRqP`hd zj-Im-Yg|8Fr{dFLdZhr(Du{!rb$f7y9gp093U}^Gmb!>xR670Rf`tb8z*i(DUlWI7fb0vxP{{Q=m&zBcNc2^ zwd*>$snNV+$bl=Fx#jN3L&kb5o5_#rg7#AEtD)DXcf~PbaS5pMKXFMQJT7_pBK6}) zYivN`xUyiB*w?a6-*p7*>6T%@i@=?Sr3^&cwE99Vr?iEejra8yja7kbL7XU z+TO@}t6_z2-&nrNr+MRF)9`-^Xk-JC;*uo#E4!jMEnlq7qfzG{CzX@^>cMcT)){s{ ztIc-V{QULoO(Ml5lPe|fi@E(s0)Cc{eZK3n5|=4iL2xC}pr=n(xa@Xt%-Nenic9XT zNH;K&f0vL{4pG6JDCiXJdMvNOZx3_sIsdf zu+H|yp?LaNdS=O8Me)0;M`s@;Qe5)=+*%fn28(jG1zr~?|KRap-5buJ>o4>ZscjDn z>VEm0y-B3F#Ii@uGIryhWF2e4GyV$>G$;`n<701mX2L3u9Pd5T7&~K=|BFjN|3u`4 z#dOEbKc&k_xk9A+VP_4aaB>R`GinaqzSy#mceL)PIdt#?aj=C&P^7b=!@1|4L1L!E zjK?}J0nOV`9Z z-`dyXEX75rEQo_GERDC>R@ygYyg$z$%jit`zyPGvw6=%6>$SNh=Z*F zEtid{PYm8ZH@)-V>VwZ!)@+Z4a|p>(B4iY$wsCT65ihm@aj>#Z;?AFOOv0O4SOYNF zY9{ba&~k{+eYxEKo_qcX*y^@13*T)muBFPr>kVa> ztcB?53m~&V9AwrX#U-GH71=Ch4F1CFZ}eR6-gz<6NU0)Uhtf>#Q@L(0ul#%UrTGqG za}316%-Xyhf%2uBiCW=fpUUX<5`#iMhbiUPYoWuaUb#Y=wz-f3AP#1h5Vez)`pnv| zYuF}q!wLS;c5%&Y(%f=AcYuH1U@w5j~+P+G$a;cGLM zPw0GZYb(WL38o?b2OtinK;l?iDjB}ZwG~en{F)>!zvFyq(kr|hlqDw)S=cKMDNt`g z9IWiB;h|-PG~agj>rbZ^6ROr)%0(n@3JznHm@Aws)N~@ybry*G+pND&&%kjB=mZK` zttqDNv;>~nUNi#jK6b_GEYfwwP>W!}U?m&h81MJ4CD8Ce984`^vtJVEsh1N6q&R=f znor`rOZ6ngYF=FuUy6ETpy<~Jl?8DywJum*Yn~kWvfEGowX@7m4@=Mx3w8m7R15IQewr`YWUk+C0$U#5jOJ~ z-|HEpc|GhQ&vRO}xcsKj2T}{fL2CV3Tmm{#Mplaj<>rY{L0RlE_LHxsQLVSr_`^N2 zSv`Y^Lb3~8FpLpzbOCWNwQ^UsUyPv@%hsg%p#P9nxnFeafe$^C)0pVqo^w-yA>!#0 z5C>E1uoN%)#O9T3lIAehOqkM5lalMaWaN|>d8yW%R>_Y0a;5C>ChV(R;O za?Zk}QlrJ0qRMR&-_Kzb&i(}<hIos4_hkfzV#s_fCKgV zR0~R8*nj0@O*?@1C;T`<#qF!#H z_fT072XkQ4^t*Z3A&4DU!a1R)1N989g& z{?KKg2J5Ir_5Q@E^2-bd(bk^NnBGh0NDtAx!db9}%7QqU1Br~+vs+5a%2FEX4&s#- zW9RpVtKS^-U&&_f+E3E>tqGL{aj>#;#X`&sG6YMZMHnK>TYh|~)5nKC{P9Wtg{;#T z6}GQKYJoUNtv`!PKpPy$YWe0ydr*AP4B9i)xYw>e9!)7K?H7t!GB2j38T0(7F5*@) z5C>ChE0#Mn(}wtY8xK}$`?qd<$BZz1{l2!x{`=iKXp`b>(5!+um|8(K(wv?lbnm`q z;pz$5@xK$dxqot_%PVR0t@ZchkWs`E1L9z6m0}C?ud$Zm5Dw?zwAZJ8-=V~oyi~^i z=~!5!EWU8+CDdCG2XnwW_2IR5<0w_(ALCM|j?q+ZMJ23QJu8+q3CGD~jsNG~3=jt^ z>u=q`t|^7aG1i+RGn%l{bVBYJq1nAg>}ke^K~+JN3aJI+AhrH1E&=UiBdhhqM|}SU zu@PC*@MSVq@uH5_`W-S`T>3{d<@uzI_6K^WgV;5`bji@=fG3n%~c|v7D9IWggKV3pZcvUv^{veqinQ}SSy5FF~ zQF>R#RkVme;_s1wY=8Y!Ca#r8v{h>t}v=X*;7Lye`A<<6k1XKxZIE_tE# zg*;P4B$K-}Ry{qW>j}$E#q~>v&)?`B&*(T$c78s4lSpyNCRKzTb5M?m(tP9F3s)Xa zK2&;(rHHAemi^;Jj=jlD*x8#zic7kd>pE`vtuP3oUnlJRyb)EhTmD0KaV*i?j$+5R zmZI?NO(Ml5xOB%_V#XAO)vxhCnONfyIg~ubh%LuEl6Y+<3i%S0@Dx_-fl$j}2rBu~dVJNR9xYBSaQ zpKY1xBkhRw3dF${maintM&s92OT%JSdpJAJbGZE8xJ;xrIP?1%r7NFu1Y*kp#KC%7 zHo6>EYS*8athY6!6CLNb{tU+`EyaLOK}}BExIKpgngkFBi}zOtN3BQ5W8z5dQwk+} zg{bt>yt2aW7BMnWJb0fgG(UhgwL#opKiPl3UGhh93FwH6Y?fDev4zBt00vjYHTUUF z-AcMgy~%hP#hE5mIV$0W0o~B(K^)Ait>w|#==)=FQ#lcRk#WSVHCujVgf<~@7P z&hggAEvPJrgZ)-l(u?;)zEqp$HBGb1X5q^4^Db4~`4}iwj3bwxO|(l3l?8DyvlInQ zzQn(2l=_yjIP<1=VZ-?*=_iv<-!T2R4l#&CX%SB>fH+uhe=cCXe|m&hRNkfafK{(- z%IZ`4iRW=>1s4ex!_tS@8mPA*4rW$S)oGCTolAuKOBkN@kI1ouWaooOQN4x>BBmpa zB%ba-W`Q`!tUroNKnp9fSp|V|*G$42@+7xn+B!Pzh7B=A5_xxx*j=!Kq^0C^5qCa= zIG9bqj^E)o{t1dE(coRp11!{~dRD`kvbE^<=fNiJ6bO)7|k{Z>W>S(Vtdg&AJu z1{wWSMNKa(9NC`V`xLKldHT0{K0Q$UcLxPdmF z!L?+QJn2cKJ5(0L!Fp?9V$uiXm^!$H~3O~WdAFPjsuP;N4Z4)s0)b4*g+i3 zEZ)R}#20-?Cb(Uhx}fYEw@^#QY!*rdDli>WTwl*FuTZ_VMK( zseQj11e0n`H7u4HhU^*5jSl~YG97c5Fm zm)AQH`(N^LWyEQCpYHROOzi^}rYf znu{;3q>9}ot$b+sAP%NhDUFvREz@IVric`r25t}Do|OZI6xOiowU1xYF2s-^(pV4& z`>`5|0zUK(eZz|&lSAnazp+d3i!di<6Yqghou-WIU?pOO0dX+3$ctroWMk`cPp;&~ zoZcmNjl1)TrdUUdCgJke;x24Y#7jaz9IUr#$rQEPJ6b0V7{>G(-MV`5O78g~ajg=+ zJ0=>rV&1%l<`%@k)Y`vB;_H-v|8n?h!0Lzj-qKonUezV`DNIh^I?H984|EX1xtE zwb@L{#@2EcN#^?jA80Ojz9jwY@g3F5ro_G(XjVZS?8h3?*X*_^Os2e^ESUSbY(Bq* z&g`acJ3WPhlem_7;BqNc7R15SI-C5v4#Yuf{aIWBy6{C-D|1HP&c~{0F(PHY z`V|$<+fP)E@+BsQbo~#tpT4#C#D|6t;$UiBo(W7hSn)EJ-pIm3L(QF|ce#_8 zQHXRoNWQ1Lw~V`7OLl3s+{GbzxX@r6YvXPWe?cw_;%XMe!FuaO`OabfYg>?5X$5J5 z2n*kP_O44S;lwk=1T81%$$09}+=4imS_-5$NM2M|f5l}TKxcdOGkX<#*y25Io#`XA zh;6?|Di@)n7Knq?`m?wMw84R_R$qeY#-6n8!%DFmV+y6g!$oqeyC!3v@(dJ(Ru5#e z`k~>2IG9=kb5ja_?&92K;~oM1mp#Wh`@XDMUbtDG@!a^_z_&~L#~GV za$y+oPTuY6R&AjRINsmCZhM^nfW7JZNNyO^TM!3Rt9vQsVvd-l*;oyUl~dt_O^C{~ zUXrgEYNF21+R$j&5w}!=I9P98{d`!4j7c6x6|IOcEY+1b%@t@qP5*k`+VlI1tXNgV zlLa6Srq-%PxKDBXoRq%J=`4!Fc*v=;hsj~2xHq<~@ASNLo;aiyh=bJnv$zDblZ~tv zu8#b!v3dEV*#6>t`&6xD+2cf&p@{IyI}b17-n>Mi5+*(FI#2zKn552pCci>4%tKG{fcy;-?Ns+$)kpcF?^<@v}@sN zUri>J=d$ylZD4r6Ih@e02sv+b2I8G8N3x*sU3*X+E0r zGctm4tn(f2otp7JlC~x4r8iC2u71HFY*Y*pJm5J5SEU1F>L%IM}h; zgjV!>zmPJ+ra>{mQq%JD>F(@KtR)m->Q(RediSp*uHZl%Y-Je_GgtEecyBI`=Qv)} zv69g#ewfj-P|wWq19?SErN9ShWdU)pvQOM4!@K%MvmZQ{X&nr~>a;0|*QX+?uYOV; z6eF*GBNQqN;$SOFeny;l9MOoi2e;UJY0nyY#go2S_f6Z>BHQnc_MYq=(5^O!`)h9h z`|*;$j_2U5N6--$*{t|s3gOTFBP%*BP6>TZR2IsExJs7C#^M%VwwiTv{q8`c2XQd7 z2BSn|WuJ}P_9~=U$v8LHE~uArNv9PUh~Br(E*yJ{*m?wUFtg+zxCnjx7<}+4oagOK zA0dm^x+b>D9sLfJ4ki&wuDd8uZ$TVP0SnLif!1M}AEfu*@OBjRXUixpL@RRzK2NP* z@lozjLhQkVI9S;dt>PftYm#(qoGtiD1s&IB8T6!rQW{MhLmAUQs$cbndJE!UW;tEj zR_qSCqh2F@IG*Ky3-?f={^K?YY5!G6-M4plwUeRKVG#GXS%0tFV6{LiE3#VSmM^j~S5;K%Z6xI8*%I`pS9kf; zpt2wi=0I=BKJ{pwTn^y#K$9tL7oK^#o2 zQD;tFrB%^9X@Z-&I0KUeShbG^B3X8Au7&RMRt93bLA?cWFb7V)JndG%jkijBu$gY= zD&{_(C__LxeWhU?`^)qFiDXBpEQo`ZP5qoT$!WVgHe`^+^-$+Xow_?eF8?>V!bG)3 z%Eb$sh&NAwIG9>Zy0p8Jl0nDipUZYdpHzugxLsIJQ1}1byQ_P>dhdz@q!x&S)cVhP zI9M&vnKH6k?0h$@*5poA#(reta~2*^Q{5=)ZxAaLpE1i`YGb2phh`PT!PJTv!dlsx z(sH4B61`RUds4fpv7!hiaNmz4odq?L_cJ2m19335(8SN>BwwUKw#QdCA8- zD=WxXvu@mCusQw^aZUu{U=Bz=%08@$@Fdowm|)2mXp~YcQK=>=Sq=}FyIFz}{v`sM z1P})+EBsS3;|gi@Sk?~Y^HNh5$BsA4yKm0L)q6FvrR$a zA=gNew5X@G53@1vx0ff_NAYo|-5#NY)BhOB0^Tn{I)r>TG(KGpWlt^ zDG6G~z321J>8kK0FIJIjTqP|)+*S|bU}^<15!wuIGy&Li@UBFF&{2XmlV z$JG0ouE~}2!HefqZ*yahDjj{lmK@BalZ|U4Df(R-DhuLZWs53S)NWwA`Ddu}@*O7J zTddCVv2MC$i>XVg-HWLw+6GqlsMp)kko82VU_Yr0&?@6 zchK-b984|Q@PaI~90^Aw-_avH-&Tz;0<^@;Z&-sC*tl_}7O1D7vLFto7X41(#|xP6 z<;%MxLg{jZVk@Q}-!?PNHFlZaPg3&0+l9)4IG6*{;S5gUQZniO&(kr*H(u1!Qlg}N zG*DD9UrfRoV5(_?%7QppSzkjfx|^mq3|}6L&Hpqe7AgIW8xix&)T;NTci+Qj4-vQ0 zfH;_1uTyi9Aj|2y7;?PU%b;c=1^p@$m}`r#qZ=HGo%)XgVg%Z{s~wu&<+Q( zTB!0ZdG(59y%T~Sco)5B-8W5Q)MDyHw>zRMFF);9MH~k}984|7pFJi*r#5<*k0kT$QA$Yb!+#pF7)==5)t9)LxE?A6#d=^wE!HX)_?ZNz-oc^vXRw#@ndU9h-E*??U%%}y1=8LujhA` z7%i$~Hm44GIFA|;_vU~&m|8?5lEo>k>|=hl9qU#N%wtc&-3=|x z8#(A!COLAFXKxZI*}%QWd~zM-8w>gWJCW)M4Xs_bGk78<&nc! zo}qH3x1uvV%HmXbtddlTQ}@r<zQndg6AV^vKTphYZ_qid`iKNsF>s$5| zzJvR74?3xr7AKe+^AT6^AP)9jth%v02E+0#tIbcY^?C*#I3=Q{9d_#_9?REL!sVWm zb3w@li2Lg&`|o!~{wUc19dVJ(Vz;}-^SXGfJZL0SmRDq-hj%A-)6|UasS#--{lxcEf5E(^=HWj=nM*3Ed%oli5johor~YAeJL+k+Pjxv zu5lu##c_O7qOvE15|I~yIG9>@cg|@&#yApk_o986vvN40MfafWv9$Knq?FCut~1Ao zw;w#jcF56s0W+`%Fj%76cz?mJxnoBxjK8HzZ{*UW zao#PJ{@Ko?HS}8!DhuLZYVo@1jAVQ8UA!rM_xMxmrexDe+RPe-+QaB6B<5NZ z%K0Sai+}g@JZh!d5&iBr5%C=~BOdR}@Vd`3yg9x`EHnuq4yM*E;euH6 zObNDFM#j1hYZZk6cJVcyPu2J17ihj@`oB?v)BgAs%!U7E+#KF{BRk2}MeTiirtn~Si3cF6ptaYR6`~@O` z`FF8oAzLTF_=4Sk|MaEUAEQo`tCHV4L>Bpa}W=@~p`UX21OhmI=rd2;hQCAV2 zdv-r&&J0ou#6fEPS+W7T@Rm|8w_|oesYP9uz zdxIpD?>gG%t4?L0vLFtoRndhva1;%(cdFJGy>uvwf-#G0PS!ft0m1R zeL~l~h@Bld+lCq4sgdvASSM%s%&L&RfcmC>(I7N@5C>ChaZWbLz~80AhGccwH8d$0 z7025~isdN=0r&krjj-N4s4R$ssg?H0T+Bj#$6m6PV&{%khx2K18Q+JzE1RAZPFpor zDToVC5C>C>2~T_kXRB&vkzz1!lhcnj`ozLl$V@~2QvdCDqKyL%P;WsTOs#I*TbFrQ z$=lIX`blUlj9Wikay7C%DZsd6$&o6qzNrh91#vL7UI}Im8$WcfvBb}gj}1`?B8+XB zQP_(U_z`l|+Fu~y7j)DDagbVnmTZ9bvXRx|QC_dvXFGR4zZg$0%y?j0ESS)NTj7x> zH!prZHm;KrG<*;TQ|sNGyqOc~ij^ODc|g!&fN zr9GkX7DTdH#hJVMt3atzs-#mKoq@UYo4n5)>qwuaM%3?X? zzdH4QduAgLDcP9b7)nrect#P?6&6JFJ#EKHV!Zo`EV>c<71dc!^ijUEH;I&N(7o9% z%JNHljxu|oP{l#qw5?ILS6)r>{Eq%x#v}R~=Ce16lx(O;apB!QS1G)~Ke<0E_Fj!V zkBtozO&udo(4IT~ICAjpO(G>5s0Z5dv#!`Tw3_dR4r`q3w4D4B1+2#jvuO#U?5q|?G{@_wn2+BrdgGkENu*>WPgNDK-c>M9 z{9S@i5wqLyY%Up>vifz8dL5tAC-z5;XKxZI*}$rspx9J8#^%&`>Zr|VGs+Or9@QT$ zWnA^ChuNUhm*b30{x8`8{S}dy03oMh=VT2t0{iTWCV_fOtG{ke!r;-z7cdj3I_If= zYz`f(K^$xekXbDH@UfdAls@lmO|(wnYG1mQx*Zzt7G)(zngr@_D^wQ5!Ips6xpyWS z$}i0aTsNTixYs}(xbjdGT~6mP_`U(<6-s-=o%J9Nwgen*m94h)9QLX!l2F9#`1-oh zzi9rYuq^nqbJ^e#dj}QNTM!o_CHe1ULt2TMX#-tVo`_WQiIcYO=Cq&nEuJ@DR6A(( zMk9GHA-1zX9PI3ZwB}Wy^5ry6;_Uo^0b-&Np1VxH*aLg2)i3?-EN@~+gOUvp_t)J1 z_md!hlx%>GxX5O4Vd;uSv1y|Bti}w5_i8y>hoai=-hC_@9B$bb(&2`<`x(T+%vvOp zT&z3b>gI@k;lmlHtfOlsBP7~-%r3|6`f25j0OI5V#KFv}a$I%~%|iJWQ=@i$r#Yt9 z>C#sUZPRoe+fBTlUeN`_>v=&O%q({-5^wCw6Bm|8`TVYC_3M|IQnFLyMr}6w&k@VG z3nJb=0peh0wJERdWDfKdk&B#kqF@L#qfol27r8um|DZMT^J&UsK1f&)2Q%w}s7bcD zCSzpX;@(pWd58G6DJG`ccfPX|Ll0N254nPR*U$nsB?Ou zrWpR1TI%fR4nh6x4*hf z+_bCETjG{)|9sky*m(tUFtrA}c==|qXu6f3UvdlM7scPDb7zjD@)P{Vi;IDZ(`O0& zSRf9jR#=|60@M0avcMymbv5HDv<``4p-8Qr1I;+YtvA;Re4(-+4yIPj)l?k1Yuy@o zn`YkJPWC&2yaN|I``_NdyXM6+&G}vdDhuLZYSld~?%Z{H*-GNnr%0= z{5{Z`1Pvd=!PMd$wp8jERgyOE=?}lgpLj%epv)2fh?b zBW|VyaWJ)7RS(Fezsd3#QJ8c~y&ag5b*T9r|9EQuolI2%OJ?K*q!x&S)cUhz19Ya0 ztk$JH!}lr83#IougZbPmoCdv{XYNsxT5=Sw#id=gr7MMI6~w{R8k%A7rYsZLz_nV9 zvhWXi8oyxXLOIi!62SA)B6FyX1}Y2UU}}Z#$jdXAL>k#;$-Uk7$SQNOrZ>XB=R_1S zlgt_F!Gib(D~N-swZ7Wpg?DH24)fGUiTQ?rIxhOTJRd@R20xoS>pu7bh#eOY2UF{< z#~fujSv0j928LIQ`Zp4efz4~57wUo5i*SRGo8MURuumh+y zRzVz0EqeFgls(H&%)LVhOjU=yRHppPR$AO&%oh89plzJqeFK#RaWJ)Trdzz9&lFb~ z-d#EoxZNCBV@5^!o<)H0&Z21f%zDdP2ili>RKnx&X%iOu3qBuoA+Uqqdt+%Hoh}c6|OJmm!7X$^-^IQZmxvX0&$R9f0k^3b~upL3i^e93k~hD)T0Q6 z-i8QO1`{D;$1B_A#KF{ZeELqa=CPB7NXpbf9^P1h+((bYo0-pW zTbJ;}zA^F|LuEl6Osyc!IKidy%!fBMB&n!EJl>m)F^KGrQ{FOI7!U}#l8JcH8Hj_a zrMqNO^8#I-QMxIcc!lE~GZ9PEZ8Or>?-v*@o{XS-Aht9>984|aNZKTsH^yGK1do(m z$yaPJ*#hXJ!k&C3IFdE=%*>{X>}zbO>aLqFde zAh$A)|RzxGG(Y)m!%R!^C0C0)GqvvXAxKUzXwvRcEJ?@8?Z=~nX; zEZG26{wLXJf+rh#AFbstcqzx=%k#4v$l-LVUO{D#(K14*;W>{c#z=ARU!D5DB^ymh z$wnVa#hxWibnVndU5#rLFWFP%>-{9y2P$!TGdHx;u+RRn^CqNZV^~B@ci1EQ^wfb$ ztb&SKuc2c>*AAbDZb`eJc&vw5`Rt=aN;W*vg{Q6UNS-#J*w8qe-<0SUe11Qe1E0{B z>*hL%J%!~fxGd^a;kKXC9iC8?LsD$0a<#qv%WMbQnm>x z*}$0}N{Z!VdUL^NJTlpzDysWKmS(-ekwuli|FRkbe(l*uiIi-ZN&MV6@N%_E#5pGJ z=VfkxxwfU0HLGS-%p5fRHO|K1>`fvi8yclcghrT+A|r052FX>_r4|7r!^YO~D$nAE z{VgL(oX*(f|B?;RUlDl;xSy?ICQsVli>vNjYA5mOaX4j0{;+=v&zIce9XIh~#21l3 z9Bc_VCs*2ggBUX*p<3o3q>0+{LUf_x@FSbg?)5^k*$uh4&_NZ%!Il6Pn-q7Mr&D#m z<9AP)!}igO)I4MD_-t4o7ypzTtj9-eX@WS|67aR{%e=;c-MJ65wWy|+orYcfrq27) zNeSN=x-Sr?Q*J}O1#z$?z}U2P{7Xl;7=@SN<}19zYfloz6*UW1Pp*?SOnZd-BepU? z9Bgj)Uhe6Z$9`c;zVC5*@J2XAS&vgJS@koUTDSSK-``P;$UX|^LeW<(!+yoq=TI*5ar zm6NyTQbzVI=QxOm$bOY?mZ0R(zQDszE=@&RQgiNMQ_$RkIN00<;e_=2&yVVPYrQif zknwE&_$g`)^HNNX!o-ENtjw@T=#mJ;{cV=)f7fkrvH@CIk=1hU)wgrZNL3|bKS2HL z5MeE4Qh4P~XOE4i_6^$|q9(*0IUo);eDc-fojR+IV>@w~decnnqQR!}$3+XaT4@^R z2k0-{M{JLQIG9>1>{j~=8)MZK)9DVXO5OPLT_$Wk6=i$%xN|oHSzA6rKNg6Csdd=C z>d0Ho-$bGI;~bMd_l2VY4#Ufqx3iNBul#k}Zqa+FEQo`tHD19<{U+r4H*%Mmy}mD$ zm(CFm{fL*s(kQpEJczl_^B5`%;$U+d+HNt#kbm8WCg~{^i;ZQOx)|~CT?rn|FJ|2~ zY1f55Lu!FINUc9hHb7@k$ZAD!L|pkqTx4^+Sm9uxbq)3R(V#+d`?>Y;V$7ZTF1}o7 z_#h58{C#QShN~7>&Qh;zR5a$bou|kw!k(ihPY);R-TD-SodA^uaWJ(W4z@3~rLB6t zL6eZ6!lo)Pv`S&R<3hR{vi&>DO`=E@DhuLZYT1d0V-|59Fc1y4&p-6J<6_Y%z*FAd zaFMa}4V6`rI^v`N#KF{}rYobfE9Sye&p@~M;gsBGCn<>1$jb5qU)atsG=4|`>Me+a z&21g!>DaZGi>P?K?^Fp5uQZn)Z!mi;+X{J-byeqF=~;)=0&$R9f0k^3&XkeWQcqef zwT`mzyBFcOVLldmGT3CXlBc)F<2jR!p(XUs+o(VsZ20!lPQlez#-CX-rH1ul&|4(V z3AGP!P%@h9NxV!#vqIcU3gTdD<>IXimxRZwKm6gzIIm8yR{E%lf$X`?`@GK0>lMNG z&p|&Hh=ZwRZ;RU4m28k&ci#35&6D`B$8>*?u>#;>DIKQ`Jz1g9VlrWVfU8}s2} zqx4c}!mW}!ffIA3?2&|dR<864^c`Mus~u2pK^$yu3k60+T0RV=YV+rnuXHy%k9Kd} ze{%7FUghZLL-~OH4M;5z2dVXE$p+|(2wAPEYEIQC+!JaOpVG);vUNgp^uEC(t@BT8 znp~^P9R7K@0>r_FuQ_(*?HkdTwclRf4#OyZ>f7oXK3o-3hw9TJ{Tm(qp$9apAP%P1 zTXSti%v~XOim9K~HOB>S`?Ow9jEfYJoUNtv^dP zKv%xVYH8Kwjc)zgN{?+u9u1iA>fH>Ij=WZIdwRxXs zG7?_ux)Dh<6>qDk@biADsiT8!k3Cp7+jz@168Z1tS4N*{*_m9W*Hh)V;E%ABuUW`iTd3$Ea!d z2qdJ)p|T(jrWV)hia0YRo93pw_}9og5=AcwX`M>g-#wouy`Y`O=#O}q8N|WVQWmJp z-??mZ{uf3uspb#DLR9WYYt|+Fgd4cS?L%3LRZwq1984|jA$C^zT)l)zd7KFpI~#=# zr^4N6NhNAc37LT*98MCbEQo{6t&=^$&5D~}u`fnwbpArXN2)w65y!jKv z!G?dwM(U`1pg;#-TlouV@v{wutRE-MYr37b?E4Q`N(2xm#vl%+mSC>8pU1kTr2hlr z%ev}$FEzBSZNtoBqp`l~EGE@+@Tl%k0!|;l-yM2N{}d=$0;Y_YlcOZv%Hr zyPA=7v%$xt1V8g&Hx#=H`)bbG&%GHW&b?s_|4ue67Df!M_I`$!Nf$?Ftvs8$(L{Hx zGVp5qKrp8wN@gxpaMcq+5?#ZsS?Z+svbt_*X(C+QViZCp{?VgJ?pUn z%DIjB^A)Tn!W;Pu>!teKA`hNF?ioAiV?Y5t37&adoQi9yBKw$t@SDM6_Nf&)=L^b8M_vRcunB4=+h z4&G!o$@AdJpWpi(wi`2}NGe!!N!-y%E*yE47vZILoid)By~$K~lgl$`xkExCDYr$= zJ&u-UPa&|HaekFA^j@JiS?4yk$NJfutc5p;Wy-L|NbY^k+iAA2>vpkeOmEalecX^% zeARO^gYz7;XKeC+$p+}Jh`a>k(zlV9xcFgzyLfsb|Nhpu@y=fts8YBHL}ZP*%tJpn zLMtGMgDnAJY~&BjV$dE$%Ou3p7uFOw#|$QR&$zO2lA65bVm2s%%7Qr95|HunC7X+9 z#}}{HzNR_K30Zm_--BrPRdlD@)d=G|tiD2JK^$xepor!z)m)!^ZXLqvCU&iHaj*RZ z?{aTKt6}=a)?5W77Xw&8M^xn^d1kw|$kP?_@v z)LRe-D@!{gL7ioQ&}_NAg&21xrgoDxK8bWG>y%`NbtBg%4vWH#jYsq0xgl*yz6y`J^|b zJhH1Oywjm?+4L|ca_TwTj(J?zu&ZNEi8kWtT@VK|D=SPETh@KEahP_O8}sY(`lLub zkn?T=Yv;v=b_?HIDBU^W^lSV6r7aj>#`uM1XOc1lff z_qmAXE>P$*7^v;L5jC7f&@(uyL_bu4&Rap;-)71Fcijf71zK5=)#A6YGu2}lGhCPG znmJ@2f356TUa7Oj$BOrAyTC$`To4*Qh=UEEdr;*_e5w2)7Xhb2&WGb(^b?Plb~Kk| zOvTEp%|&!)pt2wirWXB@xGvh)|3}(chE>t+eHf%uK&2!EY3W8%5Rj0RkWMK@PzjX= zK{}-yq#H>=LK;Ls5T(1r0Hk@J=bSV9Jn!re%j@|4zW%djt$WW}vwpK@#M#}l+jo4r zDLbC^;Rrg}gnxO9W@WNy~0 z@^0Bkc|xrPaj;GxM*Ugd72{1%mTtj!i$<)9N?K@npZWst7aZUCZaz&!U;^S`WnJT2 zSUMzgA9xYs34g1PY#=J?ry^pJW-F;VymeT3{syELh=bJndwm903$%hlR%?K9;k~N2 zfx}m7+9dbPH%~w2IVgEL-eI=LKVaP&?g)i?6~w_#|KW6%c)^kBHa&HRCrt#w=5eCB za!!LdrF;SY{hxHZ=XYbp)b)6MC1IgRFJ~Y`{0#ua!OBYJ`Sf(MRQRMQ2ykQ24ERU1iu&BZ zxB0M&rB!rLo5l~R1>zvJ{$7WJ)dH=Qk<~H_a!@KQ#phqp^i*e%e?fn-1GP|6x>BqM zzqiq4;5{bPs~`?``i}kGPFd~OOY&D=dNT-G;w8KN*r+v|3VHZ2yt9yE3vn?7aWJ)7 zA3SoCx@zMh!1u|k(q6gg=4rIh5q;nI?f46glZ4y-P-{UPOs!ZI9E;hoh;Q`do05+$ z6gjo!+llfRr1xvKe&wy{+-UfZe!R41w@E{8-h64=?|62G$e<- zd*NAHmHUOMih*=d`?aZk(VgHQ&jK&cu~S053gTdDnarxFJztzfAH2UTXnqznq*X>Q z!tTgEHNR!R;8^k62PzBVU}~|{M@eo9eQb(apWh$!_1}Ya^V;%ar9QJvRzrusaDZ2erkZtm)C^2T)lM2P=F1h#2)2pVWhv z!HOSg82nvh%6%`_9_wd=)Bk-zn0ddF!q_`|O_v*asO zBWD$lX-E-jfjF326>lWF88GC_f05Yi3RNyX)XuHTPqzq8!5L(J#~`?Y*vtcQFtzkf zw&T4+`w1f!9$_@QO>ZnKU(=kQsiG*>_d3qWR`f zlh>_2!Jj;IvY#nKK$hmkagsM*I)G2vLVw%I|ya&zqU3Vs5!bj?x zP_KeG*y)pVHYXev)F~(O9ZDX(`bmu&O=D}8T-7y9AT-K2m$eU-1#vL7#_{7jC7I-H zLbSYP+OHqzgss!;awpTDn~XMy!>0V)gPU}_N#I9nBwM&Xm%wb6vTGDvY>xsqIG zuyH$+b~nape-iPK8i<2+0$@OD!GLxdT*RQZhwC=l10*W6z>?rKqe0UjDSu-p{Lt)BWj0>^-M zUBrusOCu{kl%;u5hKJAT@YG+M2;FYJE|nknNfVK5tcR!CG=@7_lgB3;#_TRl3a4KV zt;2F)puMy-IiGXc>7U7l@E~0x%Kh50g-^NVC69krE3dt}!MI2nE$Zk+vv93{=O6o{ z#+%C~{b|Dw4u94sNL5gLs3uJKZuk{1&gYRMLjlDnUs$pMs{G$%Lkga3$dcW+9I*^b znXG=)zfMo5(P=_K&3KvE%zFBc1dCYxpKnb|Atf8<5`3OZF?~cM$8p{zOm|Z~KNLHc z%PrqA9JLlRxIFvkAG((M`_B3Qg()M7R%<$y+zqVsQ%v}C#ZX5g7+xHsWIBx2{!MBGjyNot?e_26uRt;*LvR({_{g3 zB^wI8Qu6ZgKAxdJq?Xhp7iNYCtK9gv^22b>t|>m%*8cOzND3+0Sa6Q(x4^u|eWEba zVK~4i(cqkb^W}#?4Vj7zy1EWG`k$ZW-!t?7>)6Fxecxl8yc_k+yY9+=YS^%Uw3;%l z_Kq@8KGgl~wYs!FKjhz6?EiO2exV&*TP^)Sji8}3E!E(CPOBS@7p^<9^xdsr`BcmD z;tvn`pJW5{Q$(Hu6kMVOze?66Wv7o1lvHDQyi5!DPRaPtp%H6XrQd5H4!Tx@IM~(C z_7eu~3(K++U6M3{ALC_|&Y8!Dwhy^(3i*{5>_w{}9&`e6uqj}hIX!r=od-)(O(klX zHe(q5(t?A-D%K~z6G&z&(rqvl_E_nFQ$qjFs3X+3AP$z9 zd)_Cy$af;X*5$rc?fI@o?ovi*UvUxY-mi`Nb8J_ZD4}En#Qim7|N73xZzUU`D=xBG z(yBL8DJ>+XtMnR^*t)2F;|6_B8Gor}r1KM$Tw(}VfzBSp!OYrsS@KTxl1&t)x4RDXh=%?}m;FI%iMK^mcBXC;V`lTMo4r#KFu`lh?m1-1Rh`WyCPUtK95s zj=h6-smshep*2BSLowziXx$3p{%zLHf6d$Abt`CQMOKS#$XLop(A;dzOn_v=A(+*h zD|vE?X#TlM!1F;r#$v>OjX@ktt#&L)A_}2OX%VsQ@?U~aq*i!sCGNz0PB~PVv8pQk z@&W2q5C>C>?OwhaTW|Bb$(J?vHVJP~P|a$!6i8Rw3ZiwzH^0q8EHFVFOs(%LU;1yk z>NzYXFQz`OGIY5W9%3$HA%k`1M*Z48^(A6Q6U4#P`dCd!?WKd~u8c?i$O4bfggeA( zi34jt?n(b*N`2F30jLu|989f9Kg^_RET>S?Sq7598rL-auo~1&8E^j~!Iue*+k%Xc zS|AQm>+kg$SS`>B3R$fxHSFW)W5w9|ZguASeuu+yQE_C9)89#r@ysrBgr&qnrw`&_ zYUvLzmgHgCc@T&_y2L}mZS=HbqpqvO2Ui9A^~jaIEksrT;$UhulE>L=eVd;s`b9eA zWH~+(Ew@ZE!g1vM;A_G)sSU<+$P~}nlfc*Gu5Y&fleR9!PNTBQ*JBbPt}|0b~0vtD$Ay)`Gb1H zc+Inttz9>ny8*E#0&y_4wsBp|%-&rUdz(%c^p<^J-=;xi_TKDSC$0@%U;7i8N~pCU z4yINaQT6l1qWk_^6S;?1chpAWe^}I_nA8hCAMc+c9jAT?Z~rAhkdoq}J~x8=yNPWVN0@WMui-=xk?nSo$=MhJgEXHS?{oEAm7J*7>z8 z60}xOuYx$3S|q#4)-gnNFKKA!J`l%SN-mb;lZ*#ii-?S7`$&wPNAxO)gQ*oCo?I_* z@8IfOZq$)}iW=R;m2>_2xxetwE*khgS>q0YS_|S}YSCn0l;*x2Nu?;z-{^L$nB%gV!YY$xWfMAP%Nhk+SU=vjlhU?#$Cj54GeewwR3&cTc{a&&Gy7NU= zYeTN3Kf+{|C_5uKWX6AoZ6GF4y7+Fvn51h%Ti7lw;(1~a2U80@ce~cj;7aD<>H%lY zP7Yn!M8Zo40qu5aPrsL?_q<)8!2)qGwU(%2i}xBC<7%183f`q^PG2v6%xSYIv>>7G ziFs%8umCCx;$Uh`=cCO>WKpRQr#_^rU2XTnFfVGn7Wje4zs`CpY=HS2R2Ia+)Y2mU ztCU)7K<+a)@5`q-0%GsO6h1Bh<0REmz!TH zFh5p)jU9xzO$Ko=wNfQ6SaKX|YpnEQg|C&4ALdQ-tt|0JtEAxzCmGSzApRc<;vltt zFWCU?Wh1M#=UZK~t59z)Vn81GCBv8Gc5mLqw|8MHw^~^3<~{i7pk4)WFtt9*Nv8g= zx%~Njis4Kn{`EtD8a_rg&k6Chy=^|96kjcl*KrdnE$1icmQYy`2UAP^-S^gmZ9#u)6W5B@?8#{bKSJ{a$nwz9X>hpDZ?>^Q zWkDQFt*S;M72AEv#_{rg9utG~!GnWW{-4OT>9<=Cy5nc~ZX%M6Bl>aoFX^4EfCcU+w=EAN;W*-psz&+IIn%@ zzQ;eiuk&`UvZO>mD_kwM{UX_|Ek^l2KO|DJK~Q<(bL+T=>QRRCK^#$Zx}>8P-9gmy zX9dNZJ6rY1%YS}Iq-5jUOR55w6F2-StFe!d9X@UOVZUL(|MqV4Tb4<+N?=&|pC1w_ z*~lt)@LC^#t z-!UgKhFe-A8N>uR`kZ8(m>Kkn|NM|h$;O*jwu_f9icXJ?5cXkS5tr^RuWmNQc%SCk z^$UHD##QUj4~dj)WZuBWxPRi_GvkRjV-opQ^&~@Rqt~gKGB2b2o(kV}=RZ8;f07N* zPZ4Q^0&e19zpUw60cCm8py6mk@tk z0dc-J&i(T{8x;m$UJ>n|SC=fDR8g++vm3?PZfSL{U!%L} zR9=y)eKU3BsrXn7ce3qiv+j*&OCD>7)eHZ9G7rST%wigi^CRaLpoGAP#0$h19$USzrAbtCHZ%bxIw9%jPCe%uOYXcBdvwIB{=7FjL(^%D!V zO(_Feu>t<;CT1lcQdN^$S)QX7gsBKC(s9L%h-J1?J7deG!sT>tBfdmbXW>e^uu zT-1Fe>5th{PAUWs)x#gIG9>R{iZ@R!Ga6e zl^ogz7?IQ)zIoZEjTtfZ>w3>qvTG540|0R_wVnr^C0!Lqj_n25HJHCu~uM3ES)cU<-1GG{`R%;`9{>JTJ@89UX zKGU=Ay4%C19wSvKLwpiIw}LyLk$~980&y_4#@*yydx}U!(l5AtDT5I&&2s@2EWoK29pFiX?$qys_=sb3&g?H zDr>Yk(x z`eB3Qb_#UW0&$R9zn5%)?ud}paN_Fr6Zu=qnBmq=zd~s}dn4P|!TBmZ}ZO_|K&czjF#=KA_@{FqbHqM!_9|)BNaWJ)_vNp$FlPgp0DKs0D znI*as%cG?TEs>g^-rHTW9D6|ml?8DywI0^dh>6ctHJEpuHuZgv7}j4pjK915die?Y z3a+BTeZ*!th=Zy1w4hZ<`d&I$w(5mAvn9r(ra`Hsx-~k%p{)%oC0scU=&A+cAhmuk z*#PZjBdb;aLznbJV8&4C2HuOVd&P}>1#t_6xHKjkZf`{6axojBUIlS5wF+)Ch4gHb7XKD{o$q{D3E;WWe<`EY^ zp4+vpar@}F+qdg|p|T(jrWQBmME;v@8`&d%#@;H19a&cI0UERL;{CWnlEB8-j}U7u z5C>DM(>BV0UBh#b&5weYM%6cVO5yA64W)07K5`R^wcp2ELL?izvB5^0#}yZD+jCGn zdwPyh8i%}Op7(wzhS5-Jnoh{gek!e2X|TWP5*MWiB^$bX&!!jNQV1WBJ&DS`^W-89dMG*1 zhy(6+uwV^Nd&-}F0{X8nP#`565mOi*7{qkV!J~%Kp3CV%7?r1@)<3Cj3V#Mo9iwZM z{CUSBB^zkz1~0X%Z;(HI#iXb)AvqeS+g0{sfJia+M%HK5(UCv@8hkTSvQbjNb5TH_ z&P(0*dqJJ0p1wFn%404+(f00hB!_yO1%LjUuV$oVW9IC-@SFRuSh>h;<5Bqqw9qPV zTzRRUPvn`>e8b~8X4#*2EK;&Tob_;1k$LqJ=c-aw#hg{MHtuy=44Go~nZX~662luQ ze|X6MBpaZgBJvb4;<@s&BOuI%{ZYfS+o$;Ed-pU5ZloHC~o`af7a!%4c^1Ag$|F7_o=XC8 zu+~Z@`7Fu^n0rL9QHTdo_uvr?Hiz`C9P8u{=uLZXvc89s4G{O&kp1gB8^4uofUda6 zW<7c(NyOMBauX}W@>m6nxzc_^xU)X2m7N`JtEs2P-WoCs#KFu;XCf9dYbF$n8`#h3 z8#!+8$-xlCzrHduA5A>X(!7UwG7rST%u1kI2{cT@$huD+t4vPzI;Pw;SekI%c-U)7 zbs@R#?H<%x5CBZl^lH`#Q)H-ud(b5E) zCs0Hb<>|Oq?p(7_k6ALF3N%`uC3 zG`_dQd}?ONRI8MarsRu6Zfzw>eY8>~=yFHOE!lm@toEt~nAGs8>N8 zOs%jH>j$5)>w^~AMmnW$H&pESD({mit1Ht~);sjN3VZk%5`q|Ic(PqsRiO7wSF(z0Iigf)haPC!XEnE zhe~}5cPL>A8DQ zSr7*sta;y_T1NWOgQNnNE%v0l#yAxpJPjZ2g`d7MiT|mI;{}xkaWJ(mDFY4@*93D+i zi3ji2Sfh=Vt3QI&0&$R9zn5%)?ud}p!s~M6d6lE{y%C5|Q97?FOClhUzYUbS|#%TCU$(y?2K3qxf=9Bi;~?XQhJOE60R%6d`!&K=oO z%XOEV1Vd#H%>rKYR=?>v50wRRFtz+w$C!im)L#ziETY}%DZ~}G_Y$WoHg%k%h!x37$LLF<8B6#@Eb+{)1$%4kx2M#T~~Rr2Oo86{m%uYx$3TKA8KG>5+l_lDaW z{?HdQrTCVYz_6Kn6m=9^iaYf3`!A?0h=ZxsSFc%R=hQhD)5>szZKc5WC*#8*>YiIW zSTZ8K35N>3P+1TM8>~4t(kOw(U&Tbp40I+p&*f}xRhO!g+;P#$8()~4=@5d-f;gC3 zETNBaJD6zJ1Nq%72c2e?Q3`_epSICiS6bvRU)*#-{4Wl~!CH%zSA+NCt+Q9<*yH^E zZk}YGA@$2nl8p0rieep{%JWnqwLl!C*6$@7pdAimwc={f;#D}!#%c_uO{3;f^EHT* zQMvVt#T7BLBW$iVMM1p^;$Uhe7iCIaW(iGm{l)1w9o7FroSE~w%#t*VIJrv`C)`;V?ZDv{UaqtZ9dB8&Rxk|qfXae6m|DGdbkVOT1G4tn=478jGc45Su_ zgVg%HWCOI9jjUFo@G`49yOEH*y=7crzlQ&7ec_Q{*VuW7`1Ot4{Z7Q?1;oMB+IMcf z`kqOH!QcxMFa6K2ukqh|ZLD8m;A)xEEQb47TPYXs|#WY_KFg9DTZeM4H2QQ+1u_T2JJ&A=Aw(+kgEjY{-`< z`bOC*R2Ia+)S|ehAnr$Jcb;wjRqAUM7yJOx-QAG7*XS1%LKi#5`Z5s7hVgKo@jdOr zh*j*fd3@!sUo?H<;=j$+lhhL*E*Rb*4f*FbeF?Tov<=DiGqNySQL|1nW(;(?#XCw= zG>mu0zJD$yq5jAI=+V(RBl_Z@gZTw?+N#tQA^&jOPYW6=R#(5YYMMM}Z-ylspvwPE zHc;eXFWO;iFGn52IMFZOvN^YtP)Z)MaUAjfiP>G(_d@*9b>iRu(Ww6|WEkWj7o@T=Nl{-OWN4HIP83q)TKwp;IUIc!_V(7Fe*zjs0)9x83$LS=0!LB;mFgbq z&VAe8uz1VyGRKik-+qAJDCpatzZwcf4}M4oU8llEcgc0Wto29iuhg+Ml@rLB-J04D z-^%pdNtEvR^Nw|fAF@*Q&FOkblMDUFu3KtT>e^8^8ozxvk-S0IBhGTOR+;9{4;ckN zWWZT&I=|NG)r*#6EESETM&+%$SMy0z=BV5GWj`_|9mRZVge2C{0K^$xfkgFvx4(DCm z!J8-21~b(dtR6yT zK^&~L1T1ScA2W!^JY_eMbB#BjW1CIQUR|f~(sJrJ?oWZv9>l@SqVz9+D^?iM zC+MlJB@pZMl%ok#NMxxG_5P!Cq%va4h?{s22OF!gjXDLLi<}QSAF)?2_g-GPL2%Y6 z>!*hD(4vT~-Yv!$YAuL^ja4*N330jC;2AEj1O1JVpURvm^B>=!MG(=PkR7kH>fVCN zf;d=f&E}O}wn6B#(rNKf8U*?w({+g35w8SZj|muK1+$<{1(i z=VuJ4+M5$3DE_P@LTPenB6iQbFOB#cJBa(YSvUVRZ-dnW&8*03<<32@;%8sVk>8sk z7|dGVkGnq=YVRmPuII%qF=}U60-ZjHgQ*qu{Au)&IhwU}3-jfi1@cw3Snt7>9EXd% z#H*{fwz&?WvLFsNSPK_=FFl=HTUdG3%9?gFvHokkBjfoQQuj!ea~-=YRS%%DAPzQI z!q*lbhU)vZq4Qxx78t2p2+~=+K(7y(xtLaKIwr!1*vtcQu-5Wdnq(ATaPjj%S6q?8 z{&^?Ar>)6({Vvg!IO=B#pGS6})`B=#YaRKN-<6Og>0Dq57g(0;V9tk!C~&3IUT_!siWwo0y2cHY|OP-)7zMirk6eHpCL8cwFr*$UIj3_h@byfD$b0Uy+Isour94>tVu>i-Zh#~z;~Fn-|VR}NnepRU1YY~^2>GR@4*2b zP+1TMYpp!0rn*)Ie`Oa|a+-f7TsdEZ4d{k_4hg)tQKgc zjI0*%rORFe3F)&tv2j%igaPMk9Fo@0(>GM@=vgAP&}A zUM%WdwNr95KdHwAf-%}2BhsI85<{Gd2O=(ogy4uGmRcYV*4p7Q^XV9wj9;i0vq}~A zUZP#5v*W@k%ul=y9IJa6FOWcLfjCI5zwfueYJu*Ekk#5xdXM#*G~pe!{H>Z*?uRz*uX)Sa?(I>gGsYo!lS|#J=QaMFHFl$?yAKB2e%M|ZEX&b62efK1^V(bWApvXb|U_l&A zE#|qx{wK+N1em0b_U~z8CA#8qaY93GEWVi?(P2QhQG^By#K8tDt8%N~lNpPnWr9Ip z{Oe8~17i^uck53+&cn9x0m@p$qi-M%HdvYxZU)*qUc^{+g4TCmnr=?gN(c2`*=QWO z6%seb_7<^>2XV00E)ID3iV~vVWmdOn42f^Lh;RJ!^JgdPtGBl=yVKuXLOh`Z;$W>E zS|F6-cEb=#Bsr(TE=%FSRefKv#YvCuS;K6LVQ2F+q!x&S)cSk>1gsWlhXYwHbxE?M zXz9MSZ znjQs<29zx4<>r&fxvQzVX{jajBrlqC3?xHkK^&~L9IIZB&br)M?2c$h4=Oo+*miN} zk@K?xgs+ zzLpD`_^29)WP_jV>uh~({iRTWJGquIj~gqWPXzok*(mU({P=4Rz18s{@qOPanHhp)0fK*Q$&xQeu<_!d8L*))qUhDGL@CQCeBdpt zx_+9;q0B{JuLDaqK$ZWSY)HYAjehEetT!Tl#O(BC6|PV29@yIVsVNEwaZM1kS9634xtY>ecQQ`$hHuZ`^Ovp+v1QnEqsJS>N8yTboDSo;#0`FWCr zg45_nWL|YPgT~BZbC;6;@R0vWHb6f`7iFCHkB=#rwhD-oA`(AY2f zbRdCPVSzZ90ugzp&p+>1)a8AP!a*tAuE# z^eLYpU-0lRq1~{>_wu2}na-jMdp!h66FjAR(7F}G{oAaY|C+bK>sHXrimaBI#5of= zuB3z8ORnqJr$bJM*u;eJ-E10<+-*GiNhj`%&+%uNceYT&O3d6Y1s7w(Ukt%;5{J zP+1TMQ_EyYW9#zd*HBKEXAe|xUub;`=+rhGqH_AOcMB)x$qeHC`XCPGfczw8^YvFw zA!R>tn;b$Fj1}G<;hnb{T%gtJFUjfb?t@wj;$UT?@fomW-NQn(KohFG)#UDb301Iwq&oK3zY?NFbA%@P*8v1Sj}Aeg4*|OMAlt<9?OqM%9B@AHUtYB zR$USEA&7&Ooi=Q9cW-&Z)rNA3Jxq5AcdITS%FB^GZD>7_G)mjE08$IYL2CV8vH@Bt zBdf)3QHQ@{ENH^X8Yf^seCev@FAv@xBi?V6ib`n|0nCUebU_?UEzXgWw=2i_timU1 z6c4$mw~~DZ4*Qw-3Ilmc+4WD+5u5rT4yM-Jd^c6lNpk&u!jpmc6ZFW%4iZ&T)cAb8 zER(94*z6w20T2gM>*v}G*-je@W_9_2wVSNgn}UJW=kjCd3CFck+AY4SnNV2}2Xo*^ zy+^Zgi?aU0`XHOL>%uKH$KmYjiDQZ3l4zR&m#h(g0|0TbvfTVnT6wA|^x^`hw;$Yo zJghi6ae-q)SYvM@Gs&VLbOKTf#6fEPUa|qYBSKb-rk=q+;&4~u+d37uDVh}-=h@yx z&T7)or?R__gM7x5P_KeGm|8h_$NORxgl>-yr{%C)$1ASx7kVE)<-N`okg4mBb|)Ju z3*umEDf1kO$2`#)d2E&&)fAf5z3)rqFE_zC)hSTiL+G&S43!0OFtw_g2;RD0F>E5a zcc=fd`ZZ^3Zf)WDYi`7C_yL>qsf5E&Sr7+vfVNcojbY>gN?UKmmFscqqC4eG3*>f5^A@yndYs1%co|}bzgoLboS`(H$vU|cM>z6kXj%P zQtS7U4bYu0vRZ9{I{D=>D#8ne+PtD8ZwY@j&y{_95FK;zUdV&Ok$MT}^g$d;$Uh~Yig3J-_j{7XFU+Z*^0(9 zI|=UZwC4Cmz*OWMrFm`^YAuL^sU`Zt+&}Er){uRYhFZG~-cv)J9FMY^Q_93c|7DL~ zi+NC45C?PM^lE&dL|%-Khu4@&h96A_f?CWpt2wiRyJj@wRct2 zLhnk2p50j4Bl|%$_6z&^nW;?@4Dmw80(T*`Kpdpj?1IoA~|`KvoS zc={r0KyPa7xtpxsRj7$|x;E8mh~0M(2U9Dt^!{UE;bYA=dmp*7k5}fcPUslxQRJ}g z8@(zsvDna{UIlS5wK9jRuAbPXM?EP{HKMb5uEk_T+D>sz2ZMf)_lm*t2|833#KF|+ z87Zo`>Gf9_{Q8$rk5YN!_}a&AP!bG)iL(sa2@~E8I^tX<-(;88fh2igxfB+1b68))nhGT?No$|9bzxV__OXb_xWeR89B|Ln+3r&1q5t|Pn4yM+`p5vzSqY}?1 z?au6!hN_l=K+_i2Q1*TTOtjTUYg>r>Sr7+POVEh2%DGOEwnf>lr$1`5Dpvu|N8T8huMiodxBScQGk*Om4p)N)}#7)t&PM2E_PZ8Zk#< z$p)zMf0GR_c(UR4(qQbsy!>-u2o9%Qs@Npe3Zd=WBj$37U#~ASt`REyqf!4`vf+i4 zY*>$^ck9Wz5njJ>e9}huYpvYpa!q%%Q=z7*_zL^T$F)B{BvP^w=`ztjS+RciIhNns z!5Z}oyU`U2cqtE@F8uXQ&+KnD|9lJ0|9F;2$p&X!^0~PJ&6`9QI`I9lp6I-DxRpsJ z?ae+=cQ1RMgZ=fNpCwYVv1UnJ_fGQX^`FK=?^0EAXE+yDTPAP`yCY~Vus`2Z`SRz7 zL`pV12IVQ$6fjZG5n6K$p^3+hyt=Z)cG7)%1IyCM$G55K&ku=|YzQz4DxpnjR&m)x zt1&CQ3UuSki>7tF&s~wnaF!9YD)HxsL`pW6QX==Te26(GL(%7+vQL-pl5tpQ57*r3 zo@8i65h{rN!$bZj*#P|%k*9#nPV8DvQ=d14BjQO+&#+wE*xGXB9yssmV-v=h1vw#J zNC@I!Q@|z}(@S*@c2-WEevDbNs15rzrsWxL%T_T{(uB33C{1W)0dcS?K*znjhMYX~ zjSSvO%8wbQgsZ*ml7k9DPnWZGh3P-+BK~j=;$Txio>S^Py4-6u+v7Lq^F1?I0xUcq z+Q}tnW_)_ryd3l%aZd!|U}ae`4_(6v1#UID$ltmx`XN%%p8nZMtSqgquvd9--fhGe z#6cWvX2E_pV8%Q3EWn_zETe=}hpEY()L%@&j$=1$cn-(ej~_}lK-^z_`**VO_w^jS z{S3O|BAaD><~Zh_^&^Iox56CjE+wnjDr3eWdtx7BXx0ttn{kLo)j=H0EO&yJhod~c z*NV@|)RkEF$<3sGw!-6L!L99zNt>2aHro)RjzY9X^R%`(GZ?kUxYu*N{1)5or)p}_<^{`B{@{5DA z)*~fpciuirj#0k;NDq^HKf+NKyYEAT1>#_8%`x9oE#ssUl6a>63WLt4=vR9>UUp^o zquASljcn>!EKpex2UF`ffT(m|oG2uqv*3=Lvd7vrY+NGO3+XC_&u&LZt7;g(k z4v=oxmhblt7VI=xu80OJKK(*7T}BcxUOQ2KiBTWN3b9WN;$UUBtMua|b?bdwltvQe zI^w#=DnpQ3AP!RN_mU0J3JO`RA<9_Bp0~^^IoPqLGh6NA^1UJ*u5UYtdtVo^VMuo% zZi_)2Os)JZ^mqv!f{=xpTYR*8WizYY48x&MNR0S`UQmu~1tE4`K^#o2avkoX?lXF_ zyYty*iN&u_CG`hdD-+M(0yM>Ef5E(^?S(%Xr+v-Rx@Y9 zEQRRU^jg&P45!cP7KxX*z9PY^w)cYWNf+3s5m6+FgQ?X{k#UhRt?KD#1v7wx&)A4Hk%lsdate>gUk+6d_OLP;J*5=-yCUtCCK8UDU%e(s;dZ zXP5+)1#vJ39P7G-Z9j$49+3{cP+_RJnBI55vxceTS@$KW+EIW4@m?1Y2P><6mL$IZ zN^9E`_2Ve!yd6=9=N)~queyP`*yl8JQCktO3ITC2wdjuN`hR*r<Lf@l5C^ICd&vgqjtE(;!-2r08}VOs*dLayh6uzCZsvym*f+B~ zC!|lmT$E&R9U3eU2UDwlK%@C$QS(JB0#TeXfzA8lZe0(@#<1`LHQxq*WFRSo%7QqU zTGk^dgSV&A^U$m2uf9EF#|L2|lPSh=Vymt(M>yt7orZ^6tg) z&TZyJ4FQQ0YoTqm?WEakos1oFs4R$smCc;GL@sTb$HI8SsyNT!P9=_7Uh3Gc-xa=P3T?ph7;GG2WKsRiO7wSF(z0PS!ft2K{yd4Yj8)9M=2qX9em zkgm%mB}ED$vJ{UQXVTC$?Ge8$5C>DMQ)uL5%!$dJFYeiH9asb4^%0@h$zp(Gt-xh!tuPc;-?N_p? z%24q7$_T!Ql-rhY=QXG-h=ZwBOj7Spw_zDh+vP{9eGNOC^1BMQ;}GkJgTPd}XVb?c zNG%Wtsr7rw252uES*@dU`C9`<*Ip3^H%euRIK8EPG<$ylSN|}Ro@0UEUkLH9eh>#! z%YE^6@mse84e@zaqk~s7J`9*O!j7i4%Y@fFd+(>T7D2rV;$Uh~zr?|K-Qe`)?&4wJ z?Wq+POA2&liMg$&{GYlyni@^dpt2wi<^U6=+2yd;WJ|d|`pHY-Kk7M^xGosx?#Mle zem|c}9?cDv1#z&ly&C21W>}~`S?h8a+UqCyCk}Pk?$Iby@%+3MO~zd5hDbJgUfXNl zu3X^gexfR);h4B(QXFz^&w0hF*eVBOJ@!fLKa&lk{E-;7Q1tk9z2a&>H!1fq|}= zLOrI!^ND-E4z2$AfoJ2#cqfylDKaV4y*CqV)`%0LuD<&uv2s9e;B=%R-IxVSHb9mC zn`|`0lMV0svULZsmQ^_~F&Z_da;-CTtX=$K*JTxzz=p5Jm7;&RY5Ug~D3FqkcRp)3 zJC1YK+n&u6*Egl*NryGa=$?Kqu5&5Hja21+_~(a2N;cv`Jd{X$31S}ki$4u`MV1%9 zsN;y=h-eorajBjCu%d=+IjR4k z`=1{YDcL|_x?5MBU}mb4e~$}|@J_Scy{qqUwVybWqU+j(&j;52`5}>#4U2xc2n|i` z!5(X+}ymjvxB-dBj)D5#b+^HfIb(-lb0S zYEh^@hZi)QDek?A_vdGclx%F&3oAY1Aoq$oSE;`BE&IvZh^UyQ*BlvP^z&=y1Rm4< z;UWK%Y=C}>$WwsPX7}w@E!C%mc^yhi0W{^-3;~LF9-OpGX{X6Y>j$4eS3eL3n*v(; zi}QC;dHZncS8kqjGB~)@|M{jJpNXd4aVSf6Z3W^^6U4!$0420ngSOGCt8vQR-A{MS zN$5VvdHHl_av7TGD>!8TLOcQw;$YY6N5W2#YHx& zEJ}04lYaE|>m4~!35zWowJJT?L1U}pWIlcbW)P=BkgeSPX_ zoC#GF_vMbYJE3Q)ganu)SkJ?tP6ctWPL4WOZg+ij9z{*yzA>a0 zh=bJny<`KlfG$pB~>7IlX?!LlQ{e2@MvAgQ-P* zZ}srS>DK{sSqeJy9M%`@I-9?K4c~Yx+cAi%-5<{cl?8FIUhO1C|HUr%c<_qodW-X> zzv{;yn`Vc#BO`WK@0+s-(Eo(Wf;gC3w<7Up=RXa`)12bG5-TgV!sJ>o8(mFJBcpk? z@T58uap?weFty~))9GoV{&2^9`4$WNX!@x^*@94!ZrshRrv;m|Ea`esYe5{W6ZUln z}42PdwX`9G_S_|S}ognTcwjHMGIic6JV8tc%5ev&+PHjdH&A2G0 zj;8$Jy#}Nfh=bJny<`J)M}(|aqr~|rF5Ot}OL;c0@LtAv)6Al$J*=ylDdi-Zpe%n`K-x-vGk>J7CP#KF`On&^DaC|ieaP(-;Lo!L&8{?+y& zC-o;4x3;c1@m!n#BkioivS|9hO^AS`lpu|il9B?_DcvP4-AYMHNT+~=l+rETozh5) zfP^Tb2q+>b@!tOKXRpV1`C~jD|38oK%+8+I&g^`4_d+bUK^#o2hs?@oVp6B=i?OI3 zXfXt=R3=wFuYT#l<6>|W8^zbxg?bC(V1pnXt$9K$yx4SV-Q@mcJ>Tl~V!QYJ%W}DZ zpIChls@`9Q)BuGSYjk?gOXV;1*rw%AhrH1*#PZjpH=HH$4bhvj;&*sc;mq$G84x1XLg@p zeZIf~T((ycneVSbhY#XlYCTDfs;T@DofeT}^1AuL7Z-vkeU$7>IbVLJTkf?%ci5n^ zAPzRFw}`wMJ{mo_s_$9m`w{IEgKuGg)kQs8t>*LkKO3?<*`cx^4yKl7U~2dT{uYzf ztMH7`r}3ud)9D9ZW82p|R;I$6&$}5yWkDQFt;{8P{_NaHBIj$t&Zy|$+_hG?ESkz| z%r3BDN`2vbWPnIEI32o*1Mnz&`oy13cuqd7ZWDg`Q4ObkH3jpk*+E=$Y!nzSi+|1bagG@0+|0Z_*8` zZiG&CyVW&YYT$Z|?Bx7Y_qbSWVWE$^L&xFSs=t4G?0-B;4|tP(71eIo8;n`^sn0zk zh+!_|+kbK_npn&qFR)_e;4QoL_eYrkZ?cyM?Wn}6!csAJuXPru)nQ3^gX&(ZeV>mI zPo1r&LgU{znFVk1q-$OIbI$tD^+C$GQBT4|`6>-f{Ev2P?%wtKf;w7qf8S&?yh*ap zP45j#RU?c&;$J14u~2I!}Fb_yV;SB-qSEfe|v zK%y{Hm0;0q&++|8ESvrCH7V?y=erQ!3<7bmDZt6yxZtKW246rcS%?G%YAbr$?Yi8f zg@Y}&`^LGiu)3g&Du{zk0Y9>xogeTFy3ns#C*I=zj@_*NoL05%X;Pg`=Jc!#`hTBI z18}e@VDX7l;&S<0nd>CJ(_H(~qLOkFA;$VZo5bVKxxIcq} zI!W9lF4QFWL-(42vgc56^vSni)%|?Ls~{i_HVA|-gd_Gg9D@Sz%w2i&C43keSc?{S zvD5@zNcI`dVWC3F28jEo?5||w*YzB{{S3O|o;8cvGq+P_JuzXlCV?l+%)Gq0{BB0R z!cl;*8=t$IllVtS0T2fp)Sc^$dMWjZI3o)Rj&A4Xx=*oVOKFHRr4$RFY(Hu$ehQTZ zaj-$v7UvxEuH+M&rJ7UP{MR2Ia+%n~#T3o&eYnVxn? zuH8H!pzlD^z@S^)T88H)%UdsbS`3v1aj-$)Hb^eoIZtVQ!-4@T|Nir%Cyr&{;r-s8Emd73Y7(Mut8W+mn6JbNU;8_ku2G6p05K{=hIWF2N+Gc!*2Y$o0im2Sr7*s1o;t3 zl=5(ET!G=Z+^e`6flB#%)bz7nG!4FeV~Jrh$dFnf4pQsa`V6cVXa#jvtrw4)hHURt zy;413Bsu9%l4d4mL%kj|UHfsp9`o%&o+fnoAPzRFLJDb5Y^#R`mYP35Dn);EI~tq+ z&aCJ2*_9{N+tXp_R!~_G2OHH<^_!^If-i@ASde_(3bzeyWQ!TW{QBh~=i@o`b&sT% zP+1TMQ%l948z0}=vqB^?e#6gQ1PT8g+fZ2|+sKkXwU~lK`CF(gh=UEn^m)}N-b;hm zS+G9MU+NBM4(n9IQ4o;mTG{#B=-2z17AgzkV1tm$MHo*wY)4A7Ul`FFT^IG*(Ek2# z#6Qosbw%G3&uI7rsRiO7wSKL`!D@k4%4gLwkRc!x#?`#E?nId+voeyU=UWzXG&Lsv zex$~!Dd4)eHyAlbw_!%D~_WR5uAcJm|CAioexknIFkH`vv|A( z6GgAgtB~wU?OVNl;%k(ZMvZt40K~xtA-rtGeWnqM+(m zNXE}(Qz5lL9BdGJ45}&y>n-zmmTfj>AIeChVWwB6Dp~a@94mIdAw_M4)BT=(?3akus0xEks$h=ZxcYLVq%_GtmRke7gO0<%^bldkyETyYy4BNq4IQ0pe* z!2}Qo8-(OKXU~jl8Fp>ju9t&$1l&L88Os$-&SkGP^qrR&!Ptd*3*umdaHZ(xy9+++ z>F*4Lo3Q3b6ZnI4!(~ktYU<1q>=WZK&qHc~I7qEu_tjvvKzF`p)p}uyHu$!X-mp;e zjnV5Z{(3o};vdH$OdW zIG(^N_aMJ_hsqUOGV{^XkDu`o&+CIY*dUOfh*T#kkMp5*poPcnw<*q`f7VfcsHqUL zCaf15qo)9=1>zvJe(j%t)dKBsoKsDe1ys4Av@HD>I!A?|1wCmw9zAFX%OKR-gg|A11H>HFJzT8Qr`f;iZyrgJ<< z#tQyiMik&rbVy+G99KzgOZgI-Nu4wMiAR174b)o@2UBaeT)cTh}f+40Fk_f7B`Lgc^uOAYsvhqh2|kXb?}3&g=j73&Z;D%Snd zZ2{UuA&s0eG#MQ`i`NDYxYkr8F2Rhuh|kA?IM}GZdz3O|vm=qXhcWeXQ}QU>%d>H+ zi!Sjc7hBPCsbvrj|}5^D~s;w96-ps% z584u(_-#v;=JQ@m=u0M7>FsyT#Uv~mX4h6k&6tT4t&U|6)P1NWVaW!l@_&;JF?h0} zNJSF%u9D%wwOFQZZtGU>xXpb$@z*T(Qsqv&M#U&|7M{rT^kJd^nD&Iq;x)ZD}STo$J;E2Yo>-k;&NKqn`h9 zV9z8Qq>Sp0j8T5`doQMoZwU}SDJT_IESem6(_b1bA>VDW`u9h9CfV4$NZ>ub8RU`k zSPd0z^FjN^zzeM3)~iF;3_5~Zx+}{6zR5GmhF0?|+4}l>$tAn-zGY-vZP%F8@|VTF z-E*yv1c#1T$N#>`Gs#9FCrcy#4+YL?$+-&xg;9;{Q(4A=eVhV8S z8=#-!*(o5Ssng8fJ6|GuDr5Nc?l31|g!FCH@Hee$VL3HPZVfWf)epqMu71lZtRExO z<_0YYA9#m6Z}49Vsy^=CKF5>iLg0JRyeShZ3*ul?fM!xxn$V4zeXqjk(ML|*Gd{s` zBUne5O;$YDzC7cNz66y8aj+Cx(pm8IMar8Ol4}N?%vyfFCoPFu?>*YUDKQ zHlg4l`@!Ygk8FlaGsDVHG=}PH381nd4rUh7(__np_pE-wDQd^HMVE_aDogPcsXrBs z9(OZrPky}zl?8DyvtE6a5W&QdIC5DHW*0;r<+`Cj()8#usSl18a|e%CLk?6H#KFv3 zDM zI-o1{)d%5cPEFzXjDNQOf7D*3w^vxH+7N9pS=E@ zeGuKcqT^=%I-yfL$@1GHw|j`K5fBGcOJJ_UAoaLMBP&nu3PmcbD>BQS9!yJTJ)G<^0)D&@y19RZ7htop|T(j zrj|JOG~J@?7r_j}9n{{T5PrsIdFwb-&o%mUvCR3UOhTcuAP%M$dq8aAFne(y?-gFv z_oP%QZRWP`XtG&EBWfg!ho{XEPtt%mm|E!@CwNWAx@ngCoP2NEMiNSsZ!mDTeH*no z?@cXsbRiT{3&cTc{aLaBx+6NP)_#C(ADZtcKiBXa!Xh%9PFG(H|BwNDL3f$!2Zvc~ zhzEZ`984{JmBkdEwSW}XPa8xPS5%~VCeKCg1biyHx#~Qh7(C?%oh%RsQ_J>9@M#MY z%_Oad8b*4XOS;83K~?uhsV@_nm~d^-8KR-GAP%OMcB~}c+ugM^`P&N8R0-ox4^>p? zUfqbKIdU?fkY3Y8ytf15U}|x?_#;nZe#E`L$~>ez4YR)f#vDzl-u)|3Sr7+P3*VKXBr;1q9f#0!^UI*m&#_8y$sXP zMSJTfr9xEjR*hyHg;L_2-;T=-wvN2a&cvPDkx*|z984`=`OE~XqV(LxNHxZ>p*m(| z0X^Q&)z`@yMM?&1tbVXVYJoUNtzY{m;AK2$hvTeTeQj5p(w5V3vs77Rc4?HNFR?~m zEa9pjD{^$3KNJhofes(U!PNSIMM;7dZeYM$J@^$p#B!Mhi_1z;Ulm(_p_Fio*RBF8 z3*umErFvS5q`8y-^G?yj#JW@>6^^VdVY^L^pB^2-GU`8O^q{gJ4yKmrYu(3s+kq`& z>6vQsN=;Pz56r8CXuaZ+HmQ6Dn!5s_vLFto7JuMg`op3R-GQ1_%xhF`=y_}zlA_6?Kl{{5hSYa3^vJarQVYaE zYW-QV0ouzxtJe9agKbDQO!EiS*P6a+7Qg*QQ-XqGL;o|RNj2$&?}Ht5_#h6Z7A37; zUhriub&!wf}b>srsOrd zJVI=hfH;_1s#xxnR>>dLjK7VtR_9M&4_1DC&#AL7!k0EZE|K`z79!agNN*LLqZ2f7 z=HzEBPtwTuq*|-?NKW+eO4SN9tIl}#d$J*#V|-*2_so9e$)Qd7>C0*5_c${+8+P|4 z-E2f0EEj+OPXdBmwq{=~hgA=ippH?_ck84%kt74`$j)DE$uN|MFjry82B`9XlMR1( zvav0Jw?lJOHa<(Ep@6?V+~5?K6FG-7f>|^eXB6T3fb=gnZNsJf&mJ{A#tg1Y`&ED6>?jxXgJEu-C8> z)H4Yz>rqy8F1~Q>N5Q*hK5x0sr|!2Y;0IH^0+DT&)2VxNei;x5Pg8lHUFJhtf1H6 zFOTv+$p+}Bcyng4yp8N%Iuqog( zv&HV7F^c!@XJkWTei`|x#VY(CYd(>z5ubyMGK>(Ks@OK;)29cevcq>4Y{74XgQ;B&K(|F>tw4pUbkW)pd9@`C9<@J|E6jjny^3| zY;`f=@us*xtVhVYzsKZG3s>!73@UdpH;R2#Nu#*R&7vSE*#L3>jP0*vGLad_CUl=k9prZ$IFtcJd?>zIy|8bO^ zH}U$`w$;dcmmis`RZ+Jpy!@`;S$mF1p+FqWER^Tw7jdvECL*t|%-fgGn|d0IT-^!# zarnHA>qyD*2PV{85C=2s=33nH1DTsG=Prie#Lc)ds-MH!$5NafJoMBdw!KlL1(gMH zFtbGKbZ)z3jt6KjQ;BdNbc}!VAIxzeKlfbE^rpgK5RDjA7R15KBCE=4E;iJXVQF*f z6TiP^Foi!JfOeItQ=WzaqoP$b4qCT@xPQ(1cisl81)5pUs>S&An5&9Mb2uCBy`VxXCm=D}#Hmng>Xsg0KRI#y5k-PHm|FZqA)|tCFDOs& zlqI?fD7j8$yc;8reU<1Vj!ds)^so`?Er^4u#X8Vq>u|qbhMiM&fJ0FH4I64?{LB0E zD5iqkZCk!p3s6}Q2UAP6NRX&tO$F_lDA7T7%}-(flGA`xLUpF6hRnq&3}4KkvLFto z*7+jm$J@0R>b@5rwhEMG)oNS|4d6ds@5(cyd~J*pgZKw=5C^ICXUPU=rF>Sc4^NWw zLR(q)-GbupC%4cij0rMbW!_m9rcFBkLN$uy9d!604yM*<-;O9JrGY9|+YdweCoJw% zWO%O$Z0P89v8!T*l5CBkvLFto)#jxO^oGVafB>*cHbD zh|mng!PHvJ%nd8HL!KP{W)tiXl7l?zY#Y^`*_{LAm zu~fIj5?9ijW*Yj+N;gjxuM^pIlRv=t=WbgY`R>!VC^R4rrq+SdA!kh(Z360cK-czk zOjb{<@ePB~j4?-&!}yF31+Sp17Knq?`md2oO)cf;gC3R$BU7#VeQJaG18gCp(b+@a6^~KYi}U$(ZVpE+ZLTYDg^* z2dVXE$p+}o_pDlPS(ukXIEa=sr45hFd2=YSE`B_)6udAs$QWF0zWxF6dN7ECsbxMU zv?fC;&zpje?Cq>DXD>Ce@_y@-M=fYw82WecPhh=Zx6W5+x3TA2@@Ct)CelgIGD zuC{8V2{Tk_@1?tc#RDBg9t+}NYU#9DZ$`09MC)3AtKKJ8~ zgF8@fK^#mi&Xt#$Br#kTn3eb9t9p$!S}P_PIY)AM9LhcS#<+P9@5h2Tm|6|5PIy++ zc%HHlFJpz^i+uYQnEh}$;L#2GT|8sPz8nciEf5E(^@qs@V29(ZTCbG5jM#kHsAn)G zMcB76J7-VoWv-smPIV+aM_*K%y$p>ih=ZwBm%eaKe5-8R@}_WO^__(6`|E+`HL{ZF zQ!RPVdVc02-hu#eFtu*6RK+f%#vT`4X4y!hd?)RK|GFq|p{(st>oxi_J|@Idl^_nL z*4LL`5-rV-jpV!Y9@@R-zI}m5>mwi^P7h^efiQmkkS za7sIj_`!lWm|7$kTP`jyuW%6;y~KDqyIu4Xcci)W3UU0#4ANxQ3uQzU3F2UCse9up z%JNxhDG1=y_Q<_Dap1J1zF?`170@KupR0M52|8II4yKln*N*kG)FD$6OI=>S(o7X{8i9Q;zfSta|s3>pPW zZir+f0fmB(XTTyA_O0MJ*KMssiZ@ld@pxP$zk&N|p z*hSriB^#j1|4lYJ;K{~;WfZwli(J|B?(x9lNlN_ii#h{FOgwD{$@3-IZs`iYb?Se6 zwV>lnvf-TN`J!ySqUg=R*5YLFIIiyo+t{ZiLiOMo&d@7^*U10A$ur4@+0}1^@-%wl z-P`IbEf-O3P0$R$`hwl8f;0@0&c6Y?Qv0pDVsR z`+}ZFMU$i~TKy1paQG%V=EPUkCnrer?tdSQb^N+w|9|K5bzI_H{!qbiZt^X(1kp;h z4}N1t9_`745;hGkJL)nY{_-gQlWc%~if5+)@xbMX#29xDk(Zr6u4b!v+?sE5n6EOm zw<4kSds`H23SIp`9Bc~sT*Ri)nG(W1;-V4xg>RQudVju>#_GYnt*3@HXTIQH=wd04z>)?)-hnsRbVwOIo6BdmioTp%Pw2+upwu& zPIJWe`G=Tgs4R$s%`7S63YxDIP=!Y*-gkrwL?4LKywx>mXV%}8EUq6&rP+Xz4G{Ow zDf^Xd{JNfl7Z;!_?pd?^9;B3^G66+>=;%Ql z%&ZErMN0YH)53MDS3-;}0_-KFsVb_bpZPwHN5&?F5e7hIK^)91jjQ3)0&D8}U7`J# zk0%*wO81bOjyF8R>_kFO{iMRwpt2wiW)>Y&nh3)&;o+6$40Vm3Hw2>^SlF?0nT2Wd zp*l?Y=Fg$BAP#1hyPVEb^guu2-nk>escy^YZ@N`S*~;=)8psM#pGTx4zCZ%vU}n8j z6dK|4A+|Yvv5%hbf}Vgg_C1JUkF(!yyTeFs?qCqQB?58(nkDsr^EOy5(9C*Pt)>pr zd-=~ZJJKJv2Vca~H~(NQrJ&SziuL@cS`DLw7V%;@h=Zx+(|C@S4oTY!%jZiR0h4@7 z5G~Er?8OatzZMR|NE;KxW*&%xsU^nJ;oJGi%Na|isQt(aY4>PyF8y}7%fetynk5ap z72*ka5C>DM9BDE}Z_M1;6xCk`f4uudi+qlrDvR=Zi3&UIw|H~JMh=LBsr72MU^wol zN7JLaqxllrD(|SGn`%Zks2)Byx_80Wu^o{df;gC3IY!a^$6FYVTp>;te343gDpo(P zrj^l1EY=*OhqA09K6D1+AhrH1*#NDe&Z@QHAwkln@??I%#k#=p9%IYXuM$7|8tO47 zHQa~My}lkpKUfe4Q_Eu%PvK}otmMXn-B0meUxd`h1BO>eDw2@;=5gF7vJl3CIG9?| zCsKIk?5+YW)Ys4o@7$=ny20yv5ohplQlp)kYdeP*>Me+asU>=-E!69PqG&R8c#ZWw z(w=d#{dMOrN;%buzTr=c)sdxA?AF}2D`K{Eyq1P>(BXqPm|A-w=p$2u zx38hk@=eC`u->kHK;$Uh8txgbJU%@@sOOBU%?Vn5E1OEB(=c7!iJ>=NQd1{!} zP+1TMQ)|stWQB*XV{=pXIdw0kO>2EWMYR++I$!1{3eMfa&UL6Ph=Zx+YsmDGlpj;N z%Ni-ZdFE1guQQR%fLH|!yWW!llnMC`NG%Wtsr6^c2I!9HtXf+ndU8oKYj)MOq+i)% z>Bdy;Z$G(PcP>fI!uj^xwHJuD4M7}CEfz&HBggV{J8GeZ>$2NQu_3D0!(WaKC*HPp z3)XkQ>w!iU#KF|kFRhcs`#E@gbLhwUR2+K~_tv_oC-i$!K@H*)Ea#6A*H{n-Q>*E7 z{G%HI7Q$r>{L<+x#`Ud6_-n>el zICPPg;Y*H?Z1(>BDXUwkn^0L02UAOI{hEB}Y1PXNvxciCr+VFVSy-<}zw49x{^(*# z=9S@t)BC>iaZ`kMUGN*iS&}R*##}v8&-AP@zO5)pS&(%ezL(Bh020Bm|Bw-b=n)al4cfx z*W&2*mJ*DZM;5jFYlG?{4XMYnK5{^1K^#miNx9JWPMghYjJX#!L)V&ys3!(h)?T|Z z&eD7R6#f#2NRL4rOf6^O?C}y&qW*GUO=;Wm^u(iEE<3@a^2dsGDR)|4hI&E01#vL7 zE>?zsHP)``_CITa4j;t9)N12?#uTJpLX+V{FfF;;;9nf? zIxco)$Y1iIf6V>)L1CyYh=Zx6DHulO`(2f2Vw1eV1)p=>+*{#%SVW1yhv>DdHCOFZ zp|T(jrdGcF1Q+wfs8l@05Wa~-Ms&7Uy4pvY%l?~d`Dpo>QwdO65C>CBAO)j2F_|^; z?QrT;>W`QLj z4Fy`RsuvS;?UEq1KpdpjuYEG`S_`z7eO4{0mAy@hh0bLhlwG!wLlz9t;O~`*h=x=<#$Q<$xIll)bx?T{`snX(72u`?< zuuktWBH5U%eRnXT$@6%#UdJyXXEnhIJ)XQNG51ScFtvz)1q$}>$;Owu!7ge2neiVM z+sMkH1sn=OykCermvM3-Q04z78%T1nP1|#)vf?tj%DBjits!qXc5Ss6EmUZeYHw9C3fU(#T}S=P zP1|rOBp&!aafW@#N=#A))8}bQhr#%T;MVV>T*hId2e~)ju-*vW+x+_`RpCuup`b`lhpOjr4Li!^~q}su5!Q&MD>sV^7(Yzi-k2-lPrP=|fTs!8&2>>Wkw3FT9M& zBtw_*)D?YALbvyD?~wd`llS3GzUcO5+g84K?Zpez8MG9R^8FFZ@}!FusxgWDA1q=8 z?Eb#VM0k_c4_O8s+lRaa#_#(rqL-8jl>{q3WW9zh%aicw^*;|||9z7c@Fp)SkiW3x zx)YVlG`S}0Zfcp%mAiF4(y72$j}D!Fh4$d@n{0tMnQ&0f#mcX$fzMrXbd%uvAkWc! z0+j*Us>Df!Z$`}Q`@d}RKgkB@r+9V>5SMPrO22i>e`W1@4GncFM# z8SDYHCHi#7-ANQE*#L3>^!8V> z@#}gHUR;2#xM$7!vPGxAFL;?)NAdg3Lj1SfnGs6{BhS3QI$a`Au2mI7JopRZU}h@P%atBS%CFnWUcyAUSG1(dE_b~=f7W;^OT?k} z>Z~css}>a9G|$D-Wg=W!Wb7<&mM1j;d7z-$8^*4^>W0o7R15K+A?nAu_`O8cNu1#vL72CAFyzdmNQd`N;CAB*McZexK~ zt6!c|a%INRr2Li?;&lZO2Rm6$oTFX_BHs<4Zo&<9x~qlGai>~aqN&C5C`k+J;J0Dzt^%E z2H2H4$l4W*t*qIGh)$oGNZw@(AG%np9|<2XC$E|emNeBJ4bPE%MeGrRIM~U0 z#_2IF%Qn{~%PjBdxO!*EDBtAT)%3WXE`B^*v`QMp4IGGrsr5oonad47Smuj~qucIa zsl_NOn^>O)(b0TSA;%=6S2*MVh=cX^h?>;>*@^pI1}ZLzK!&7IzvJeyzj7 zYJpbDXVofqO6w(L5#GP3qM>}peprF4Oa!x*KT6Im%ZsVG;Z*@Nsvr)gR=ro`*na!l z5c6q?bhL*x7XB4(9OUB;$31B$-wjcJ#IuIJ za@&9D8+k~qk}W3UWPv!CTHhGeqcH4j^l{~Uc=;?Nb#Wl|L+ogR zI9PA*+kVxenk-IJ_4hw!dy}~|nc?ERkSVEo(wlX^2z%wiLQ>z5YDbn5pbyo7_ z=1uS3qI=akBadC8%sv%~G@%j%Ge<*efjCI5U-w&JwLo`7XVp@qGSCc}P!BRfrn!ml z#Gc`$@cfx!v+Gm+}3YS*Wjp_~IXkgQ-;= zc>m47$GGvIM4i)-D3+Xbw3~B+(wBoj+ihb?_N5~J9SFq1dW)o)d`UvG=ZCB9oi;Lp zX>o3qQVNnLpFlLr6rKyBrilGl5C>DMe8r<6H8BUd^6AgUD3&HJ#`7UtQEYVU94ThJ zr??!5PX>WFNUdM@)nK(icfM!UQttP|leaVypZ7F99@{>R?F=R6D4l68+lU)D%ynx~ zgf1^24yM-2%=-z>1l#I2v1%7>$J=NRo^{)pJ86BCA5#)^?{-0`1>#^QEAWSPKk+E0 zso<#b!_urBG6!h^LUu8odvqOQt1Fdv5$kOb2U80jN4Y;lBzF2R`D){>Cs&ZJIM)`{ z$i^L!N3L)Nxj#j`8wuiIy$!0beurkYznHW}loYwIa(;^WJ@T628_6Q?wN)Y0NkM3A zK^#mi?(7t0HttPiy=|n^{W2`xY_YN6<}F?cid}r;ekvtv=&A+cAhmw&pMcc@?Qon` z%Y>39-j=}xCF#Sag0G8C;kaEC1^w-FO$OP+1TM>n%_D z54UI3O<#j`)X(AXe8dqUcvz~|l$-Qk6O; z>Ghq=Sq~dFnI?a2^&`Meh13FZkXpa?$-ruX_Oj2a#clTHK&E_cChzo8dG(ef-L)8( zBw;MuSCy#ix5+UV&q0R|;$UhW6NhoWe7-}et8vrs(?xM%_ihZm+Err{(@ct%ABnn% zuhM}y*vSf{7j7z6a(&>Sbv`pjW#GvJI#1S*s!8V3K^d!`j=wNMy#;YFwHj8K6fE`} zuZff@ctl91oL~H~t@|U@!=ZRQkm6*KiVG?W;$Xe?A(NoCJ6Ov5Kz&)CpGy8JEsi#Y zhd{>2)!B1sV*-c0h-5>J{XSaf8jiHzPb)Vf?^La{bAw|N9@07_+%ADy5ke1sPd3P? zqra;u z#Y6V~hjuR>D!-QX{wQ2nvH`06-(*7!o@^i~T@rt;)$@rgqvg)laxi0cKyP}6KDiYp zPC3OhH?-s5I`zN3vmtgS*|;Z#W`=dzsZ3Yie>(L}q|t9flU#2CrI$Ceca8E;(CzP= zJd(bT=69^-&U%de}9$n zqfABJz1>SLvb7#3QgVLjN=8vM@?SRjpJW5{Q#?BbEPrunF)uB`;WV1kJ63rgV9xm6 z-(&Y{TiU^J@M`h}8R+T<;$Tw%=7p4Q`Tf+DyP8iGb43OPKJ~kolLfb>#9)`|KJsF& zgUW(9*hQ5(Y3lBtz+{?}8rt|OnSJvc8mmjy^O7BMMXHT{h7pM8CO{l)3P81(A)piF zdO3uT!|}n6J$b?VN?KlMIj?KZfwwh=GCkB=5Emql|NGad`pSqc)SB;xeyM%EPQ;gP zkM`E|M?n zU6#uF=jFeDzq|nA{yAm8l8s;2bMWE8?U$K^)Aiski78`^lUQp-pwB z=TjN+`HN;+!?xLY!^!li1W*NBCbVt^asQen z^?&m=SS`@ZdRDDE;r0Mu^y2gNi6|>WgXyg4X4m>_%&t4)H*mc;$!hzZ2O)Jnl{ zRHvNZ?Df-$6O-#zvf2*9cYNt*w5s#<ty$xe#bnK^#o2(q-8N)S=$S@WYsQuLG2j z=O>l)X9F)fu5*1m$TxCE{1-2XgQ-Qi_Wfr;GG&~CImdA6jARh?y%l$p3OwAJL$v_D zzUPRWcn}9uYv)D`+g$fcYN{)emhK{MjW{>x_19Ff?PEWWJ(Jf;sD=gs#KF|c*;gCp z-buWdVRVfn(o@C*_ot++V-y{)-qvf6tT*yTkXj%PQtQu>4bTeetXk;LE7VTP+Ab#T zq1;H>pDT)_uSL3w65nGvm1~8=DEt9Bd=Ljy%Z(u6CDx2ZI>+sUOtzdya=Eh}Q&IuV z`?@debCmEZ6ri#o4yM*OwL2>#2cO@H>gJUMtZ&RHiru6Y=Fa^d^k6RXg}KNGR2Ia+ z)G}LQr#PUxJQC%v@ifmcAHz;b%rwESVw*k5fPkRkvjJ2V#KF``JTcjP&ruR>iHu~T zkJLWw#l)`8!_HuWfB9)+w@d~iH3D%kwWu?CNNKZYmsjK0CVOzc37v?jrM8;e@mN)% z>+{n8^GRu?f9FFG2dVXE$p&bpd{(VT?*-Tqr;MfH2(qlCQWK`lwoXi zlacP#^MR{Gd2QuVR&j_|K|mZ#t)IZR1b3VB8sb4g`Sm`xj{ zp8wTF`|MQdMvANG8d6wsSQ!^g5D6!UgQ+#lTDZqp#I}3ymJ(s(?P&%VC1p=^&1yc0 zZl>g^IduxCw;&Ft7U6_yCDr-KbczWe7 z)0B&z)QcJ4_)l{z+?Vr``lFz$7Knq?`mYgK|91K!Xa=Ef-hj>N}#KF|+{Y3YuD5E9Z_~WjYlS4n#xT}x$=c#5%2YETFnnoVP z;~pRmrk1w;&m@v&(dnvd`T7h#%yA~&W@FnSeX$oqZCu1Yj3Uxw5C>Chpp-J(?=wxA7D{fGBwya7#Kx?A zbBy^!Bz9CEdlWIYAP%OMd0c|iBeo4<AoYoJwhYV(SCDro{&Lyg)+5KeQihu8dVSnQ>)s| z!byX4E7=6C`~8Cp3_rJ++YjE0(1=as^LNxJ#KF|U#LgOhZy0Ha{oJIiAhE+e zMZLEh`*!1Cv!NZjr6~s@;sbFowV0Sw6}(=*#^nF!FRgS>wy`qak}g~lpT(6T#M3J^ z<$VAR0*HgDCDOL0ZBB#B{-~m#D>GDRYaToP<eE_~kHL%h5M;$Ui_1)RV0 ziJDkt1oN4%@W(OT>QKwo&w3HVW3v{UyrtN_kXj%PQtQu>4bWcpS+#gl5~WO(^)4!~ z{s`*MmJ`OWLF2>4S+DW!)bxf^HIWd_=qq=z=G0%cHm|7&FInm1e(xGXkzIM?y zy>`=<2UnJ^70yzaV~~F5UFbq28)!5sFGTGU*Ax;<@2F%a#(k$S%$c=$F#HH3WazL- z&iwaeV;onF{hZaXq8ei{3ll*F2Xdofx%VD%XU|KQ+i$8DWq;crm80OxKynY;8j>wL z{NZ{q6TFbYKUcQ2MN%x8PsW8;3rjXYmH(S;_`{Qp=&4%=FAr0Kvja~uFW_QY<#dPT zpZZ`hAeGcE7@eAh|JJGhE!prtlWbfZJ!aw7zuRNZeWKHC_c_=jt$TO;a^=)e!}Gh9 z3pIZqGx(oLHj>32WK)NVXzhNZ_0wu2yWUhXUQdETRLp-Jn{OSfb^Y&;@=UVP$(Wpk z$5dl@B_ogX>(Bk2i?N)9Qy9GcJ8iq4Zp&VJ{r63tNj82&PcD0%d?r1zHQs7aXDJF2 zK&>$*+wj7hSf{$(HIni7O`b_Mwy5%)HRSU<4GgNj;_0nTZ;|Vh{v6FKXgV)+RFN|H z_wV2MpGh{vyf8W6w%&K_PgM)%4qFnh=#&VQ2ruyK&t4e6K|a*{_eXgq*%SEG(zflg3?>YWulmAIJKtIK^Q@{&~S>7u(BE08>v`O>m z*63gQM|SQis#WZk#b1zbB%6dTsvr(F1)x#{u9>~Mlt!6xK%(<>BiuxpD_L2>aEAlS zQm?wQ9kJ~O;$TyN>5IA|wh=8uef_}Kl11;1B5v_IxyEf8{6v3eUv=vqVhR9puqmKH z%?<;xxY}8`Oz!p3N+JKexa! zZFYFwA8c~Z@pd>)&DTdg;RJdLY=}=JfH>IdV&RHBi=mNe{=r=KN0|Y)>6bx@5-2ir z(lmjIjW2D}TcBhE#Qih2zmkn#*K_dpGw6zY)~rlX-ie&OgvYTYH>BK6XiejK=S+mI z_w(kNwC@h)bL~Te3gTd93897SiB03&lWg-M{h0XAUDuV^*uZ8_QzoukF)wZnU_xa< z9L%f>U)Aa~2~Qs?co8|ody6d7N7s9sDknd>ZhNSv)v$_q4FJT!%xaWBoGhr(vQ^~S zl{;u8QVvo0;*e8`d6JnyqA z0>$1C`?}3|s4R$snYGd)6|U$0!uV!bZ?uvGzQDJu&R%VN()Cw(JEM<=uk=9cRuK2E zSyKNuZ-dnW&8%nDGKnw9%Rnmf(G+CaRd&jl5>W?K6LmX4yIP& z?es-7`rcDxLaQF(cgOx8_-We9@#zHxYywzsUA4Lhl?8DywN_|k!``G-arHUsGUx0C zrKw(38&;a|vF5ffbfaEQQiIBZIG9>aW?tWzZNeG~s=w?u6bO>kEp&U%ye8JMS&&)& zzV;B2@`5;+S~Tw6D=owoY_A+yKCt4(g(NWmhGoz480RCqq#TKpEJz2A+v`m1;Ydc8d-S0p=hi z0V12Mc_kaf9wCT>sYT%FA=f>tl5=7`eN9+E2`gbWdHCt+=6OEPM&h9WvqR`)fjF32 zo%AXb6$Z`}4c$5DSu}-wLtQHcA_}r}`|rvcbGif&WkDQFt!`)QAY%!}OudkPwPb4( z*N=E9)#--qPiNO;=~z@Nf}!4mIG9?mhN+JSOnCf)dIqQ+TSBFs1T$4cpK_HLHjrC9 zsyqmS)Bvvfu;$-9QeFLLx-60peh4 zo#6RIYi`8_MLvy+4Bu;i9`U3@=W2!C_5Djd0mWRJPkXH=?$`W4RW? z@aZx6Qu_ot*;9LiS~0|TT|gX6t%3N>oeIxaS82noG3{Kx;c5n&a)<_#?DmZWdj$3$ z-GF)v;$UiJJj;zzFzz00u~_|@#X%T89e)cA>7~rLUl@ilzqs3Ts4R$ssg)L0#c1~S zoZN%$NdM9KjJbwX3*5(E;^qFf(>uF|+K6vwfjF32j#5)O9F*Zf#Bp7{N#em02?QZs z8;q#oR|*F2S%v#thtvXbkXnD1Y=G{3&#JX1f1%Ccb*SU%`QF_LZGmIUQ>@#!yW4%z zp6u!F`aJA}Mis=t)cSPm0@l{n!Sx@Ntz0`D=ifdI!l84j^FBy9S1P2_4C_X26+?2YXBe)rq-*P#vS)|+U5roSmw+aXgR*DZR99+$;3+K z`X`b)&9P8#K^#o2r7G#xe8(?=L%5-+3>C#6Ij>T0pwn5HOMQPfsn&C02~rEhL2CV3 zvH{xRIIGr=dX?ShxROtFH~XDd%w+j`rw!1kf;gC32E(`#eS`Cq!m;h!WID%7i-eV> z53PfA9UB$bl{!?Mpt2wirdBn>gXu@j5@_4>Jh4g-TRYA;-?|YlJihm z5C>E1vsVgN*>V0r_Y#9-OZ*4Z0teHD>0ikDvDIT`9vt+w^SC6O3SC&3@VX zWabs(2?7uYQ|qpo>v3ZbI_=wbij$t7ELE84MBSO>j_LNiS$B^#_7RIB5C>BWhoeT_ z1&boljam0qS`2di`O~C_tb+H8@ID{SRf}{XB0dlYQ|tON)__#&C2d`Xqi5pU#%}+Q zw6_eas_WjjK?Eu37Lb;ZZX~6V1_=r25(Nb54iQA81!z$=pU0SEj(P1_*IIj3>n5{BW0WaG4N0%0;qCoGI2-4J`-gq$Hwy9Z za}5W%9y9Uhx{--maD{FXBzs#AG(Y^evr#~{IIg%DI-Tj3=b{n$ImxxX-Z|alxx8X5 zIh){${>H!VM+I7LWjtrLmCE8JEqhR~%h-`5$*I8g+FHL2O_C*RM;7L6fFl3T*=U73 z8)!-fy6BiL z4PG-Inpn+~G>Y&td*7!lD5Nq}sm%RUF^{AnUp z7H7$Ie{9@6QVi`hL!s-hS4S4l-XxNGd^cAbz{fH{FK2kwlja)aEF!v5C>ZVuJbvM3h?C-xYOAy z?G7`wQXBHV~#=vZ~QDaTZCZA?XGdT^-Mq_SeLTI_bK{xv8ph=VNw zW{iPCuPxCtav46dWgtiBX)pMEA%A*f{%n#`JrSYAOKthO9O{k5z`j+CP6J-E-u zvNHSMMm>*GXjaw=jC*{su1o6i>$1YZPqtpkAk~TBkDs%We>vXu5U^ijwl`>AaS_kE-9)c))Y z+fY~#2dk|GtEHcd8SyS-Q$?PA_ zG0k!s)*(zY5C@Z1A8N@^4l6dp7b}YWQ9bjp>W@!fNuazWbfG_7jtF8ofHVN&VA9gh zP(KtMpM6|x@hc(bVOECiPaY8=^A!wtYppjIFWw-7!h$$hZN*;+`hCRq`biNSWXo=D zNE0Z4%VaiseDMfnR2v1mm;sU&h=Zi{cg_ZAr;IEu;lee%Hjb6-!G0M`Va532Xl1A3 z{ua`Mfp1eG>F-{tK-~v%us${3Xz5jp<-?O>D=`hD^fg?B2q_MHJjaM8B z3*unX!eplUy&Tu2I{i`B^%Mj`fp!M0@!g#~diX|=l=SkR4AdkHIdb5V!*J@F!7NTkQq zDk>|IClGE;L=*wU!D{=koSD!O#Ux#bbco@*UiyGPxSv_Kpr zt-o_NKqn$(Y291hYoZ-U?0El{#K3vH?%8eM(NNnA>sJ66>`ceLSaE1Oj;+yDzC`mcRqLYpu1{J>u6-O z%LXLTPJ1IuQLmU1#vKGQCGUZ<#|Whg*7DfOePF4`Q7o-NZ}PrTQqU{?Ijji#(wHwjI7^XDPL;sbH8+A3FJidw%f z3|bqv$5jirT~wZ=^=S6TK$YS1Y{42dN`s^Y;vi}LowETt`65e;^vmvI2HTVC3Q3l0 zUE7ADUA4MyRUMZknqu>BOC90OLsbQFulnrGI;$YIc zIDN$N+U)hYklnINU&g;Vd8}OGsmjK>wedi!n?dd9^Omeq>EB-=iiY1U1ukLUl=P>`VKj9X&~aCh(H`nTG(hI67RA@ znc01BZG2anvTBNpxv+4}-Ko8iqv-dv_5YrI18^{DrGKOP79P}b`SrV`j$+-H$A>G^ zRPnk>2FrFD8Rp?1<)I>gIGD5;+sBfQ#kFl-jM6b|evkT~G<{RSnQ-T*&32wH?h_v3 zO->L8t8FD_wZW>3gLzv`KMixQk=#OVJQ=y;`?kTs6S={6)fAAlKpZ5kzjHP~ciG6& z5-a7fCzY*ZQ|8@MOTkxRSERmb7*f9WTaKQwsxgSw8mcOYgLS{pW=Y57sg>zd7rTzF zHVWnB)=*BjYfjHj%(I!aXM+*vZV(5PmRL*tz>Dxr#!}46Kvt$j!H3;(A6$#4akn-} z!b}+G^%2>HX$tC6_1z}ga(-<74$heBWDIDThqvZ0$h(R(4BjReYTjtVy0B2Dk}^zoUsNn3P7 zH`5xoXc^sNmijHCrvL41P%||hE)0I33&n5qPzf|Mm_6T^f-kqGca(9$L~Art^{*@0 zmioRF%Arb}JIho#!|$gCzSKTU>l-$=KIVS;^D*gKDa_dbMgE_&fuaoi&IXabsggbe zZ%_`oVH3fLZmkFK>*>*_+gYH(B_)ar_#sEZX9;N6IycYrkz&udR9K zt=)Ju^@|t9f_3ohO`5=)oINlJOo~aEizZZK+eaIlxPiiT;c8x$Y4374QS=A=vpX9o zUhpR0w(kF6k_#~($Lpc+I8Kui%`_zC@%|#2(dIwP6qX`>_Fg8!o9r&8jXz1Xbk<1}ZBtc_&&nwrzX0+m)p8w#>pm9RkCwNn5Z1TU( z2I!}Vyaen$pGD6M54}sS*p~3Hf8*8~MW7`=DZv-9EZ0T+YuzT$><8jtOMuOGI>B)8 zQBjSdg4vr9YDZER%-FDDidEMN^YjRmNW@$X;$Ta_`$wIl5w1C;*3$cy(rMYJpH+Xq zSIak}4<$5x;UB($c&Zx2!IpqxPZ@dlt6z=W(X8s^iKo52(@S0YUWe9F#J%+O%t?C& z6#>M-!s@K%F5P;hLA>EcWK~;Y*1KD_{ZxmL>r*t&H+^jB6dNckh=bL(V8_o*L{O=W zSO;~ru&U|##%umKueIGZj&Tc~qE0Ll$k_mK|J3%6v+-v>2VXyfW?W=ty_>q(vpIBE z{dZ=~gFxeFCY1}{kCpq{SG&922KRVy5#Ngiaj@RUPGmToVC7+`D{&k>!*ew$8hz&S zW=FhgymOQVvjMXSDk_MBDT}i@sJk%n%?jpAwO?QN)EcTSQnjwpSYl_^MGrhUqCi}Y zfH;^0WGo^J>n6w!o7Ro}A9$=s@fLnB`Tn6aUrdWZGKikT1IiY}!NRWJ&Re1LZ$F%@ zoz7^cBJnd`try$QVZRW#}0 zb2I54Of!kfF3`Re#QjHEa{sq(gQW#pS&^mnP1Bb(r{VICPt!f;SxF|YrsSOpIXt=L zSSf6JX}HDa71VtY2kXAc3ith4mZJ`zuw5wzY+f^59)Sv*%X;&tx0|@=dJYDmupka5 zE%irRulRDuh_RL>YnFpC9{A$mcoud4etLN@lDuWS<2Mu*#KAPsKmB?OZ`)>2!pBU| z>G9Odpwyq7%tMDu`)O{gjVV9cps*kg7WUHik+kR!Zp`N^Aq}%mM-N`zlKk-cm8tMW z-ZZaM%}~Sv4#dG~n~*A`uzC6>`r+6hMnpt?8GrL+Xq7fKnXq*@u{s8q5hN`T2TAMC z{tPTF&<+Y&T59^=Z3v_D4AeMCzg{*zzntSx5G5bXx}k|uU-0@!hZ(9Wh=Xe4dpvCL)hnMexlQ_L zp4j>nZB~z56-%A(UGA~WrzlZPDMn9phNK1JAZh*Chl8aB+9@MTtI*eQ*|q_to-SK2 zOt>9I^!c650478H@1KNY>Lt7i5NiR5gLR+RR&jx!a_Wl|8~d_Wc{(MY(_q#bm8v#N zQkBYT%7_m%SRf82Evn;GAGZ2J?mFfovzt=?{9?PwML((>H-$C-q+UMRNQAG|#5PZ0}Eqw>d7R147Ye#K8U^j41NuylEx~)?=p2LY|Nu&=D!o zW2Mf#56|y5ToT=GQ<8tZY;?Kp|tuW(JZmI>Lwjt41Xp~Q( zupkar+W|Y5>))nshbunLd0}Le%-BI*Cb*7$>uEvXMw0t3wg5D1fjCH7f6molX@O3@ z$kIBb<7w)*(M)@*qL$I4#W^_lOa;AH+m6v><9OR`l2QrkK8S;L-(K8^&SdOIt%7sh zLplp}`j;c`jX_DSlZ@iMnRpLNVyP+<&?K|UZekd%6gVlC)j-;F;ExGQgq*nby zKa#Fe%R3v^niqcI4h$IA_e<%X1as=K8{0 zrc5pqp7SQbb3RI6BAQnb<~fLib^qS^X6mabc9T=gv3quRZ}w?jmYH3%az^#qb2XBY z>hFW93gTeW8XM{LV~L!uxnwGQTPJH2*^ArBK)^k+q6(}r-gJ~eOXbmSd z$gP)4_T+QGtHKBx%mokZ5Rdfn$DLRy>h;o4SP%yb>#@dC7mYzjrTgao>sR-J>CL~5 zDL(x{@eXb2dh@LA(={k8h=bJ@onVVP%Qm`w%7)=~V69IM%ZkuUhg?Q< zNLnBclGdMlGO)BjciG6&I^33HZzAlQ3Zfc%zli^%l)g$o>8`TqfwjsDe*=PO0jT>R z4%YpQ(g8VVzFWh+mE450b=OIb0()MQl!eA94&BS{-#5#+h-7QD`bb z!|WvDy#4o{slK~v_y^ZW~Y`o%-H}% z{-3iU4RVwVR!yxgJG!=o)^$A!y2hg?uv< zIaki!OC)Eb?!3I&6(%Z$e9rH+JmEND&5O4$Og<1Z>14m#-+%kYhqE_{Lo%r#0GXuT9p)-hQ@mtCF7s(P2t z*yMkm4bV>!c?q~dNytQ7r#etMr*g{*E5?Y3_KSrWL3mI!1vSUrDe(ztQU!6aC7>e< zjr4A_WPxkGE#^%iCZ4BJPu#etj+5cf~E|M#7Zzj8J}GcK~SP%^bT zR!4qlaBO+>QwGmljy=EGVeBYGVVM<(I~wbr1@#`p!HW8AhFzh#?hVswSbnd?S8|++ z55b0P8}EvOwmUt1ZuH?mVL=>BS+qGMvhwJy!)xsa+GN4*-W~-M?a$}mzkbJ?yj%BnaI(27YjtH(#=Zb?(gJa?BB=OD)imB7 zh!z|uqR75Q+%~=!DA2tWHh=3}0~6(=YJ_V9;$Ybdf7v{5rz3zlApC+PB`}U!`ODJA zQ!}6MdDUGJopE}i(7qMK{YP2idz5#J&7Dzxy|oY+6uKMuxnN z*qg#h$}akI9T`I!0CBLYnqVJNU;R|D6F*FzHX|SClcjHZ_x5?rdPTHrb3G-IAE2-x z4koQB8t32lI+#Ct74`X2ijRJyrm>DS%Pgo7M7#fvK|G`Y3Jc<3gT>6dOZU$2pRWOR zV`tg_b2lJk;O>;2#xpU$vXm;9x`lWG9>l?lfDzJt4Sy4tdbHnt|J~ZKsplTyt=%Sy}~4g-b1MGPkLg<7vbf0)-fB^{@o6T_3IBlz&h>@J2kM3gTc@rPCkl zSQ8^xiwQ)nC&u6AE@2GqysFLhVx3jTBjh~21yofK2a}fF>x(!d`b1CoE!ccEiqdMC z4voDnZq4sJk=^?&PZWT-8Ub;z!IEMq%jyh~3Y?j(Enb-E@MLWeCn0|LN+c6k!;@tr z60shFI9L(P)6pIm{xF=T5M!h&Df1<#7iO5~5n9_ckoVes654@S4?!F(TdFB427*}` z^skvsU(0m638tURtJ6Af%QmX6SnkK!mP674agenB&e;I%l#!)1mq{l)B4L$ch*!zL z=9ZYYp_Gd{e5M-ilEIBfPVMV^XUB=l+Guu zd(R&I>QTQg>Fp3Pyst{n=7+e=0&%cxRSjzE>Nbf?cXGa#i9S&wE~1T11!u5O*r^gXgD3LDp(A$t7i{rn3_;kEh% zsHz|iR@GK!O4EZ3yTaeRc-ihpiybT{WKyhGimXiY#fX2!8O181H}Ci( zfr&%@(^)Sr$3+GT3*ul!7^pL&eL{7pe8X>yYu7eH&L@tr?xjS``O)&!gn?V(h;+hTm(8(8BTADW; zn@of;E)E|A`w44S-~N5^S|OLl3IE7Bml(AyA7!YjAP!d5F(<|lVfC-{bDDFnv?QrI zkNIn}uj<8fjnrh$r;6$$rWX(glh!xsm(7e)%M+*p9F}wf_!kb@W(rzZ`6)?Gjcc+f z)bycjK^$zbJn&Er-+VNEwrAGb$DKgnq_DJB9CR`FcDih~TvJrjC=?dN!HO`u#!Tvh zZCr`{Er!>UqsCK8#wjcz5A~O-HAR3V#dl#SEQo_;d(Xa#VC??Vy3Gz+z9x}y^JR0j zx2L2yS>5y7A}dx{i1%Yb93-v3b2dOX9LUnTL_ORw6PqwkydgO8dgo;W!`&i5x!U>| zo)Lnw|SVGcl2MYeS1w;w1}A*l0j#G3knP3V1qTK+V?Y_=E_gThA>T@ zHuk9jK`lLsXPcL!9g1w1O5E;1VL=?M2-?NBt2L`%*KxQkh<<%r{5pz6XEFE{->95- z-0sBu+ao9}h=XN&>!T2(zX!>NQ05Mu=+HYmqFUjAf>Tq5ltU|P(LzieXx0L8khK2J z*#O;TBTI`seu(M4m3wN>v%`Si+hYk9`;He5@H(@46~X+) zkm9u&yb*(ZzDiy%r8yL?u;lP&B6OF}51^`oIGD8NkD7kgwlsKlG*La>G!l)v!^zgz zTUQbJ(MN%Y-u@6_n1DFgV0|kSqg&3?jV{vQ*fvGy%BUUjbW-^>{yNM-;+}rSvrH&k z5C2abze`6Ut{r0$ekeM_=2}6x%o+U8qzykhnRG`TJb2b9u&PK2+ z{gr#(z2D!TG7kyT$>&q_bF`XGzKjT97Z3I__SQe+rEP>`fv$8;{!ZRT8P))T*0UnfQ;Mugy6*e-UskN<5V> z9ZNY~>^ysuNX|w8PlL+HK%YxKz1CFTJAx}T{#_4#fNJSFW_l)Y4F zMN%NO!x`jg-`fv$8*Wm$3zARQUaSvblxtWQZAA(EPQ6B|f=!0cM0biF zGkW$Wk(`ZZbi(^08G1{&t0|U;hCgJF<{BJOThbQZZ`4kmt_LcgvC02B8=#*e@)D3_ zi(2Y;pUJO2Zrx6N?Zt_OF3ZSt@IApn++pItqd;e9_5*RS*)Qo1Pn`bc$RARCD_S^| z^_hYwDUS+DTeH){Q1|LY*HsZ=0UT@zFsf>1{v`eS&LN4x8y1!%Z&B_- zq7e#uDo|Ju2g?>cPUePa?ZsYU>kp*dk{!=K-&Uoc5!Tcbsu0;vG zTqR#86c)t6l=W!1^q9SP?)9mb+%bcbtHXB^vxOUIW9qAK^#n3ndK|(8tg$yJokgIgc9wY3mRI)-d^aSbw&GLb$z7q z3bbzpasN@)f7WfVvjJLJk)^fdUdBXgcMiwD_M0$9hZS*fvT=xs@F(2H6OJB{g@{j3 z_dy(Nu&5huO8qWn@|TaRqSet8Dyd`_FCN{dJhcq|boWA}QWO*x#KEMs6UO!tHRe)d zkSnFMSAwsffVsIQucLm`gjAWWc2jjI6c)t6vQ@kA^>f{a$H}He_CZpWp9X7DxU-bK z_36s9rfa$DOERFaAPy!izT4KOYJw!gN1LwLB+lUiq_qQBv6>0e#kdS%v^&~}2kAf@ zOj^|xB`J1$@xMO&B-rw;o7AYM|C%Xnf|Jf%)1|>Z<;Vz03&cUv`a5R>w1YyH)@sn* zJ_oibT^T+Z%NwkGn}_d?zq@y^)hzG`#-gufBD?|+2OF$*)YE%CSZY~T(@doL{#75G z4DJ2BA4+u<`qx<8i~U*y4Hk%lN$ccC^3mRpVaaYL7hBhew|)6{!d-2x2&^o#{k66n zl@S-ZAP$yo`2IoDy}tb}@%D{cTjL&aNhR)1yF+pP+|o;9KbbTU@5O;Qn6z>{YcLE7 z79FiyVneR4ae5p`mh9tA`6Sv9MOQt+s<{Rg0mQ+i^<}};UiATeYPa-LcWWEgiK)o8 zBBse;{&TWQD+%E;3CK+nG>{XyXC{jZ+8uLm>N_6dWdLnJ1z zE!6iK@LJaMLEQ&&u)+EywaxeXUdXAh1IA~37ysa|iNTMa1+e|v*Ci7xk7${L!h$%M zv^dGE_eg3aM=-+$&0QQ)i>RrMACEnMQzINvR>X>HQw@a$ajJ-v z!*4e>q#+*A0dX*C-9ia^e90y+mvO|RNAo&M2&IK zQ3k}pq;*NdD$MY{xG?Qorz?wAO@yg_HEJK|4sRD6tkiv@nka&@1#vKGU6@MYjwJHr z_;uQ4Kx;AQ`YZjz4yM}YV&9zi| z&yAeavG4v1Nd+`~R-GFq(Lpiz<;93>K^!dGPM;iFrv7R*iL67cjY1ke^U!fqUwZnE z={qAO#0}|`P_`frCM|)}u&UZeu|KK}UP&}1mC!yVzZdn=(%|s!)9CKNSRouJEQo_i zOXH5vj?KNVbD{J1O(aIH^%=6@A79+oN-3^njrzc^EzEpBwSkLh|cxb8e z2oLc{9f*Tv`}q(p+|451-O9WFN6T0JSA`D4(VKg5G4-sy<5jBqw?6+ZL)=&i;3Cc!AZp{46Xl}N?@ z8h)72tXn;c;{@m1e>)pE-5%0eBE{4(SM4HRN<^#M@2c%AC(9T&7@m&|>Z!Q%ulv!| zoTWXb@x~|idWy>}`ZA_CH>N0QG%OQ_9U@fns`8s*?`(h~|IgWIg*zLUJMp;Qrt>Lp zs;Pad%q7Pmy7Rj&^i!8O#pk>Lzr$vNen}lw)j|if!Ek_$tp7bvoD#mMf z@SMF#Bxl3=RN^}AbB(cJz8?vtm?;f0OHme1jUw8dBmuOf?K?lt-XxN<;rzmulPI)R zUR0f7V{$X1ug>GL+oh7|i~>3_QQsQ3fwMP>L~2JO|SF8*dL3V(psclIWc zoDFq{iuKvMmwcAoJU{7|RSaf`9XBXe__XqOJXe+tD@r?KlmB%#KtDy~C4fltp-|e2 zky}&kIML3X`7VVkE>5z-Yg#q}NBn!MkKCcz55&PHRhfRqiR^LO{xHo@k$A3rTC!X3 zQM9m|I2*J3p9iSVzJkJnIQSA^KW9|uqamR5$($AckxEzMcP4|r+*b)Pk;0~_c9SJg z*bx8+%hqS7hqL7bJ5QJ-GJ&4*dlW{eFMcb z+^!AUYu-3~Mt#Afxm4;mIVV(95CqW7shtze63So- zGLaR1jNY(lyIwDgdk68wP!I=G7W14QQT2zZqiG4=S0=*leHC0}is@3_vEl(;C zaef7Hu))ePG*NOetx7lr3+$Ol7hd>uJ~kNejfmr1kVjiaq|M zKz{W0eZc_jDVy1}tybMSH5*!^$@tTomGn?p5C_Y)M@Yraq?&m|z(t23)zrC5bBFN$ zeS7KbQl%eGGpW3?P*@NLlhz91YzqECX<|@cRbf1z)cLq8x>r8+F%s`{6KjeGa+6dCVf6+65a_BVSv+rBP@kQRW0r1f{s251L`EUiJC zYj21WB`Ab~iYZx7H#CThu=%)S1?+#Yb8SEHTSFM9APzQIFYqRLy_^G7PpM2BG@Uaq z=_H55z4Ix#R=lEClV@X%IAnl0n6&H{lmu)t#xH(t_Q|E3kIkk^cYQLc_oD7LP7=ME zNDCV@SRf9TZQ;Ee-kM>HbRKNqur*{XvS|j6Xfg06Gc`Q!%-!W@{m7fjCH7fA-<}&UschpMPsP#oWNCO}amTj}sjgzp*I(&(; z7>tzA9IXcqcX+o+{mKi)NIlh4Hz?U^0O;bRt5QmYz|2H%?H>#uJg(YLlpS+>MKAnvAK&#eP~Xa{^c`2tO9Y!3K+9^2+ea zRt(9~%+&gR#DfD3N_<6cFM;T(4<735q&S+820$E4TIUOAjMUTXf6;4GYwP<*s+kog z-~6@xtax2W1FI9K#vBR@;$Yc+(!LO3pe3CpnpsrS(9*{mz%0W}a z^GP5MCav$0isAA3*xDTD?ql85?Ge{eX}F9#@T{`&0+al?QES959>l?<<%Vt7e$yzx zPUQi6Y%sR#^mEj}9tFkwtQ)>Kxt!mRFF?`)agenB&e;H+e37M<+;fmM{-PnXB*8%b(BTu&lCO@#3bd$^I`wxP&_y655lb~Xf8wnuswqeYKC93MxB=u;?TSGnK(Hgt|-b{kJd%Y85Oj4yvUSd8m?{X;)R=8TuN5ppO(|LFh!_ni$lTFFCNQ^vi$ z2Oo(q{d{XSZD3iW_)#!O`2EZIpr+*6vp1;?Z*rft&7N&_{Y&p`#|`R-kCR*PDUcN% zzPIS@;|x+(oNqXLlcw+{71QWQPs_|JaCN%w2b8y9dph{5_Z3*VXY@|L5B#8Y_6v9v zZ+Mf28$Www%QPk(=VCLlRjg&ETKPQE?i>axmEOLn+K6@b_is>=;7!J>Ze|AaV8rV@ zezI>$c;CIdsM*L3tI_q(o!wSDuM1P{To@P^Zdy|1u@s;Vv~6f zcw%z5HEo%q zj)BKCH`$A5i^z;Bahg(~upkbWtwhf1$&Z`7LdUcTXAXcgFInBapQWCO3(@&PO^!qy`q`T(Kw#l zi;w7tryfBZEL#k$Z#{W;o}WHSaF8fl$mj{1zF7H?gNteNO%FBJD^JAP1;oLW<;CKf z_>pa`vg9U%(~aZ-%|?qB{sqhT*Y}dNTlxc>b)h1FIGC~&ZGGwqAFxJmkh=+XXT~X< z|CTa>7cY(*f{=5 zd33k0n_t&^jCs9Nf&=P4h=UE5_yu3lIkCUig zfWm?}n6z4#KYYe3F+sNHLv9EVecW3yuU*x0bt|NZ1APzQI z2SP8p%pMWg;)hl=USOry)ZaCLj(bE$kX^ z586e_xx2yyIy3hbEENNyYS-O%6|pNOb#ITq!-1p);vi}L*@uIr1==YiOKS~FP>{<1 z5Px&k=Kk1YYto;hf;VTzZP%}3_BqkVM65wSSP%yrtaT~-_Qm{$B1Jkg2S&Ahshgr3 zvF2V8su}`jof}`SBVGvuaWH8)X7&nPKxOHl$V+E8;n^bLwkn+#Pvaw`pRYncWR)?6 zvITLlY*ox6(tTPgd>5|eY2d$P&BYw^vD-`UnB!R#y_8}sk_LqZaWHAUb82eB-)VR9 zo%6#X7&2m5eRyI`MXBUCO=Q!PP`P~-3Jc<3(sC^BNxzX0Z6Y6~@g8f{hY2N~@s#Xc z;_YV{?Ms;jZIO_)KpZ5kKj$s5v_K~!WND4OG*Eh|23zG=4PSfaP7rDwZdLj0ZU?KRX>&?0BrOmJN$bzK z8Z0f)$ro8#cOxGkx%vG}owB?!Q+7E`v8YZz-C5sga8DrFkcsX(!ZZVMu)*5bBWv3G zzEZ9*?Dnq7Ry%m*B&R)1+#c-vypi0H;PEkquO$=~#KENXt!X}yT*oR> zXh}6f%RqTPvo3$-8C^K<%pzv@)#z>wNLnBclGdO56R@;EHyp^)YR%+WD0V8f!h9lw z{&bf0hERt6+i#{>IR<4_Mi1p3J)!P{IM`r0?my!EB7E~Y_VXjQ0*ZWxo0Uu1@1yO$ z+!@%!5LLc8421=8FlpgshI1!>SRo3!ZJTeeo{J%NzLy{CJ;%h`>tV#^zW-EJ57i}W z=^P!1gJt{GJz+eE=ALbQ)7r}UcTD8I7h)?}6GG3k^@Wyra_`zgVL=>BS_GkQPM>s* zO1@{|5V|@#JL3^Tk1jicN84|amwx=g74c3Kh=WP%?b=MrXqR}v%=lQIUQ?_at~B9q zzC|UAy0409OE=UAAZdX(NLqjH$-vSA-DM+7t4glT2}>5uG|M`I1}A|oS!{(n{Dnne za}i_Yg@_S4UZ|=d4mMb}YxX72`f+1}12)1}ZTS>Q8>hl&8=VA6W*DKPZKLrX1Vsw?k&y(FsoqTA*PPipc) z|65`mTvTm@v+oVq9y*H z$vBr)&E3ML+GZO#tR>fFl3T*^q`i8yu!5_@s&-!Y^76#am96MD#vjVK~le`6+pMbWDI? z(d>+uw*P!*1IgK#rd4O7ifJ|Uq{QUj>NEZL4kyEQM^)5o=*14X6`CW%*_%XiHd2z( zKg@p2A^wgg@$$$dn|38gu&l6`mTh{#K2h!N{JFC?iR5fFFzdev-qVmU_;h8nqEK|z z&WcoC%#)j0qYZWY!GZkV*_%XiHX0dOUebGuCRve{+$B7XY9#YK4ltN0$*7**{XB|& zxN`O;k(`Z=H-1l5_;#$6sin0HUD>XQMXJ4%868qTs(4(DIy4=4_9l^>4VhM^AXlZ= zBpV)qoIdzn6zgAISaJvBZ9J)C<93A0AD_KRBxi%IjhD(I)?-`%i^{=#!M`@t^t?Ik z`SnK4Ec623-3Mp=8vK8q4bV>!c?syMbDa%tXrv*J5luSQrqoxI54RUA|NgGP_&&Ks zv9mZd`++#vq*JndeQhy^N`?3k0EmMv0k+*I z^f^Z)L6%J=dE1rezYlyzUn;spZp>$UE2mqNAMvLHAP$x-ud1Ngwex9%Pk32wpVvIt zG1_UY7GmzoS&q<;IN%y@$g+$AXshT(Vt_gUibCfOnJY~HyXu^2RR!c?w=z3 zaW?+U=ito+XvRfW77?jXFY~W`s*ka?jLXxKfe&P~>R7KiKl|b!IsZW08?h4vaj>!4 z-0J9`^2A7yUHdUv^|*OD_x8IZgTgpW@&IAK5b9FI^ZFnTHdb@A_U%l!Qn$bS6!NX6 zRP`S|(!=0?-#vQ8cO^$R!r(q60T2hvwkc>*Kb5Ft-${GsmH(xSD97h?7A>8;C&HKJ zdv$da5HDANI9RsHi-s(whp(a-uH1{8Nj6EpT<|4Q;u*t}6axj{nU(EEC|eK*Q&vc# z;IZ-KXLtLw+%SKhC6Wsk46P->>BsLb&T*I?P$K?(1H}DDS^rtL!O{Y)tjN-;(bY40 z`&5*S-$r@Oj%(l4viKImuY_GeuJ*0rdH>_LQ1?L`Y_JS&UM{Cb)lw-|il=!+e(@7F zO=8KHx?cbG8AGrid zwjp-eAP$yoPth}%A8Hq@w&Xa|KXEqCM4$U^$Y7g$PKFT3xnWio`S%$h5t zT_bQKUgG$ny$*FB#K8uuvX(6_+KY{l4~3k&SBqc#w_?Yhg{1{kj2y8J2Ezr!dpjTw zHduqgxAxP$Z~q7+mETo%E;0(lzY2io~GMt=eBh5{@_MHU-b2R(SsbNgu+>ugeN!<<~fLi4OaVcN^`Ds z@VFdRO(u%M0MEH$;+aj{A%k)MdUI{hc!U)R;$VXnx>-X)D~O zAP$zTT>SIhHol9E#>`?DjfFbbaIp29wD0})XV5a5#jsvwfx?0~n6zY*d_;;K6%yRN zVcL253E@$9QyMoqMdW(!(d<6QRw3du&mazx*55fBpc4_Yw1Upz{`z`8jq9Vtr0ABn z`uE45{B~L7%ftt+ag_JUSt~=`2XU~$il`CzbWL!zZy-DE>-E8Ywi63teQZ`KGr|+IZVL==$TM-he zFISm#Cc341cdiXsy??6}zy1;p#k_gq3fKH6X~av6AP$yoN;4+yMWzcs$e*jAeV&R5 zNm$kkOL0x&bn%Q*sV(V2_^}`kCM~=iH+r|9n_cvGC6#C|VBwy8!m~@{xe>-oXh$;@ z_R|KMwLlyst-o_NKqp^hX$jkqN~D`$5skV|xY^oe5$7mWFFrByKE%7Zs7-k1vk^2{ zAPzQIJ2SEvx^Gu8eFOC3oKLa^vX|D$<2>!7FV|Q^`dH5)&Yd6*HdxNT44z*8Wk`m8 zd)GofmHSgFljGwH<^AMLaZD)`SU62kwjd6ct!>C#o`|e`rHLm&*t#r7zZR|#`G(fw zqt4y`$s60^gAauTaj0CfH6c)t6 zr1jv^I=ZHg;PF(~j%SWiM#HAM`iN;sbjej=bkiUDy`+$|KpZ5kzjHP~Hyp^)B9p7U z(!BVpkw1e}236lxw~v10j&Iql;54@B3dM|c zE8@8)5C_Y)xGUriDM2Lo$3uZ4SyFPn@B!AP$zTdcq+_P}n+Y&jr3? z%Zep5Q)VrPi54;0mheNBZqH#kC@hGBWy|%xO01f#GmqEkGZ7i5AxB2%!MNG^o#fVY z4_*+dy^=*Z8C()rp7d{L<7L7dr|L_6 z*4Aw;@q${+*SDlxzxb1*DtsRH(>4CC`S0&bsC{%!a}&v|7=9&yzVWRuJ$e5#`yE?8 zS9I<^ZX5KQZ(+^`DDwZDjX=1wK^fp@nLL!-8gJ3ww}T$A9j`X;cOWw|bs=W&)AF^H z)id7{2O>EeR|-Dx6MR;ZF=h1&kl3ubnDOwW*?jcY%@ilu67$&9uCq6Z~(nq&cES#xD(Ux+PZ0yG1%qAX@w6;BalSs}+>`dXS1Z5OTl)G~wa&GKxx_+7R zE%!I>ouJ;wNqTIvcJ?NboDDUV#mVbe_#cwpb1g-6b*~K&bBk!vCsWoYNZzyZ$2vHB zlSs~nJk9d2hf2$IrN{NO(d!&$y^m~a>QN}tZRiIA{3-V9&)y`GvvFg~Sp7iS%wIpp zr!eK3|8Xus%8ven-qYobJB~?L)h*B7B$BgX+UuKvOEZ+Ge@^v%Wba!ka;qC&uZ;`K zGWDOQSOrX7IAfFlbv8ghMdT$w@2Mnfx=xnFv{3^3_py32udzJV+p!dZ9rM0=KY5Aq zpxF<^!6sGjf}WwD6jXVJgnBn=bxAAyAM!6R5lW=x1-$jtLm5Y$i9j4|Qg!{IBAoen zBAs__{E^ul--d#%zKDHnkAZ@WK-U8Oq7IZTh=XOT$mi*(^;KA@(6u!#+k#kvV5MH> z(C8LTuHS2;S9vcHhZhhB+XmoJm7&hcuP^zwKm3Y0H=Atgg){MGByg9iJ|eDApRxkV z7R15A-s^rDP039de2!fFN7OHKoQq|-G5p_O(yNCLCdL}jvp~)Ui2J9uf1Hg!^EvqX z88qV}D=T?H>~?tf1qzlUXJhSooMx{D3z4sjOu=dC@gy={Qdm&$K^$zX>Kz($6!xpe z^wcr$S=Ezm)w-1C|9_;N1z1+w7Ov^;F6l;8q(nMIq`L(H0SW1p4y6$gr8`9dB?Y8I zx*Mbfkxo%S;ku7|{RbEK-lfO=>}R`$_y6XYV}AddbIq|rq4vAiC{^Ibu(iJ-APdET zob_I}^;Q3Es&4it7OQZ!2R`M&$j$clC(EQ?a6(AntoHy}C=L`YbAb0NHm%PhV*`w} z>cuJ5Ji6HQDYm*Mx-{l8C$YthfGiXTvH&xiw#MQ$#RX2o5C63olH9Lu=eqc|O4rCRI16j-SW%-w3F`K&_z8TTA-uCFeZ*D^w%j zmYD4e2Z*m1v_23}#pk^y(e>KuBK;uX0VobMSe9h&=)0Rf&vEXkpkV0+Mb{cu=|>yDN_lAu>$>52QbHZmn#oK9fup~fnAlZ^xHouUG%@1U}(QXkvswdIO{ShM6QEx~B zW2X(@BEhT$#R1m(y~+mEO8LCCJhKJA_c%(WE|SfRKWFiZ*S)UglSO?ylx4|3tTB!o z1;{EC2O6w}#Eu{y-V8GXw8ICY6CeH2+9~f^k*RuG`kc~va4N$dcnHOTtflNe57!

Bvaq)MsHy4U!e4NbMK*F@>Euci-m%oiY9C=L`YQ$zZIitwY^NCobD&9+oa7(?aJ zCLv0sVp&yq6NSRCS{xJy^1zIoGM$PZ5qdr=2eHRtnteR*ewrL+X;RF;J14k_=4zSkmRW_i`h|XKf zU&vTkk+wdz*Q@+#IhDXO%=D*UN5w}7VhC-b2Po^jf$l?bputk`>1nBGt;VeNjuHF% z>g2=C&t<902%quIuOP}Ai9LY5C=QAPSu3X})aH2%wxEu_c`V^`!-`b+fG?GJ9jvBe zTGuXL?vVweh2lWbN)fVslR?v}Cn_Wzv5-m_kmU`{-|$cQ`tI_97>NKA>~3Bt4&;H2 zCd>#K%T}_7x(>#Zi#+4kOmfFM9ycI&qY?87iL%1xStt%9TXi=dUY5C&H0a`&OK3qA z*|t-Qy5h@KoFXTY1HL_+E`YV5IKW!JSJ{9%^F42^&JPz{+N?82>fMo@7sxHTaMoG1 z??0?^y%**|)N@T1c6k;Q2O6xmPP4HM!dv)Hjoy6FG0)RVEZ?Gew|v6TI(ULTYtB(Q#^o<(7~!?9>9WGBSyfNrT}EeZWa^Sz=y5y=EA>Nh zfVFn3ZMt<;6sHTru=N}3NpD4|Q29iJW`l}|+1uBQUBP#nlw(yztouW`~zJB59}s~Y#x zLI1Wqx6(DQeFIT=rVk?DVI@8&4ixQ0URE}3alQ*thU>)25{}#})vqdwF3U_;=Q?(_ z;dE32(L!+`4=CQVD3N=E|C!P9-THFDQ#*snK9i78itG5IvL@O}ah8BA6bF($vT9N_ znZDUeNiuDD$==-Bp=oE$(ILcd`eaVI_M7E<;HU+~0oMAx$_CV4_IYceBxXD`XW`4j z(p`QyY6q98Q%f-(96S(yX9q*mhGDh==spw&8m#Wl;W4=PE$X?+?Bvga5!c>2D__!O zugntd8SLg3(a{2Ap*WDW7~_4YTRcjF&#sB_p#Gu03JpT5Xp#I1AY@DlXY$n!gMk-@oqCWDS_2Gy2SvDt2-5)Jq za2Wq!7l)sp@%K%ht88H9E46+pPYISbpU*dT3^2nQmmX6Z@w}DO@jj{H_2rtsZ}MDa z1GmPn`{hF)2HrjfS7w>d8>4MAI#}!M^LqH}S=~xfn1A2oxylA&f|TC-smoCg1y%%^ z7|-zSiAlUQLiuO{PrW*X#EdTgeUs-Z8|4^twsy*I;*tBx`@{uE47Ic32kUOTstLXA zI%+C>Z}ay}o~vwVlSrm+)0PRXqgq!Ce#U?K`o6cCybQvef#Wlt<7B+|f8XS}%0}>l zESq{97BA&q&j14I?RavkP5FmqH%L&#)><}1=05#plmAvWpuURdr+{{`&qb81Ju=6o z9-d4E*^(NcSL3=l+3;+~D@^@9b-=EpgyKL4)t6Ly{*qQNW8r56=SC%JDU!81j9<|0UsrmUu`@Jr6s)VC$kx*XJ9&ZP zK+(SIEJ4@*lsc}SqT4uZQ;8pj2S-m9Q!Kb@iVko7n&$|Z0-!k1t((go)~r`#g~B!O z-0^Y9B8+HzlYe7~Cngd9oh~H_~4) z8=Y`WF*ZS;q@LI=Y0~l8)~hw4x>OcwOf=`<|e? zs8Xz;+7~o%z-|hL;y`2dw8e6(UT1xpx3lUHY-OXv6i?r{q^YTC*6zE}MYj zKuN`4qWXr(Ju%pHtDzNvqdIY(xqM48oqbD>{OZ9I->EIY0#F<%+OHdEJ;rqPhHx0y zFZUUm^Ql}PN_lH%$T)cASu$zgCD`SOP#nlvi!yRXhG{ardS#}Lg5o*ttewGaqZAJp z=q2}GyYyDnj18N0z-ddXM>o=$f z(&xnKt~*74a4BnY*NGlU-z$>Fo$uxk?4|*_55<87YjN%UagIZk_XtvBQ6Lrjrb}>d zxcLrN7cnDA4O{PZ*hvNy2g)jS*G5kz?MixA_TJW~A-~vnp52vtF9gL?WNg0HS|X(Z z(L!;cXsbwaeV0Cz33jqvR1sil&h{8s%(2kaKn~%o=D79}9(L4%;y~8Q#^Z1L_T4*T zro-~dXW`qo(|y!^GZU2we5A)Foq`NuH?BZ&pd@IyQs%3o#8IatG_xectjsutReQdHAC1 zt#8Tq_+b+w6bBkCg*SW?s1+KK->6Pe1aMttgP40u_M}tlw`dvWO&>7k0v>?kKv{jM z(rYXGDRIV1ufl;br-OJ z7L11E1-ZsFx5sN_@HzWhVbMZyAZv95`PrMe%;d7(XbgWW>q7m_$LmrFyVmf9DtRg4 zt8ZbKCqi+cBoL0&@4<IHlsYzpb_F)f`gyKL+5LeD@i7?l={&A9b zno)9vT66M^uJHNaX>9AK^At874>`JT5HZ!1#$9`|R~k!5UT>6USC zeG3aR1unWTMfkhC=IsN5KvtnR&|r-ivd4@KW-m=5t?l#O`rfUm=smukG%$=;7>9*L z5Cglg9*P5HH7kfK@X~I_bw@oLtFH$IMg1`oJI+^)lTa<~PpX(ANZxWuluNWaqp ztOdmZ*80852GkD6d26|n^pEBxMoAWS$?7g(L?!A+20ZJ}vC>y)Mt1Z;9)Vp=2E~B} z>&S;Qron+CepO&7r;xB@+wvCG^T!L;x6+2X>676}U_~=14wO|Vde#^mPWuZl`6B2x z9J9i*&~&J2R_uiRq|ylq7P(|6bG_ao!M)KtY@}%dDTTO47*(4y?uopI?^=A<*CK`}cC=N7O*6m1T*HboYSlOo5pHgNg~VoG{aS@1p8nzC$F{3h3TV*nd;T{{;^A zo((7t6fNUWA^qTImfh3fEsKh(MXhvw!i0XsFFu8D_Zlr-r(uOtC=O(;)EQa+%;WEQEySy+-noagvyFB^HP=5`mvH_+1-^vD@9O$JN?{k$1a(ze8NEz>O zF+H;*(W$iPrO=t8-ylTo6o0IRbtb6)t+D~f|6_;bm-lSEoiuruc!`aB>-bLhj3z6- zl;4Yb4rxM}r$n_6F=8`t|Gr5zaFdLuZTA|Q1U{SDd z4W-9l(m7II#Q2tC#EUimeUqi&CMiiKJs;!BvFIpzIZMTb*3%QwFB)q07iE%XeJBeQ zFZlZ=>%mRl;Nm>$!Ig8-OwGmprX!+gbpL2ePvkbQA$oZBNI=b_zijf~$_CU|@%$7( zfy3CLo!sCR+59p(mpe<}Tgyi{_7e&vceGw^$AuNxBkQ3!&_R_Ui~bJFNj5{H;gM_z zrfNCuZGZBdf@kfwN}k3(eDAIe%q&nGXbR9EL|O^dSA1>sL1d6lESr^wOdpqQy&wdI zuJTVTQ7TivI3%eT-iVF}I zJNwwEQCcje>Yk-JTkn-r$*%YN$BWX^@`YS=QSKoX!hP!br)TuVG$$;QK9 ze2>x-LAo37D(xWtiM2tcDp>OE!SpkrvH`{Ykp0hlHhvt>p|_u*j=1NYWwTc@*7+!o zFg=Cq7F>0cj?waRL;aYi;+L0`G?Paq_CW8UIM7&O)Y5VfB7dUtnt7jOn=5#8Vfne? zjC-W-wgz=9U%Se8Ko*JvC3UAS7$eS|_J-C+mKwIa0ItCR(Ko$K$79SIv`rbo=&+R_ z6bFhn+;MdK#F+j;x>|5T`?kSoSC4Y|9oO$dx9AJ8-HN)ifM}sOkh5sDJ&c>;-p~@C z2u-84n|{QPi8tYHxy)$Jq(_Chl<*mlh2lU-@MW?`zM#4AE^?nGPISKTfP*D>%z8AM zW_;{JqCvR4H?VGn;(j_y>Ob=~w6&mS*7MdvZZ6_|fJ$;L%8F*lsNAT9hMTuIO3ubf ziGd4mzlaBR?gGVu1}hiILXKzxm2imJ;zfET{|(F3x8(GQW`XR-sAamzv#_h5p*T=h zrG&LwlnKO3((gOki>xCmKB*>s6UR@TUs<0z%ptl0yDAHc14T=Mr&30ELSS!uTrpMD zM(XDw>vf`A|1x`2GP~(!4won30Vob+EgQ2O!=%K4YVTs*9Hx~18<*vdGbbhzFR!3! zS-M}tst07DI8YJ>LQ_SH7uJ+=@xAV367DJaT~7Zrx@VcmS4b5?S=S{CSPO~+to6_O z4BA>yE2#6<%F}fc)wXFvXLP}Tt^r@xMyl9-+}8_HgrT^bxA#2hvHVz-kPqLm z0J2aV$XZWQt|hw+-klDbxi5E2OfH_VN4Mg7S{_akJW-l+}6_r(J@pE}Rrqe#2|0H9%IOI8avGteoTm zr}9wh1530SXmjI-XpPW~lof0!h1ni_GkOOH$U!0&#Xlp^8 z`JT5{OIfxQm3XB9wSWJWT_xGtw9;1R{8v=ql%8!@AP`sU5Z4hg0o+*BIn1^S9MEe?6=7r)w z*196?I`btxvGrQ%dn7V6T$B3wQJHwR8(f8~UvaQd_hBdBP#h=;A-*SX_U8n$%kxFv z%GR;lqm=nBM2cBh*f%rIa?c&*0@i}!0BilTe*$eSs2z^;)}rc)V(560co$J$;GJBm z1EU0Xy;E?T1-Y|0m-!6d5$wSZP#kEmoTCzkR_JWXo`$BYn|0pVW#bstd!_Z@^~Tcv zU^OuY?79pn4wO|h1`ls}VhlCsxORQpZ@6w5+cQ(Lv{IELoc5Exfm6YNwV*gqv@%9^ zi{(krubj;Be=jPSKy6oV%T#(-J|oB+hWvQ)X8Vj2kiE+&2lIXlmzuhD9Wpngm3AC6HzhWwqM%r2jK~8S`7l{$b=7Cey3-hSfCr#BP*&d?U~Uy3 z2<^|cavG)HGZ6dGX@j7QkSL@?(n|lB`#=+j_DULUF6U%P{B- zdTKfgUYBjz_UGM289)|_16gar6)t+LmCtuysk?)^-h!~;Y5|?;bVXQqi&CGj>TV#c zvN8NN-h219YSGCJ7K{qrN@qI1TE(UNRcXm>p?Kr|nbK$X%->})?~_QZ%Lr|9O24k# zQ@}mdxaAI?QZ7Q@Xv=u9e)G)!sF9PR@y$_HlcLh(X?mZXQ~g)pw-GvW0vs$}^)0;A zi~&_Ppp^ex*$@L)HY8`_E?ruQU6t`?xHeF%!tP4cPLA5JVH#x7!f@GJxAsg>|J$Cw z*tyCEYZ%Im@8J8i4y8x;vAVo`uHMKr#-AD<%+2N1kfINE|NAD-RW>%|pS`PaXbB`q zT6ANUR1eL$xR)58+Se!Pr=@cJeUs-Z z8_A^TDZV3I5!SaezbUC!tO`3)I*e_!d2L}BdIreffKkgSm>1^xg2X;fn8DjSim9jzHBduz5y`xTNbZ7)AOykEPTAy)7? zwQS0jJ*i(cHzm5ndu@y&a$-3DN5 zHYg5sP~{KU6JpGJUf*1Y^dwsuu`S2e(pAx)R-!yTsW`ZNA9j5r6bG6DE_9uE>aAbd zKbUvPgHM^r4_xe2%5GiyOx!z(ksZ3F2OO)RI8e0Y6Fo}=o=Ist(o%i7vm@LiN}|i1 z)%CKM>8klQJyWLvStt%9>y>(ytkpxMxBEjx>_KDYOse>nin3L22Sei+v+;=?Y;^&} zf!^R$Qnzc@q@T>F?#Y#x`Pl{y^`5r+XOYLMn0IqFkw4V$1C4J!ieTl>3F26VU7ZQVfs*=`j_bljKl6w# zvK=D%RO7)3a>-~ay~o2c#0$%5kqx}SSV3_hXJz}+>xQMZwb_1OZlprykO<$6E34zZ zxlOz6hY%Uw2fKU_SmO8T{-8JZGHm}9iUT=op0S8z;<~BniTObNcjrfo{81LMXwdZNi)&NhM98LsjLW3I0pg6!-gTnI6a1 z2KE)vCM7qNytoe_!y zS*uob2%F#I;#f)JAq4@JZX1g%y0BikTWdrJr=)ARp=06~`p?;@Lcygcnx`Fl$Z#lwGlcz<~ zG82Wp_X4;f2@qtD^2aiaN++c$%<#HlR3= zwc31`q||ePIxdxOO+T$ zg4X6RX4kU@a&Pu-5NY zHlWUY&s)o)by$EFjpt;se}i}>>IM;$%SG=Z%cs#OE#3I!*C}D!V^AC@tJc1guli&L zv0v*PVn2G8?$nmx({obGMmZrxs7yb6krl`)6bG^v-zx!TFs+ju~wkU$)K+$FnFdJMu*u7maONWcHyMtCxq{*T&l2hGmj%@9oWdtjl zL2)42QcgNMZl_teyBt>yT#-6oxx3_TBAb{o-FOw1PlJLU0we*716hkQM_i>v-o^cS zE1drw0=X!eL&xOXC+#>d_gQaV;tz+tX9J1@to3`94X7QC^VT{*ZJple?R?nMX@%zY zK9#ccx&B36p3u4U%d{RkbrtbI_n|mYR>?}eQ{?Fp+uZuJ%{c_&aEHh}>apCTSQxRl z?uWVLbOW+b9LQSc6C!!_cVf|E(BhVGs>6meCL#}ZH4-=c?xts+STuJ6vQQi-TGB?m z3vCiJR&#ii*zf#W8e8(iu%3-xw`ZUQY z?==3(-lyNRcLofiD0IF6vQQkzS{0eWe1(g-L17i28LK{4zh)2W3Y|;Z2zPXAY~){k zoB$uds*jg(}caWc8XgouvQ^bW2_W%X)CKo*JvS?f7|^mq)Zpu=Sw z1vVOc)&0DvM$4#1CZYA|n0M(4Q{{jx6bFiSEw{g^5{G2jT==3%goSZdYLSvSRVVe% zCQ^_Mv-1gT{|Sl%$tq*ITriJYQ!)J9d$BZkgzKT=l!I+!Tc{UZa#SpF#s;ji@wR|0 zfySuf)uhn2>9s7O`;+dU_$BgMspz*}B=hQp8=S3dB$`jFh~OU3!-sB4*T?BUcPhV9 zvpt9Y$)~JOFv`vT%9;I9(cI5uC|>h{kCWi<$tOSU8i*O2JCJ_Scl}VLD|ORw7*yGS zQvPpc!xvoH2#?;sVjD-lwv@`ClYQtrds#jKzIH#Xq=LTak&J!+-@gLd_grPe=BD_; zEq(9gA__OmpholvKaS)QxYu^^6>Lt)*1>hIXY%mB6;piARW|CIrYI6RP(y@YQ)%vU zJ-ITef_IhGeDPR(M}IQ_OZ&p#H+inIk*_5yn^abZKZq$z{UrPYcHLfh;evK4UC@2A z9aR$ezu)usJy+T2_kXrmQBCCfqVr(_mXQV8W|0(oT=`1nc7fW&(=5a9f8Wb(oe=9_5xVOPguCOWU#YBd-Q~p6;IOFf{Oz}Ne*|@^3Kv|r+n;X37)nJEf zyNYvZTwwUnTv`Br>^nwk@7b@>|7f3cm5q-C*0v4F?D&pbTnCSotjxDlKQWd@cVR_% z3w96Z<}UnYV*jmdKz$X@PXQ@gTgxeZS5+^n#B`n}?PZ_#I$`0a6}n#!-+Ijb+}Q^> zs6ug|qu)S>UHgTh)hT#G2}IJ_`&2QWTjuk-y|uc+SDpq3dBQH(hvGnw_)8n|kD;<6 zjbh<{QZ%f2^R*x!hmWt8jgiI&Wd_9IkKsVHP+WjG{Mnl;WbMDV&Mc$iTHs*4WTJde z*|%6{Uo2Cl!G&G_6sh-xARr6Hfn-M`_t;}`SBCYRI&-*?f`mCnXgn$E=Q)!wTe19h z9x?&4P#kD=q4sq3vDR_V_eMeLy}SDr%2pa}=Hc@+Td1oyJ%-F$X@JTG6!&9#{m*+g zeyg$pb;LdItPKVD)TmdQNA0^T1GkCx7_9;q?Ca`j%_6F|`Plq<+kxIgaUf@jby|6C zd^P?0Ui_FV+JOf<)_Z38@`))PjYla@{ztxSKo*JvIg3Pfh=F8Jxhf5Dz3%Hh{tVPs z4h?#J9GuOMz7lz|jIdQU6bG`v2P{lG1KXQ>Ap|j}FUIXGBgioGGZ%E86O_HH$KOqB z0iuQCK(e1wxE2K_9h}H@#3UQ;wH7+$Z16h|D48~nmP$>PRlWpdp*WDU-o2I3Hj`AW zIu;T$d{xO$r)#UD<$|S^%pRjw$%dp?0FxCQy~9$pxHvKkhTBYc$F?b{4( z4MKtLLvbK$HJ&)!)6Y=HHooyD)vsGmBezwWF~njg5#8>#po#aA2_Or_fvhzep=f>4 zF7g(7zx`M;@8gOvUF>qTFWN6_^X}gJj69hS$U<=-52U;|J&IZV65%hjxhAQfnI@~f zt|8o;NYAAh?$|)}5cb$8C=MiRvLr6d{PqiZv~XDX`%dv=Vv`v5Qwr%?17#kuWRy-= z(F}?MSqlNX;7V5-u0X|rso(?2+A1l|RRq538RZz=FVR>|js$?Upg6!8GZUt>^?A9P#nlwohv&vZ^=vo6)G-t zq}*e=R&4Lns2OvLS!t*n|7hGq^;OI`xB5Tq6z)#I(mPjny3%hRziUY|C zX6X8IXnZE{c&Eg-gFNFadN;iEqsG#nEZM_?;uIIy?RHQc$Xcso)AIc#-W-I5yVjn} z<67Ri(FNI+V&7PbS|_!?`NCd?0L1~;`n}2q)EUuvYtis{9WDX&>Gwv=kUDC=O(;K_<2VHq{9Fq?dzg?EM+(T<8>^r(fqIA5MMT-2T!F zn+u>gkhO+R3F4=F(kmzT%p+OuPYfDjO{^ard#)T@5CT7^3=`j2~gA75K<=7?Vm(`SjSxiMR-D0lq2#d}PbmzI%wX4G~6^X$#6)pnQqkDG`EbAG4=0Jk_B)APdETtTl%zDCR3AdSk!&z8KM1 zR=lrsm=#ih6?ONC!}A=X9~XxH`^0)E4&;IPUaYPO;~oh^g(Mq)15r$tfz#>~yQX>M z!&aY}rmqQrEEETlRh~J*+-5&IotND%Z{TA~?A&+M3Ea*W8w+^GS2=%K4UmQ6K-R*w zUbbj(7$Ug-B>g~^nBj|#Zccm6tY-}EOW%;uD9bs(T2LHdt>3F`K<#jxw^rpiTd5Sf z`_@U+k7v_dws0Uc&tO8m6x*)NWYzk}h6MrWJ`@MC7D-`Wiqz}Ecj+rH-Tl)R7y}v` zVl{e<23{Up7>9K>z%~h>IFPku8Ms4Y;+}n}l8=o^O32}`6|6M~_w|RfFHv?K#OZ`t z3yK4IV3lt(ZRR!#KOs^ryz*MYblvWW(iif1LyT)i+{`$fD?k#UIFM`-=`%EB?fx_s z<3#T$rlD^+7i$MKZeD%p(WXdIu;&eX+8Y!HvX;_vHjf)guIht=mfR~TGPR#_o}x09N%w6!HwU(C1q$^=qlWtq_6%C z6_ntEIiFq0R{_-16eEJ zP_J@1@R|g0(%9 zTAqS4tg^v+vGp^msrY^-n6l&VQG?8KymYwzAQG4;SwavMBAP zI#T7B=6qFw(EM9lcHW;Zl6-4$_AA3e=8de;L3*3xG1inc%|Zv)~8J^yXhWPG<(PA-i4~=5WTVAu1QWjh3@JD%nf6qq4xynXRIO~y>ECuo7u4{_-qPk4A zDPwgc4|Qa(@V%P)P{ot;_q{w<*|_ca>3ho3juZU#B<%q9g`xTOr~CI^)S0&r-Q&|G zu^<0^ljkZMgRcZT z6ZJOAPDeB&Ku@1U5?3(7k}_2opOO&g`X>4JO`fZ4C^kQRaJ|M=_2ZT24_OuUw2o7x z1QL&(gwrGQJHKr+|E**Ht!zMj70*urDZ^I&vjR0&)(SpZ8B#N^_7d46V=8PvkcBlx{CgDz2I(4UWQb6AjoVYi>@kb0t<~bhO2MO#;+jsANo-6<= z8&KSjA^XpJHh!zJ0d>SZ@2p)yJDJ(kxtM`TMo(b~%xS>SB&bFaAkh9?KJR-bvbAXm2a(lpb_7g+lbKQ0_ z_Z#*$Gum*A)6Zag15g~uS>B8T5vxcCWpMdJDGj~gRk*{Y0ciZ(L5lx zP#nlvMnqGIa>P2r*)x|b65aL;(J4#BB!!hDF5b|&-W#4+0<2r1xS!6F`p>)#UD<$| zSJKKuW4{i$2cQWqIab7_;hV z1&76FPL&-e20M3w;y~7tVndkpGTOi66%*!kJZJcT*>d182X*ROoI9JgQSL@C5G@o3 zvert>lSUK-;jLr?O*vPFL%A-A>#6qhv^7U>w-(lh=Jx?vC=O(;PPHvo#V-%bniZ@L zk4kHw7At+o&*R3xU!5!#bzz>x1*`?d0oMAx$_CU5>b$i+kx9Inm7T+i4UfMtJ&W>a z=R=pqo$r-Tv=uuBv3wJHfbK(aAZwMzA|xcsw`eGbtBbUYNUN@VQe3G=yjmY#^yy(S zzwHqq3&nw~W!E{iZuMwjKa%QXG-~hB@<`Sb5~|JTpWSAaE-BR#!ETL&;y~6KP>a%1 z2`s3gt4Z0}XU8FZ9k)uuZ5nOYjdU!IB$5Z)a)9DM)-vz)rz;L?%@vCI?ulTh`K?-v z^SIQE;H{)f(PjH~nKU2?P#nlwR#>Ha$JCSfG8ATG1ZDHCQO9e6SE|;BzJ<{gDiI0{ z1J;7#0BikTWdmxZeBN3yWY%Am3YOkmu+&&|nFdzQ>5wRA5$~_3T#Mx*Nfm*Wp`bXB zwNj;fJZ&;FYDfe05B;seA8u4V^PMg@oj#<);aE~^eGFt3iUV1zK8Cfu8L`?_(-*G2T# zE;{;b*5<=5tcT)2)|%~%xZy1Yzk2;yp{xRW*4C%v9AtOAnBK=Z1(Nohe6v6jpg54V z4y62xu!x)ugfk|O2ut-aNVaKLW1evl)HVc~-r5s{U7`xb0oMAx$_CUK(Rphry_8Ns z%*PCNiyX;vm**|VM8Up+(Qiqvf)@nQ?p5hRQH+n7LL}kfV9nLiN)t6+OuG694 zCUfoU9oSP@NEu?W9(s4o=bSN5^;I?D*ypJ*~CGq&Z&!>PDYQS1h9AK^A zt875+aGbXmF`NU5ljJR@(NJ8ZAa-i2p`wdnci(d!zUO;WA)XutyOIcs16gZN*RA_u zJEejYqT|FZN>|b5gN1iHCHNhv7 z_af0Gncszd!6APtVs#bp02BwZR*rZ@jDdUQT9|uCk68dSe9%2-Mb#rgnnQ`umm&7D zunQBQIFPlnx)6OdukoWif4S@FnDfou$h%zCP9Gk7H2BUl)(3>hK(tUC$Xe?6U8(Mh z8KexurC+t%wzzgDvUlz!xle9txiP|o}T2LHdt>3F`K<#Cpx0d5KvpoMiZ!aEx zsla-;*nvJ7#u=}S8YX0`giD&2#tne(LvbK$wTP}&_MU!M*+tN|51)+f@R$-PVz^Cr zjUcqU$CddNZ1n=gfvmM0U~Z9VXuMK+g{`nU3={{l zmKe)|giUK|kgfB(3;Z1$HLOyWag?3vcid93~% zfgIK4<|=3Yslxl2N7x~r!dk80eW~F!;IPdstS@t2HZn1>b+&Q(9ALdW_f!VZ7h8?KDukujN_(KZR}|kL}!!;=*6+3{qyP0ibj@BhlhBqqRiPxHyIhE z*#{ApvJ80cRxvXDtDD9}!L-j!kdH<}Qkb1Kf{)DB$i0pS`n# z?8LI$C@aefi(g3gdStGgAIENIlB%zARI=MJnnoy=`OntX5*TIrJXv|3sTJSL`{WuS z>MCZ3VtuLng#_vs&#n2)Aip22!~bY&!z|_ESe?1gKW)LC*|mmh=igpd`Pj6@zu$$U ztRsBAP#A!)T&5t4TZAeSL{WuZ>YCK>|LLV_<=}KX*5Kkl4gYB`b$|A9@!&F2)Z9*5 z&s}6PRdHrzrZ`f+>QT>q+=Lp=N4EKbe%f4!vE`BKl0xxc-(RaxqJL6_ewSS;eUtYD z*R9r7!)%?eo`gQ0?JeO8nm0%Im~`9SnC?HN=~=XHeJyy<%V?%;Asx2b0ynWg8$zwi z(!RF;B3gnn?*G%V9NV0#DDs^4e>(OjDZTQ;StLKb!qLgW{r~<` zU-+M~x?C)uk^StWG>j-gf+?BeqU`kqrIOis@Yz}?)!tQ(M*$LW%4deQ0bU}IC8q)( zjnOFY%X{CoRkx*<`wg}5T~7j+wAGij&!A8);QJW>l%l^B?T=LWWf*b!`Afj5h_U|Y zDZ$S^IXc-o7@3>e*}9rKod3zrzdcmq671L3EUVAfW+A&^U~^mJ0Dk#9A04gfp2erV z8HU$UH{sy_X#(!Qe`1RL{QJ*8W#I?|;g)@W{s^A-=bt}+CFZY928S?h{m;+9|7gJ> zHh_Qj&)*Rd&m_sLn99pF(mVVbw=rkHO`Z$#1@9i{^4ywx!ePz+3hTiz9PJqx_lYmp zf{V}PzaN7N=*-7f&U_3is52k?^v}oKe%cpmO#G#M z;eW*WpDg?nr@u7Zoqt?#^Iv!T`-@NiuV1X3`^CRgP+i`^GXE@#lD|ZQ5mH#}(^#je zk2$oVW7WTQAQSLBG4kP@GJ8!j`%tT5iG&?v= zmP8BP!`f@z2w8gRKE=}yv|$hd)Pf)yo(puFzoT2R0mG%UmfcoI;#KZ{5n0Kk-Butf)Rz2 zu^iJuCgGD4R`25&oXU0wp;|?Io3}5EybxegCXiCIH=No^Gpa1x-@b}1i}QTK7U5>y zzjahHxSTyk=^Bp#lQM^t8VI|Uc|Wr9-3Sv3>$0@lc-uplQC?dCvcwZVh9`=XW^iI; zzb5CuX}bC|+fXU;^Z#*dLqh)XA7u1@=O?IC{4+l#41MjDZR5bSpeio1HGB}_Yfy)) zQbN}fi`ZEw_0)oUgDiHQk`oRe@za-%NG-A3>_4M{O3jb?-Q-`FL3RCy%kUBF$V*7m ziz2;kH*N1|wlh9?wEpp5mmN(Sbu3OsOb1YFo#el+&K5Yec_OmFJ<`Cutg@K+(JSK2 z*ei?`)eZFzEz~{EZahK=?G;HVG?u4*OOqYD+#KyhruWMW!AQs+7<#@F|2pDU9{MHH zD}^tF!{4SmNO@Q{Y3%#7HJ2ST__b}nNv?6pcf5BgSUAZTC-&mo19zb{IfQ`ySSB&Up92b&x)2GtyFdQ*JPr7jP?Vd+4e| zVrGQW%1C^0*xZo}$US$#BO{!xiip*OvTb4X<85r_W3S?t-j@hq6NxvimxmrqEV-M`NTax!>cuyQp=4B)EEgkLFO}3_6w= zLxlYz8conz;ZoK$u0okTFVz69)218Sdu9)UmBY@ULYI-*kQ2qWTGqDv((@bi?=73P z7^c67V_<7r>WtT(JcEj|f2236LKdAw*6+%`Q*)a=Raru`2~$lMquQW~gy8J6ZR?CA zk%l@%_+ERlTyct%EPLA zbG7$x&^kn zGw7o?Z*VV~eF$fyC*|TfaE|onn6v0f(&{x?yTfo5O;PukQbJc`jk3z3qb{GDl7ZVPf6yFOQzkkkcNlR43 zWZ)1EWdAuH--1KnpbGQ-&;P;qIx+TIG!xx|L#&Q!(CV6T2!sDse@fx8h1lsunr)ut zED9}c(HM9PT69d$@BZdF^X(p`N`CNvsE_cg{yolpmjZOHCS`bIUPs%GG-)RZ-=}+D zy)ZcXW=*RWwaw_l8^bTZFPIdoVuxVPdEnmC#u{hauHNy^aVD+)iPKh#J5RiBsh#b_o2 zzd@aZ@SqnLW3eH;V5LmC90dwqsPN-MK`lSzKh#J0RsV{t0a8`x-LJ%)3jV8c$nQvz zJnt$$LQD>?PSHwkpG5UT`9powU-c>L^=sIO4t%)Ljktr;2{9$Tl_zA0UkRX9`derC zQV95={+T}TWq~@b{QR<@{Thtjmz0J$?^|qNSc6Ke?nPQUbPB|r2()W`T$|L|q9 z8A|(CTl!wOq&r*}7vQFvNFJ6_mQou#5*!tWnE7G+p+4rX`tdA*r$pKl`EXr)_z2eH zmS5RoE=(%XF>x!RP#QQ+3;SXIp+45H`s$$!DoFaJwQte0YE#ft-5y{@x1NgLs^9t6 zp}cTNMem38hx*vR>aV5f_9M02v?zJgsj5gz^faKyI+fC1=-HcvMm~JCbX-5|Kh($h zRey7BLMq8)*hrIVQLOm$a9kdKm(CUMOIqS#j_)vvKj`}5{GmSXulhRb#g@JX@kBD6 z33#TL!t^QynjYl~>~KX_J=`9+t4{5Q`)B&Vmj!BO{rP3V`!$&7$IsOtU~se7;M_qb zXlcM5YmjE9&bVP1SVUCp{WR}_AKsq@6Ndk*K9zz_c^wbF^`RDb0F`GE_a%G3xO=L7 zWUM}@1nYaDn11+ws88^#eimKC!@9Ng?zVOED`a~dkBC=P$F`QJ^upbcPK@dNY5WNO zP@nKu{YMSY4ph~{US;3f-=|r-*YNhG07dhyl)>A^A3LPXZT0;K|4^UkSAEf-&MiM` zrRF`?flbG_?{GmSSulo3p)Smh#^(L=MqMK#BeV}+viHR@bs$7NY(dk3d&+AwHNdHW~=HF!) z)Y|grmxYY%*JwU+O1C&>P34u|zdvmpB;ww2~R%Un2;-vQ@BmdJDD1L2WV@_@DD4|`- zXB{-xITOA)Ua5Ql^}WW&F|{T4njG z{0r_R8ePKP3~Hw*E6jdmlz-X+)vqn+c9vUBp{Xrzm_%z=6tK(7?s2u};eH{|c}m;% zYMSbb9~sr3ws7Is7GAzME|)sO&`K;c(C3V=R9DmQ%iMqOL-XXtbMlFD)k}V47yh&b z>R(%UZ25WY0VZ`KQSak-cJjjxtP>el%eLtKR|QO@dvdHz{m7{Qv;~@9TR1MpoW4yN zhhl#dtvmXN_3I^9gV@&?s}iqc?*AXk?lP*1?)@7$jdX}`04V|K?vj!&>F#c%L*gKv zq7sr4(t=0|NC_g{p-4z8lF}gjhxOe2mh-$}&HIth#c{rS_P+L>Lj*7E!W<5xf+k#t z=cd)xf8n79mlL!sI7Pbf0V{ivKkc}I7&eQer+&~Ev1cn(;-xn95cp4{hE`52j0P-B z2)&K?tWF#ENI?vxTYSUdw88jr@01BE_-~TMvmrV>IE)6Wa1Si-1^;M0F8l3p;BSK^ z$Iq2g^pP;xMQ^8`OS4zRU_4M34!Z|cpal!Zn405i514l0-Z(vc-^#t0Fmk1a1JxJt ztkfS&2pa0bVYE;MI zRbU1SzPPTxgNOVWfp1JnI*HlZ<4L|Zvm3*@r=mTr^RT*22@Ye1DzJcs+srL*i!u@g zSbiBEkx7gNPd=JlcwdUcwqwE`cp$b*1BbCd6U(ek|rl8$M5;zW$bS(hX_gE&D!QCjN^ zhp|Bw*ug^lkNN0P%Arr(Ma-3YRfu|*pH}k!7Cj@)9}W^;Ha^pV!`Pt;9AM#hJ5^m1 zhOF-l`R#imJYT342c7w>1)K3@f$u`04@bWPhjBm?uETRvNAADyaDvOBQVMMN)4*Cc zITM@aI%98WV?CFHF^u&tDx$`!AJ-JPEjXc-6AR-43q(0`gtG5B?he&2k&B?lb0_F? zzk*}78DOt3&zVJ7nZaRPPz7$Va9D1cCGft?-Qc(S0Htf&k7a}bJx2*_+CZ6!{CPH4 zOgM}is=xyl?uTZ&oobJi)jM<&ciAj{eR7{#9+#cNYg;#*p^h_UbJZdfA;Ka;$vq?-MB0^&{kVsPfIOC$Xo%%1{)lAO;o^ zIBl{Q5~H0g1qD-M>}rih^4w9HR)(qNuPS5ZULTXkx>;Y6k94wgTZvE9b9t%b*5)k}FToqMM(E0V&g2OOL zZv0*LzvI9XKpdKIy@PqvJ|hsGe|Ipm+hwf+b?X^-f1} zuuSqW!-+_kseQ8!jQL&(F>#>T^)>u+DMNW5l4Pixe?$5c7l+l}F#PayU9%IC)2W(W zD;Sdp+CP_e^VZeRtd18pXWESLDrkQa$KvDAskFw@ToWVi&vbnray^3G^r`+gf=Phy z2}T6uzcckig4PDUEw83FH2ek1El||`!q}8D^;geLHQ_J`X!k@cOcE^Y^%CJxl73=T zAkH0q`>AS=E_kG!Ktt-q@`Bdb#D{hyI7||%AO#kN%BU~@*wd+w71y?95JkVpax4I7|wvAPp9rZC|kED9UGiHQqxzc&DZyko$^&x)Tmb zbqui>p7*Vwx^I+!i#!J&&azMiIk51l#7(NGG|YzKVI+!Pa-!W70yj0$Bte@CE@Ruf zS}X!MOb(iG9iE%Ung7Bg4=#s|+^;vOSiP;dy`A$-#KXJ}kA`L9NVT`y+5Y6z2#thr zm^`#{Vqpqkq5ff_j3s(zQr<$6zve4P?KP`o6t4=EmQa2jm#v0vX*f&)s-OrKI47Lf z(Ak))UM>Y6lMLeCrTT$9v5t-Ir8D4wD)7u3n4K#^6_mij#Fut!o+-1@#oRf=jJT?< zB}!QE;}gV>mVJ~hF*OE2Z%YZPpbQo;e3gReax_ogyZDZMj6mLCCGBLER@(XYA}7-B zX+aw^9HtCaPyq|JrJ=7cj%~Lc3ZF#YH+fv?9nI-;`*r#ORt$OvW#GIU9Hs(QPz4Ly zrIB$+wAnJRCCx1$c| zmJyL~P1rJec;tKvvrCa*>x$aKVQSEX>+sz4;}WL@k-9;YML2e%=Tm& zOw`>Fh{)kEb*O>{SfJJAI+A@#$#O<5h1!Z}{83Eepw;s!`8#6a6s5IQB`!Ej1FE12 z7WQZuH&Pl=#+AHX`Kfa-4IHIZG^CetD7bNxkUxsn0Lwy6sDc()kQ+cv^O*Wlp?M@y zTO=|o=yQl6b9>io?8Lj=^l9;&I~=A3RnP_t6|@a&bn;{m93m`vPtA@eR~NwOb=Q)u`qqGa8_}G(bzXy|9bb) zK2ub|BW1VZL)BmJJhR+y)f2t?BLau%Llq3b0(Gpgu?rEqr->5@R&QQ{%By5A!UILM z=2#>vi}2okpqp<1RWJk#MRuLiaA)<;7Jsf5T zRWJezURJG&eFtOPc@8p4-=5&)Wz@V+j&YWBqJ0?kx|2QYE*xeARWJq%n;mCsL;=XN zmtBq6P2Q(#$eYcFLJb#umtW~QOP~A!`g6um1rxAvkMR*@{@Hr3tOL~@4irS8)tAiK z3!Y8ARJdo8a{jYGU&sWiU+syPI{Pm?X5ezX(Uv*q-^gf2hpQqHnZ7`t8j`H|KDfa#zWxUxK;Oj<4l{#R zPAtqEEd26mQ!jk?!wgqCG5r8->Ss}^l%r#BVU4IDt2NUh4A@38hbmZrh2Y`btbOOq ziWyeUMkj00K%$?|HA6nPOeLL)zcD;P1;!%_sDdR}sEYlv0LK-4not-vUKDF$lS_RJ z99OY%ATf(fV2RJfhQlnO3RYl&R5jnOTF*Bn{j}iK^fS4QC9_FK37<7ijp++oQIa>n z%Fqg`U=0>NS?eGU_*V9lv&E9|ZMikRmKkm9!~Hxi|AvAk>hvAZwXudO*nowHsGo*Q zn9AD+^kI}dUK$rJmlUWy$*MhV0xXB|cnpu=FdL|XEm%NH5-^`wVWyvJ9y{;re^e-E zpC}e9mnMewEo%3RJ2MI#W(!TY4$n>fivPl62QDYXlMZo;UhRX-baX$P&GSeQLncpQl z+i;jYRKWo(^hmyJPPWWWdP0&DfWO>cq+;RxF3(03&#o$WD9pT)8xC`TDma3LAmS~1 zn86dd^w^1@mtu4nn}b?AXV}Hn9(>+J%X(<0aF`=h!3iv!wz0EQpCi8w-c3nI;(Sy& zIkNwGEB#m@__hr#5y~QP<2gYUoWVlF*+pIMP}_cI*|E zjRagc%o(cS0v0q#^MgwEZRco)r0-LS^9&7%td7pn9e=mtOMUR;>mT6B;{sK11q*(1 z*8Uj??Row0^VqaghgG8$8@ZVz7{=k_?_9%%wSmd4D>UIcJU4al{tJ&AxSWq(ulUde zaEwCBQ+gyc0ta@6NGGyZE`>KZ+_;aH8iCD!H)!R=!XASK3G{+!yQNl?qgLm#lG5Cd zTUbWS=r2d^Sjn5sS7M;U;IPL~1$VGefYw+~C~)tOB~F!CbB0jKEjMKmZFFXgK)U4f zBk5#dDeMka@Bj@9$oq+}xayZxd?c}l!#to0o?u~c z6V)R&utL85)dLoxjz&jV5&Nd^>(PWbr29JU9HYPtz!R$A1r|8Rkqcz-s!J7ewzj5+ zvfLwDG*FI4+D;Dp5Nhq;cLJ+hFQ|eySg& zE|2#ELj*X?8>-*~7S4$z!mufxnBgD$iHoN4@i4t$CPtB+Y@OeJVNAS>4V;GafhJss z=cbqKP(91@c4N^Z>fW!Qt3jSb0{|UoG27PJkQ|AD` z6};V)?|62;8XRTQy?xWIeJA|DcAh^}Apk5;Wm`4vZ()5_rbw-k8SElg>AG-!yd`GH zLhwzTTUi@e83sTVo`8iz@Atd9}I~8&2KGIeXu)pW%mEtytz1J}>@$PFS zA-hH$>5L@#KU+;HVy(#*wM?N?=C-G8ZrLiito$o#&ENfrBQ*w6hh0dsdjXkH-JJ|L3QUt*1HlBjWF5=07VTEiA|oHBJ3d$L_!I7YCV%(H6Zlzz!nS~st^hmYV!2HW5Aj^tsjRvCr_~({6`q2HVL$b}V!}my49`1;+xPO?Sa#-wE?cBXr)V1q z_J&M=asDY(Aq*`1x_>FCaM%XZx9@fu)7XXmthO=XBWU?$;&UoE!?FSV4q;G*aImmR z8asLU(^mSHIND>{Y36mpm(%>lF0v(u`jp}e%BsAgr@qg1e^Iv$Pz~#I==gFIiA^M)6vlhBR$k$QqQ|r30Qx>)5kJuF4 zDTM=vML{bk78VT_3gt3M;wYF{knU1qeW2NAV4uq}-E|r>|DYyyt9Y7I91e?yD#U;V zOP9-D4Xxjgedk0%2ijm$&}B_tnb2 zQ~Q$E>BZXba`QfNtk<+M_h(g&fPLgxs6rfAz?h;bb+|ucE)hxJOz!c`U*#1UnZPTy zg4_A#%(7^Lz+QM9R3RQLXjQq~?%9$%l6s@w!#vi3W;Drq`f<3g%rI~ zncE{Ipu>8;D)LTKVtMF5c8&_cuh0?(BSa;hmjb%})!g z&sC%DE%?0G>YgJLLXF3}JeATlfWw|aD<>9~1QsMh|E5-FjB|=mW>I&>cQ=Jvy*7xM zQK7fMIKmGUDZd4WB|#OQgN40QVejYVUlwXfVn*mkG1Phr?E5VCV))(6^w)Hbi6wAe`yj5v`$!w#%kge-@9ShuenNWo+upoy! z=t3$O@odJ6s(LWhvETwN{qjO8pK`KX(y8s-P1y#rf3o*OA_ALqnCTs_5hXMA5 z4a(M9tqzEEy${MhJjvj0(S*aYp$a)*fyBB_Wg9teQO%@;ptpKc%m}k3dOEF&$kt5D z2Zx9rc>N~_ns6PSn^tH4h37T69Bk~gZmfc7di=tNHlic6QDKkMx<2-4FIW1ZXVW0% z0dwuw(8`I0<$?tQb3qM;yDW8DSxqABjeAAeQxAh4MQxhx`R9*6uUH4dlM7YI0}I^& z$*Av>OX3&{B<$_kGy<<|Px{X}+NBbu78(WD#sO+)?+xV0IKi?EL?1~hEK=U+sn4s_XjIKQF4X32zQja(qWyZ#+E0j0Y_EdKottX zf+8LYTB5L&G=8MMX9|DiUaJ!$gS}Ris=C<;%W+e*Bpg- z3Q`Fr&YDS+E}~>B(s8;o`R)Y*Tarc4gzNC!)UWt2JjLL0xWf(F-i;(x3I@>mVs?;e z%YXT)&h|6xx4g;vN^$EFB^*`^t(;g`30O#v+#31FPo%fui%RO4=5PA^&rTGGv?X6STA|GZjclLcatQ4wH z1{RPaby27HQcqe6=qVOR)#Rvy_!I+$AJsN1SIwfyqytZ$GN?j1SU4SVZnSC5*xzlk zG=1^F`x6BUHx2*rkwM?ekIKy!gY9V>frqso_FAK zG+FnspV?}$5{ZSd6jUKnMJLtIqXe~#X@6$Sv~j=$c1YhrD<>9K1r{84&8SD1tLJJ8 zWsET#s$L%T9_yY)ugGLJjnZhPu>hlX6;$CpSm-n5riC8~&%UpwK*{TPw3S-SMy@nr zyqhCX)~5`|2VO^f4^^lJ3qQNX$h>|!75$MG+oZQ;OdH9_h7-)1dCc+C1r|-M0k54@ zLltVkf{>R;vVgzk9ZIIn=}WZp6O8SJoRir4-<$_&&$00@fVF20RG}6uRD}wXD(7^id=YaQY1!dEAnHbi zo0cp7$}h(uPq&K@4y%JI)Psd24!TwLik)6lYf`=K(6_DV;ir-%y~&Axt9a!CHI9Mx zb3HWSIy^V^wEqiF1Gt>*56kb5t+(ksDa;c#*ZAhlc!%J${tYUWM_+BRP=zM2z$CljzY(}fMJ6fO*EIM^biMF# zcPjUwYx?b)kGFS5so}6Ds6sPXpt~5D_22f5L6UVvWYm{oQD|2Z+m|^Xe(e?>N~YCK z1cx<46+VIm^}hHX&QfIL*N3GpdqaU3d++w{S#03s;o^N;joWTg%+^zIOwyU z5^`x7U9nAh`_}K(FjshH&gLS3CpRPB_1FrOskH!iN3RzhCri zsjpXOA1L$m4wv0sb7kCDs%+jnVll!x6B~c&X!K2P{qs|4x-rF6o7ysB)K){@oY@7& z*yDEHcjdj!j%~d!Ygp~`&!yR&e*%9iIE&iT6je{@H(|HVuNvB&gsR~+F#JNkhx+eV zSib)N59A~`B|qI?8?wgU^Hi?IRVZO#3H0*kJ-$a>_3zgYzxBQMFg0FM^>r(yhiBZV zX5*I^s9#7Fo|hrX20tYBJp8ACua|}a1fpHvk&VVe4!tirHl3J|^*ASW@^d9l7oQ_8 z{%^DGrcd?15v&b-PvE4L?Gb-$eRM1m=Pbe9k`vXa>(55e9f5aPv^{hd0c?e}LAxhn zVeMdHyX2hbq$}G!?iXGCN9F|#*k^;Pkg@s+WGO{V?&x)3S=bI$=l~1iF|k$H?JZOF zq0~w5O?PC6m`V{cFiJFrZu?jGPHi#4VI5F~PhjEO=F;;m_sTkk%FH#p!68@;KjwPz zu!BDnEsHJVJNulg6d`ux;Wvgei0G7o_R zgPl-?F0ioFgjT&EhC^Qb+My`F+ACg2*OSZF#S(p|A?vQ>kIxQpSQk{`Ggy#VAf;Y4 zaQcpt==^5&QLo~5T7MirR;)@f>OhqG_93t(`5CIv4HhnIAFg{eN`!0vejVPA#1n_H zUvQT2vt`x$ZQmn3;Vz&}(hW_xe*E7w&iogiFW_=47T08g@-LB^JHJZBwPZ&zH_xQh zC}(vPDo=gjyLAG*EcgXlIkB)Fu)s@=*fBR8sZL-bIbdF!B=x!j_-i&)4|KzAG|E-v zM1ccRJy3;Supsz7x%4AZy4s=SOnAv%eU8pPQo`z$tRnHx-D1Slqfew{0KDqKWTjBN&0dGyqz})RTu&b zxsnVMIW1A7DUY*@Q7CS2Xohg}AiBq9Q+B$_%5m%f=eCBR3D@Dd>B;+Fc!t5{h%-O_ zp3*2Jh3Nd`t4>j**o;p8<0v75M8awo|%R7qMEV-@U z7yW$v6VLuF!7EDoQN#M?Tfk}Caj3!sSYUi!x%6ewo{B5> zj>s%_Pn~GA-P^AwQgio(5%*O3U4-DU38=y(SjcLfzMb2nSj&gW(La-}QPRQkj0z5$ zf+k#t=cc*nf8m)1m*ex8*|+KZb%|oEb*l5N^*rsUnoH3DNy*UwCo|(<8sNnCG_-PJ zVPC<*ifCsFpMZu;>SekM&T?TsY=oJ9MSXYS>3-$QDGV&&Z2niM!Z)zs;dNZlhKt?I zl01n3{DovP?0gs5Mf$mv_zlYob`f^qm5pyug&DA*Zy@xm*Tu7|LQ8LZ*l}p;gMcsz zk9x=EfN$T2p;K<4VHX&-nHrDk6a-u6ProVynMYB+aIk2G0D(;TW z^f2|w46;PDso^6J+>4e!ACfR?{TIl%QF=7susNv0JXm08y>oWq;kdq0*$|~nGDmTH zYeBK`HQ`jXhlSdaxsNCuHV;j>4$n=iv;V@g04`_y-dk7n#g{!UfmS@f*6xx53(FV; zhPo(vjDA}SlAD+z#RDMP4kqPDrnG|0~zQ$tP*;uEyQ=s;u z0?y|DfGRA4g)P5^wH%fo6>}bpp-yEqi?1StxUn|K^syGmP>llM=y2E~RAC7$5N(`f z$2(KRuh;SCy_x(Lk>04LzdLX5e@wGMuZj^4bn};>3d>;Op*-u;c~0J`oeI~#b4)36 zuE`>4x|Weo()1*}Yz%oB;IL(=!U|ZxsF}1CSc?6Ihim_)*I8w2iMZs;%npw+-% zi@TMr=&pR+2r_XkyirJZktjJoYii@kbJ2e=gQq^TOalw!&S6mEwsfS(rkcSKlJH0af@7 z7LM&nVZ@_U9R@U)f{DpoU5hrNLwe41VtkH6+L!c#KwITERACb=;9s0yEfbo)lgSNvOc&l%%sp%n_mJz(o%p9f1s;vMnxJq;_mQ+pr=hwVZY_P|03Yer|y<#^Il z0u=3Vm*Z^xn?styq;s!=)VP=Fg}DT9D#)&2RWl_2yHc#ag654FemlO z<+IhQ2bgR@Z~u5^>i4t2VMow}>+syv)BZ0!$KZ10Unn@4_8u-OA_v?H%}#r%)j|}z zo*2z&D6&09wv!7?7mlHo6AL>53sU@E#a?h>MIVN-{D;_bPBN^06Mp_WGU}&{oLSXu zz%P6PRX7C;qzoIO!^8I_1>yr2(?}@kn;8;#{F9qc%~I2!o`>r_fWuCq3TI$Jr(Ue^ z@LWsaOSv$L^YC1)gWv#B$~Rd&UiUP!SgZu#oYxst;T$ZGpPH})Q~pFg>-QAZ{8^3j zEE8X;XL2UGgmDg4_zM;&oK+*csa>l}_EZ>_3iQ?)de=A=t958nsz_wpG&*_ zScF3LzsRbxXZ-x(9QTz=_(0bX9a5A5~A;am1!aLPcz(f8R)8U6Z zQMSB5>YD=G=XRv+P5=DVz4$`R=+5*%KQ&=m<7_)Nje91so$H>!+%adZ zM%%p#Jd*K7N~!fHmTVz?|NZfgn?BY5H-eEOB7koSC4+rd7@Y@`!uoDAMWahK!AyEl zSIKnj>nYMA4NrU{IPfo^-4(H=Mh za-e-;ruwJcd#}p%6cnO4L~+1Cfo!0HjgK=PYSVn~-n-A+?HKu)OGun= z!$rD>X$%;skPS4jVXW**NWA(d%I5sN6|Y~Tq;?Omz@kz+(tnz$T472wM0RtVHaUG_c#+~ah-2xXxAC8;w z!$11HI%m&2a!tf&!~>Rp9RULyvVj9OsuXWcn0&41i2RUL zYgtCwos}A(z#zffm-;7->Sr-KD`4P2HgLg);w$IN&si>XOHr5v3OxFC2(I}1Kn&sT%gQ}no7oDE)WWEj)KDeOSr!VB+aVwZGpCuD_&4;m!>6oy=cQt3_!{1Q-OiD8W3c`n05D+E; zuu+L!OQ1dd#HDMHU0V{5F+EUrf-k*EY7KFm3DsZmoCYumARB~W1A&Ti^F_j#*z?k- zA@MO8_lsM4KJ=J2*v3>!&TW0GPyq}=$i{83!5YU|Boyta%DNu)_)z|-Gf$s;8;=(I zRR6b1-(%f&KESvQ*&qTNjIeUru@MF@UW#GhjI{A16#n8!4Xx>@kp1@`lFu}kD>E9ZZe=!`yQ?i5?l1IE@DB*&n%>E$aJ$jeuvbVS8&^gVLSaG)s@M!M>1}S8N z3~V&_$%`$%%I|LbID=Yew)-^Q9pf?6{sG&!k|=v?A#wr0AcJg>|9^&h8`XtHmL1|K z?HSS8b5D{EEo=e;E}W(<8)hHGbIMM5%1Ew z2&p77WiSkGAppi*$OZ-2P%^Gyb{RO`O(QqL6rvcHZCQ1Q-;M z4N9=_ETz*RQgf8^HnqOMkm9MhC>=~~nLzSql9t$b&(C8Pz@UU|P=Sp<_apjFl1=fP zWyl;dwfAqMby?6z+x^s)@)oz^|0{YGSSo17b(n5i-Cc)?8e9;2%1{s?Mb|wIfsy-K zC*6Jdjz^0;N;jA>?%D3NUCP8+2f!Yrn(rK1ZrY ze^7SzBY~A5{GJjH7xB4GPGz+04Ecv*fI$b@pa&a5L_9-1zxIa6(kW^C+G{2f>FicB z5^~p*hq6qV{}Si}20disKG--iyL<7hCTt)wJ(baP_!fpj@shdk6H5&ogBXvUvtJB= zaUZh505*hD>v=Y87=)jp_2-uv&xf{G80VuF$$4TX#;u4oy&(Y%25820m~QHST!)Df zT+k@zu>jEob-U&M#b4csdP2f-gw0g$a)$KSNd&tn0bZaWMrZ{AVPXOsw+9JGa0v&} z{$$D&%k$W%%1*qZ4Vozi{+!>LY3L)Zs|ExUWP=%O5L?d8pSEusqgL(QB5I^(?tcDE zZ%a4-^1)Mmj9f20F2G=hY_NchK{U1(#r=JJ9}Rnl>l{}e`f%yg!j~Uvl&WmmS81a? z0t^<&1}oSoD5x~YXTu1;SaLO9MlMDR8Pxbui~1DC{u#|5ufEa^FjyfQY+xh0N!C5^ zmeik=u3Hs8ikSsp!Znf)kYHiWPSeFvQbJmQ!3No22OC8spQN2|vya%+vqMHL2#KwB zrVP0i{&ZK{vpqnb54akn*&!PoU_+r3bMwN=IIA-;kO2YiSkBGJwyc;p|8b{#lFTgk zGb&(kKr^nxbW?}$I!v74f@XtdF(N1O#L6mZWd-T!k560C$sgwNBX6-1@yHLWU9B}Z zp%ny#i3@DZknEE8dn^#O=x|07%Pw__|4KtnFgiCf@~qPsA^CcBqjNzvxWNY3yWfHD z33)@5Q~c^PhVvhOmaU2}c6YW>Ll_HMkf^%q>~KRic)*6o+B|)jVf5Oc*j%Kc6nS zKfT@&+=XYaEni@uF7UJQ(yO7!jj3p~mQfm~J#YaGe#nLZ*q9tXtCAjnocudgQ=m5p z<&<}4iko_zASCV18)faNk^6ul0L{1#(@j0}>o5s|3;K=vi&01QIS2Ov1?%N`@4>x> zAOVL@WxrY%#Kzs;HIe}Z2|_Cf2$K-lI8i325c?do#=7+0Nn&XX-}SkBB4dp0I9tJ& zNAwgAu0}#3$c8Z3*j2D?4PD&GvYzMhlOnmaz+o3-pboSf9aMP|`9`IU4KRcu8zNu> zRgK*C&vf7S`%DKoG8Y|Q586JxEhaZ?;%!HWM7S-Z0T?2X4NuQu%$kguvmZ%`cVKB6U^be3ASM(A!u0QE-^UtWE{nAAUzc@%2{FzTc;^mdeWLb1ms%m?zryacAk-;4iTisSv% zdiaGKT`CR2cvg^KOHZET(}v|e-e>4`#cgFA-~YK(UaDl-{NuST7o#wDJf|07^v8T* z8G3Rmj+h!EvX+tHe=hawP{S;?2C^XwHo_hamvtLUz3cX# zZgfLlvvb{r2oO`p&Hg2rg9U=J9wkPSJovFKY}eLE)4L%}**rlH{uMK~s(a$@*4 zQACSQEk$+cRR>cJnsGfnxM|$E4wF2%Ag+-O2}w$)<$ZIHLhcNE9yGoP=SIEZcBhy8 zDM%7rS2wymw1R*zDS!>TQDIiMJNU_bM>+Nc`o@1A)}YDC{Ixi6_-Rt}%ZK!8-lYK9 zPy`#3v$nQ-nFEsPTXN*=;cj=l#}`|bgbn4Bl9X{lf86M_SS?q{L*oDp z708Av*m#c4rbF{#Fmo3fWKt8zt=D&c&R1 z83!z|-`(jwjm>^>m(TL{V&v~!Hh2~j^Hm#N4VrNsrkkG3*I{}DF33jPe3s92v^SIW zN7A6zZ@-EYHq{YodPJ2&BpK})T3w)^N6-oa!lVv1_5{QkW{DG;?n)`p(oW%P#6|?G z@{6A>9t`h0=XXpw0ERkbLj!F1AlnrWdo~p5`eCx#Zs2W_vLR3|W_OT2az@c^c0s-e z7#ffbO|XH&jY@;{*tCG=8Q+kiJq#KPA+u7(1dJgfelNU2wwAJ z74}z1=H_z+rM>ia-j3sYS}lH^{x!=)&R2bXEy#v8*ccVIQkqK%SJON|91L;Ntl5b{ z7o%jZtixAh;xpm6^$;+$AsaeiLv*=Zrb0n$mM-addfOo3sDG`gvQga5pD$ocfu=k8 zYObvV+0X?WB+6!^cLu7`Nqd^+Fbr@89dl~lhXy-)n)LhLzjy*1a3)36g=Sob>882s zb(r+P1@Y=OwH6zG(tBoeM7TD3w=O;CC8@(3+b-D5oE&{Y|tl5 ztPXrV?_v}<$jZe=EzTch#_`u0O1d@O#O7PuDFhhCkPQ>CvEZg!ry%pAkFs%7`%=K< zPk=}Np|>3TK9y<3cA{YCRfom|vSA7~-ZI#GAhMn}#vg^&oc)x0KB>jtgNRfu^L`FC zPx&%W5-?1m8P{RDX?1rUCNpqBR6DP02&95)kQ<(t*ol^YIHU6Ry>%=^{Hj8@JIYPu z>I{(?w1R*znS+hOcl&$T=HEmfOq4KUlo+lc@a5J@_t{#1sEVjYi}1YaI+#N?EWk#$ zanMwkjv&j*8&CRJvjg;1?vvg3Jc*&qoG)f#&+D%aNLoNPEWw6-wMGRRvxnp}+S>ez zg|mdlWm|mNjrI0KqvD=Ye%!0|j3s2l3T(JV@K$)R*x67cG0GkKUcRg3Rn1c>cNHw1 z_x~wX+I!XFwt{R}gAI)@1ZWIFMHe|2YVUXoN9f;p3OklzOK4486mD}1YF^#w){qSw zuu+)Xyhh89X224Z0^{k`%D2o|;G4Z;#xagJ^Ykz8?$r?y8_0$&*pN-BiCOR1mo(gP zoe#e(^&Fg^52FGaAGPcfRUmI%1{|9qe)P<=cfa@e;VvUPDab#Z`f zID(D1WTIWgKD`4=Nsi|F)Ss8s8(H5R^o{*6jI3J;BPg%d8jg?+C$JI!6i@ya77O8z z?X)lU+Y5#7q=pxZwLJS**WL!+{`u{y$>9Xqa0VNWOX{e6FTek#_iY&*@v>W0{~W}n ztx(sRf@e+L6IjR%7|xIl7qG#tJtfR>FO%#o@+z0zC~a@XiWEE7=6(Ii!t{g^J{LN` zaDi;Nf{lz;+$DS^hrN`bL-{Xup+BkWqxfTR6EmGi90ybl1g{2ZS7^p{m~QG2UWdsI zT#yW72$C$7rxUJeR&G1@u7wWTC-b(RHESo*Km!@Zd3K;6H)sU`VR{TUxD7T-VJ2lE zvfMlEL zp5$UV+?W@0`FoRBQ%QHoh6mV?;6)OAHHL*t!%O+r=u?#3q-gapA&FUK#ly6wQm4Gz zfZ+kz@B|x0xUY||pOX`6TgkNw3h^q--O#ZS+%B^Y0w1onh%c!7<8@jDM@ z@1I#~R65SQhrhy;KY6&*XPD`bMmIeKsz+=faUcimA* z6}D^u!v~si9j2Rl=+|NL1sCL;ITRz2ml*3hCvJ^Ul#>5Iq?b z?%9kL*oBhZmkP8y8h@z=7y*!tCtxFiON`tq9l`XTf-JqdqB5V0rqxk*jT_=AeK8wv zt`{|6Jb`Qkf{p4ul@y5_erBFe7A`^UDQ%ofOS4;{!4K*-)}EQ42wpV@0wEhgV52ye z^NUK*x*Zm7kV<&DS@=;S=I=OR3>#Ik{^s0+6j8tkf@}nXjkg7ep|^1t9+M6G-Ps~+ zj4Ai!zfDL|sS`d$Huh9G7!fdnp&8ekeJ$qFC}TYj_^SdLxcxTN<;4{2M>?z5a*%d7 z`ycmi_yS@2ceAf^{f}2Wx9-_9Y4(x{`b`|%zu&t1lWwt*onfKWBly7n+3KQ?6nJTu z(`P=&{7NhPvZEtXd&RcBA?Zg&lTR3ZZv=q z?Bt*H;j@UdS5i8kY`M9~^AD<{SM$dZXmPTTZ%LsgOZ zxj6kQ$3IQCV_Fp}0R|kh5ehbJ5g*m6d?BRzWofY@BfoD}iGxd5K#xE~N&0BfH+B8$ zAXg}4<0;ti$$I_>i`}c->s^0LQSeL3x{LFr#{{xCuK!3 zlh_}qw_g5|M_BjmBY1Ij1V0?I5dk)u1yGE*v3p%ha^I2Af6h19Wm#pQ{jQ@J7C