diff --git a/chain/gen/genesis/genesis.go b/chain/gen/genesis/genesis.go index 93d75a303..0e46b0079 100644 --- a/chain/gen/genesis/genesis.go +++ b/chain/gen/genesis/genesis.go @@ -6,7 +6,6 @@ import ( "github.com/filecoin-project/go-amt-ipld/v2" "github.com/filecoin-project/specs-actors/actors/abi" - "github.com/filecoin-project/specs-actors/actors/abi/big" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin/account" "github.com/filecoin-project/specs-actors/actors/crypto" @@ -20,7 +19,6 @@ import ( "github.com/filecoin-project/go-address" - "github.com/filecoin-project/lotus/build" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -131,11 +129,12 @@ func MakeInitialStateTree(ctx context.Context, bs bstore.Blockstore, template ge } // Setup reward - err = state.SetActor(builtin.RewardActorAddr, &types.Actor{ - Code: builtin.RewardActorCodeID, - Balance: big.Int{Int: build.InitialReward}, - Head: emptyobject, // TODO ? - }) + rewact, err := SetupRewardActor(bs) + if err != nil { + return nil, xerrors.Errorf("setup init actor: %w", err) + } + + err = state.SetActor(builtin.RewardActorAddr, rewact) if err != nil { return nil, xerrors.Errorf("set network account actor: %w", err) } diff --git a/chain/gen/genesis/t02_reward.go b/chain/gen/genesis/t02_reward.go index d215223ac..5d1acade3 100644 --- a/chain/gen/genesis/t02_reward.go +++ b/chain/gen/genesis/t02_reward.go @@ -1 +1,36 @@ package genesis + +import ( + "context" + + "github.com/filecoin-project/lotus/build" + "github.com/filecoin-project/lotus/chain/store" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/specs-actors/actors/builtin" + "github.com/filecoin-project/specs-actors/actors/builtin/reward" + "github.com/filecoin-project/specs-actors/actors/util/adt" + bstore "github.com/ipfs/go-ipfs-blockstore" + cbor "github.com/ipfs/go-ipld-cbor" +) + +func SetupRewardActor(bs bstore.Blockstore) (*types.Actor, error) { + cst := cbor.NewCborStore(bs) + + as := store.ActorStore(context.TODO(), bs) + emv, err := adt.MakeEmptyMultimap(as) + if err != nil { + return nil, err + } + + st := reward.ConstructState(emv.Root()) + hcid, err := cst.Put(context.TODO(), st) + if err != nil { + return nil, err + } + + return &types.Actor{ + Code: builtin.RewardActorCodeID, + Balance: types.BigInt{Int: build.InitialReward}, + Head: hcid, + }, nil +} diff --git a/chain/state/statetree.go b/chain/state/statetree.go index b0cabc309..4386efb43 100644 --- a/chain/state/statetree.go +++ b/chain/state/statetree.go @@ -26,7 +26,7 @@ type StateTree struct { Store cbor.IpldStore actorcache map[address.Address]*types.Actor - snapshot cid.Cid + snapshots []cid.Cid } func NewStateTree(cst cbor.IpldStore) (*StateTree, error) { @@ -134,7 +134,6 @@ func (st *StateTree) Flush(ctx context.Context) (cid.Cid, error) { return cid.Undef, err } } - st.actorcache = make(map[address.Address]*types.Actor) if err := st.root.Flush(ctx); err != nil { return cid.Undef, err @@ -152,10 +151,14 @@ func (st *StateTree) Snapshot(ctx context.Context) error { return err } - st.snapshot = ss + st.snapshots = append(st.snapshots, ss) return nil } +func (st *StateTree) ClearSnapshot() { + st.snapshots = st.snapshots[:len(st.snapshots)-1] +} + func (st *StateTree) RegisterNewAddress(addr address.Address, act *types.Actor) (address.Address, error) { var out address.Address err := st.MutateActor(builtin.InitActorAddr, func(initact *types.Actor) error { @@ -198,10 +201,12 @@ func (a *AdtStore) Context() context.Context { var _ adt.Store = (*AdtStore)(nil) func (st *StateTree) Revert() error { - nd, err := hamt.LoadNode(context.Background(), st.Store, st.snapshot, hamt.UseTreeBitWidth(5)) + revTo := st.snapshots[len(st.snapshots)-1] + nd, err := hamt.LoadNode(context.Background(), st.Store, revTo, hamt.UseTreeBitWidth(5)) if err != nil { return err } + st.actorcache = make(map[address.Address]*types.Actor) st.root = nd return nil diff --git a/chain/state/statetree_test.go b/chain/state/statetree_test.go index 3231e36eb..b22828e19 100644 --- a/chain/state/statetree_test.go +++ b/chain/state/statetree_test.go @@ -2,9 +2,10 @@ package state import ( "context" - "github.com/filecoin-project/specs-actors/actors/builtin" "testing" + "github.com/filecoin-project/specs-actors/actors/builtin" + address "github.com/filecoin-project/go-address" "github.com/filecoin-project/lotus/chain/types" cbor "github.com/ipfs/go-ipld-cbor" @@ -144,3 +145,85 @@ func TestSetCache(t *testing.T) { t.Error("nonce didn't match") } } + +func TestSnapshots(t *testing.T) { + ctx := context.Background() + cst := cbor.NewMemCborStore() + st, err := NewStateTree(cst) + if err != nil { + t.Fatal(err) + } + + var addrs []address.Address + //for _, a := range []string{"t15ocrptbu4i5qucjvvwecihd7fqqgzb27pz5l5zy", "t1dpyvgavvl3f4ujlk6odedss54z6rt5gyuknsuva", "t1feiejbkcvozy7iltt2pxzuoq4d2kpbsusugan7a", "t3rgjfqybjx7bahuhfv7nwfg3tlm4i4zyvldfirjvzm5z5xwjoqbj3rfi2mpmlxpqwxxxafgpkjilqzpg7cefa"} { + for _, a := range []string{"t0100", "t0101", "t0102", "t0103"} { + addr, err := address.NewFromString(a) + if err != nil { + t.Fatal(err) + } + addrs = append(addrs, addr) + } + + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[0], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(55)}); err != nil { + t.Fatal(err) + } + + { // sub call that will fail + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[1], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(77)}); err != nil { + t.Fatal(err) + } + + if err := st.Revert(); err != nil { + t.Fatal(err) + } + st.ClearSnapshot() + } + + // more operations in top level call... + if err := st.SetActor(addrs[2], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(123)}); err != nil { + t.Fatal(err) + } + + { // sub call that succeeds + if err := st.Snapshot(ctx); err != nil { + t.Fatal(err) + } + + if err := st.SetActor(addrs[3], &types.Actor{Code: builtin.AccountActorCodeID, Head: builtin.AccountActorCodeID, Balance: types.NewInt(5)}); err != nil { + t.Fatal(err) + } + + st.ClearSnapshot() + } + + if _, err := st.Flush(ctx); err != nil { + t.Fatal(err) + } + + assertHas(t, st, addrs[0]) + assertNotHas(t, st, addrs[1]) + assertHas(t, st, addrs[2]) + assertHas(t, st, addrs[3]) +} + +func assertHas(t *testing.T, st *StateTree, addr address.Address) { + _, err := st.GetActor(addr) + if err != nil { + t.Fatal(err) + } +} + +func assertNotHas(t *testing.T, st *StateTree, addr address.Address) { + _, err := st.GetActor(addr) + if err == nil { + t.Fatal("shouldnt have found actor", addr) + } +} diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index 70a20ab8d..2dced4630 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -8,6 +8,7 @@ import ( "github.com/filecoin-project/go-address" amt "github.com/filecoin-project/go-amt-ipld/v2" "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/actors" "github.com/filecoin-project/lotus/chain/state" "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" @@ -16,6 +17,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/abi/big" "github.com/filecoin-project/specs-actors/actors/builtin" "github.com/filecoin-project/specs-actors/actors/builtin/market" + "github.com/filecoin-project/specs-actors/actors/builtin/reward" "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/filecoin-project/specs-actors/actors/util/adt" cbg "github.com/whyrusleeping/cbor-gen" @@ -130,31 +132,6 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B } /* - rewardActor, err := vmi.StateTree().GetActor(builtin.RewardActorAddr) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get network actor: %w", err) - } - reward := vm.MiningReward(rewardActor.Balance) - for _, b := range bms { - rewardActor, err = vmi.StateTree().GetActor(builtin.RewardActorAddr) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get network actor: %w", err) - } - vmi.SetBlockMiner(b.Miner) - - owner, err := GetMinerOwner(ctx, sm, pstate, b.Miner) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get owner for miner %s: %w", b.Miner, err) - } - - act, err := vmi.StateTree().GetActor(owner) - if err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to get miner owner actor") - } - - if err := vm.Transfer(rewardActor, act, reward); err != nil { - return cid.Undef, cid.Undef, xerrors.Errorf("failed to deduct funds from network actor: %w", err) - } } */ @@ -178,9 +155,10 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B for _, b := range bms { vmi.SetBlockMiner(b.Miner) - cmsgs := append(b.BlsMessages, b.SecpkMessages...) + penalty := types.NewInt(0) + gasReward := types.NewInt(0) - for _, cm := range cmsgs { + for _, cm := range append(b.BlsMessages, b.SecpkMessages...) { m := cm.VMMessage() if err := preloadAddr(m.From); err != nil { return cid.Undef, cid.Undef, err @@ -209,6 +187,43 @@ func (sm *StateManager) ApplyBlocks(ctx context.Context, pstate cid.Cid, bms []B } } } + + owner, err := GetMinerOwner(ctx, sm, pstate, b.Miner) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to get owner for miner %s: %w", b.Miner, err) + } + + params, err := actors.SerializeParams(&reward.AwardBlockRewardParams{ + MinerOwner: owner, + Penalty: penalty, + GasReward: gasReward, + }) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to serialize award params: %w", err) + } + + sysAct, err := vmi.StateTree().GetActor(builtin.SystemActorAddr) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to get system actor: %w", err) + } + + ret, err := vmi.ApplyMessage(ctx, &types.Message{ + From: builtin.SystemActorAddr, + To: builtin.RewardActorAddr, + Nonce: sysAct.Nonce, + Value: types.NewInt(0), + GasPrice: types.NewInt(0), + GasLimit: types.NewInt(1 << 30), + Method: builtin.MethodsReward.AwardBlockReward, + Params: params, + }) + if err != nil { + return cid.Undef, cid.Undef, xerrors.Errorf("failed to apply reward message for miner %s: %w", b.Miner, err) + } + if ret.ExitCode != 0 { + return cid.Undef, cid.Undef, xerrors.Errorf("reward application message failed (exit %d): %s", ret.ExitCode, ret.ActorErr) + } + } // TODO: this nonce-getting is a tiny bit ugly diff --git a/chain/vm/invoker.go b/chain/vm/invoker.go index df5ec704e..33034b2ab 100644 --- a/chain/vm/invoker.go +++ b/chain/vm/invoker.go @@ -19,6 +19,7 @@ import ( "github.com/filecoin-project/specs-actors/actors/builtin/multisig" "github.com/filecoin-project/specs-actors/actors/builtin/paych" "github.com/filecoin-project/specs-actors/actors/builtin/power" + "github.com/filecoin-project/specs-actors/actors/builtin/reward" "github.com/filecoin-project/specs-actors/actors/builtin/system" vmr "github.com/filecoin-project/specs-actors/actors/runtime" "github.com/filecoin-project/specs-actors/actors/util/adt" @@ -44,6 +45,7 @@ func NewInvoker() *invoker { // add builtInCode using: register(cid, singleton) inv.Register(builtin.SystemActorCodeID, system.Actor{}, adt.EmptyValue{}) inv.Register(builtin.InitActorCodeID, init_.Actor{}, init_.State{}) + inv.Register(builtin.RewardActorCodeID, reward.Actor{}, reward.State{}) inv.Register(builtin.CronActorCodeID, cron.Actor{}, cron.State{}) inv.Register(builtin.StoragePowerActorCodeID, power.Actor{}, power.State{}) inv.Register(builtin.StorageMarketActorCodeID, market.Actor{}, market.State{}) diff --git a/chain/vm/spec_shim.go b/chain/vm/spec_shim.go index c5f6f1b06..1b4364494 100644 --- a/chain/vm/spec_shim.go +++ b/chain/vm/spec_shim.go @@ -58,10 +58,9 @@ func (rs *runtimeShim) shimCall(f func() interface{}) (rval []byte, aerr aerrors aerr = ar return } - log.Warn("caught one of those actor errors: ", r) debug.PrintStack() log.Errorf("ERROR") - aerr = aerrors.Newf(1, "generic spec actors failure") + aerr = aerrors.Newf(1, "spec actors failure: %s", r) } }() diff --git a/chain/vm/vm.go b/chain/vm/vm.go index 5f51b85c4..001f59d4a 100644 --- a/chain/vm/vm.go +++ b/chain/vm/vm.go @@ -163,7 +163,18 @@ func (vmc *VMContext) Send(to address.Address, method abi.MethodNum, value types GasLimit: vmc.gasAvailable, } + st := vmc.state + if err := st.Snapshot(ctx); err != nil { + return nil, aerrors.Fatalf("snapshot failed: %s", err) + } + defer st.ClearSnapshot() + ret, err, _ := vmc.vm.send(ctx, msg, vmc, 0) + if err != nil { + if err := st.Revert(); err != nil { + return nil, aerrors.Escalate(err, "failed to revert state tree after failed subcall") + } + } return ret, err } @@ -366,6 +377,7 @@ func (vm *VM) send(ctx context.Context, msg *types.Message, parent *VMContext, gasCharge uint64) ([]byte, aerrors.ActorError, *VMContext) { st := vm.cstate + fromActor, err := st.GetActor(msg.From) if err != nil { return nil, aerrors.Absorb(err, 1, "could not find source actor"), nil @@ -453,6 +465,7 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, if err := st.Snapshot(ctx); err != nil { return nil, xerrors.Errorf("snapshot failed: %w", err) } + defer st.ClearSnapshot() fromActor, err := st.GetActor(msg.From) if err != nil { @@ -526,15 +539,13 @@ func (vm *VM) ApplyMessage(ctx context.Context, msg *types.Message) (*ApplyRet, } } - bfact, err := st.GetActor(builtin.BurntFundsActorAddr) + rwAct, err := st.GetActor(builtin.RewardActorAddr) if err != nil { return nil, xerrors.Errorf("getting burnt funds actor failed: %w", err) } - // TODO: support multiple blocks in a tipset - // TODO: actually wire this up (miner is undef for now) gasReward := types.BigMul(msg.GasPrice, gasUsed) - if err := Transfer(gasHolder, bfact, gasReward); err != nil { + if err := Transfer(gasHolder, rwAct, gasReward); err != nil { return nil, xerrors.Errorf("failed to give miner gas reward: %w", err) } diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index 82ffdf45e..19eba8377 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -6,12 +6,14 @@ import ( "context" "encoding/hex" "encoding/json" - "github.com/filecoin-project/lotus/chain/types" + "fmt" "io/ioutil" "os" "runtime/pprof" "strings" + "github.com/filecoin-project/lotus/chain/types" + paramfetch "github.com/filecoin-project/go-paramfetch" "github.com/filecoin-project/go-sectorbuilder" blockstore "github.com/ipfs/go-ipfs-blockstore" @@ -134,6 +136,7 @@ var DaemonCmd = &cli.Command{ return err } if cctx.Bool("halt-after-import") { + fmt.Println("Chain import complete, halting as requested...") return nil } } diff --git a/go.mod b/go.mod index c70a46698..7e3a60ac1 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect github.com/coreos/go-systemd/v22 v22.0.0 github.com/docker/go-units v0.4.0 - github.com/filecoin-project/chain-validation v0.0.6-0.20200303230205-30309cc5d8eb + github.com/filecoin-project/chain-validation v0.0.6-0.20200304211828-4b541348b199 github.com/filecoin-project/filecoin-ffi v0.0.0-20200226231125-fc253ccb5294 github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be github.com/filecoin-project/go-amt-ipld/v2 v2.0.1-0.20200131012142-05d80eeccc5e @@ -23,8 +23,7 @@ require ( github.com/filecoin-project/go-paramfetch v0.0.2-0.20200218225740-47c639bab663 github.com/filecoin-project/go-sectorbuilder v0.0.2-0.20200304050010-2cfac00a93e7 github.com/filecoin-project/go-statestore v0.1.0 - github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf - github.com/filecoin-project/specs-storage v0.0.0-20200303230804-16c9a38030eb + github.com/filecoin-project/specs-actors v0.0.0-20200304210626-21ee86aadcb9 github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 github.com/go-ole/go-ole v1.2.4 // indirect github.com/google/uuid v1.1.1 diff --git a/go.sum b/go.sum index 4cbdf7aed..eb7eaac09 100644 --- a/go.sum +++ b/go.sum @@ -97,8 +97,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/fatih/color v1.8.0 h1:5bzFgL+oy7JITMTxUPJ00n7VxmYd/PdMp5mHFX40/RY= github.com/fatih/color v1.8.0/go.mod h1:3l45GVGkyrnYNl9HoIjnp2NnNWvh6hLAqD8yTfGjnw8= github.com/fd/go-nat v1.0.0/go.mod h1:BTBu/CKvMmOMUPkKVef1pngt2WFH/lg7E6yQnulfp6E= -github.com/filecoin-project/chain-validation v0.0.6-0.20200303230205-30309cc5d8eb h1:vGm95uLk+4ytckKQX8mfOwMzgBge9rqHbTfDsImUWIs= -github.com/filecoin-project/chain-validation v0.0.6-0.20200303230205-30309cc5d8eb/go.mod h1:6FYR4Xi26mfXkNlrHOo7fZjITZ9GrqqF8OKG4IQ4zWk= +github.com/filecoin-project/chain-validation v0.0.6-0.20200304211828-4b541348b199 h1:HIdN3/s/fda3kQYMawG8ysYC207LJ5GnH0XziyTgQJk= +github.com/filecoin-project/chain-validation v0.0.6-0.20200304211828-4b541348b199/go.mod h1:JU9alo66MwdCHkpk1kDXB8vT8A/oMkTkdZ4mQjA4I5E= github.com/filecoin-project/go-address v0.0.0-20200107215422-da8eea2842b5/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be h1:TooKBwR/g8jG0hZ3lqe9S5sy2vTUcLOZLlz3M5wGn2E= github.com/filecoin-project/go-address v0.0.2-0.20200218010043-eb9bb40ed5be/go.mod h1:SAOwJoakQ8EPjwNIsiakIQKsoKdkcbx8U3IapgCg9R0= @@ -112,8 +112,6 @@ github.com/filecoin-project/go-data-transfer v0.0.0-20191219005021-4accf56bd2ce github.com/filecoin-project/go-data-transfer v0.0.0-20191219005021-4accf56bd2ce/go.mod h1:b14UWxhxVCAjrQUYvVGrQRRsjAh79wXYejw9RbUcAww= github.com/filecoin-project/go-fil-commcid v0.0.0-20200208005934-2b8bd03caca5 h1:yvQJCW9mmi9zy+51xA01Ea2X7/dL7r8eKDPuGUjRmbo= github.com/filecoin-project/go-fil-commcid v0.0.0-20200208005934-2b8bd03caca5/go.mod h1:JbkIgFF/Z9BDlvrJO1FuKkaWsH673/UdFaiVS6uIHlA= -github.com/filecoin-project/go-fil-markets v0.0.0-20200303015849-1159079679ca h1:EccB/LgjrA6EVSpaVDfQyWe1DS3c0x1DcASBQ8beUdg= -github.com/filecoin-project/go-fil-markets v0.0.0-20200303015849-1159079679ca/go.mod h1:rfRwhd3ujcCXnD4N9oEM2wjh8GRZGoeNXME+UPG/9ts= github.com/filecoin-project/go-fil-markets v0.0.0-20200304003055-d449a980d4bd h1:tLOl4GWJKQjpjqvSYSW0FoIjhoAzJSEoRibqkVFlaCE= github.com/filecoin-project/go-fil-markets v0.0.0-20200304003055-d449a980d4bd/go.mod h1:rfRwhd3ujcCXnD4N9oEM2wjh8GRZGoeNXME+UPG/9ts= github.com/filecoin-project/go-padreader v0.0.0-20200210211231-548257017ca6 h1:92PET+sx1Hb4W/8CgFwGuxaKbttwY+UNspYZTvXY0vs= @@ -139,6 +137,8 @@ github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf/go.m github.com/filecoin-project/specs-actors v0.0.0-20200302223606-0eaf97b10aaf/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/filecoin-project/specs-storage v0.0.0-20200303230804-16c9a38030eb h1:oHB9hKaD7g75NFulnfh+SCYS5bSl8hB6Eanf8A6l5tw= github.com/filecoin-project/specs-storage v0.0.0-20200303230804-16c9a38030eb/go.mod h1:sC2Ck2l1G8hXI5Do/3sp0yxbMRMnukbFwP9KF1CRFLw= +github.com/filecoin-project/specs-actors v0.0.0-20200304210626-21ee86aadcb9 h1:5/XkV9N7Zlidi2RYY/04BToD/XeQrudUseI7Gx6owl8= +github.com/filecoin-project/specs-actors v0.0.0-20200304210626-21ee86aadcb9/go.mod h1:0HAWYrvajFHDgRaKbF0rl+IybVLZL5z4gQ8koCMPhoU= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gbrlsnchs/jwt/v3 v3.0.0-beta.1 h1:EzDjxMg43q1tA2c0MV3tNbaontnHLplHyFF6M5KiVP0=