diff --git a/actors/builtin/expert/expert_state.go b/actors/builtin/expert/expert_state.go index 7bb6e1227..ed1a3f414 100644 --- a/actors/builtin/expert/expert_state.go +++ b/actors/builtin/expert/expert_state.go @@ -71,7 +71,7 @@ func ConstructExpertInfo(owner addr.Address, eType ExpertType, aHash string) (*E } func ConstructState(store adt.Store, info cid.Cid, state ExpertState, emptyChange cid.Cid) (*State, error) { - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } diff --git a/actors/builtin/expertfund/expertfund_state.go b/actors/builtin/expertfund/expertfund_state.go index 31493181f..f7b013f4f 100644 --- a/actors/builtin/expertfund/expertfund_state.go +++ b/actors/builtin/expertfund/expertfund_state.go @@ -55,7 +55,7 @@ type PoolInfo struct { // ConstructState expert fund construct func ConstructState(store adt.Store, pool cid.Cid) (*State, error) { - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } diff --git a/actors/builtin/govern/govern_state.go b/actors/builtin/govern/govern_state.go index 131336fd4..014c1ae56 100644 --- a/actors/builtin/govern/govern_state.go +++ b/actors/builtin/govern/govern_state.go @@ -31,7 +31,7 @@ func ConstructState(store adt.Store, supervisor address.Address) (*State, error) return nil, xerrors.New("supervisor address must be an ID address") } - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } @@ -85,7 +85,10 @@ func (st *State) grantOrRevoke(store adt.Store, governors *adt.Map, governor add if !grant { // do nothing for revoke return nil } - mp = adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + mp, err = adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + if err != nil { + return xerrors.Errorf("failed to create empty map: %w", err) + } } else { mp, err = adt.AsMap(store, out.CodeMethods, builtin.DefaultHamtBitwidth) if err != nil { diff --git a/actors/builtin/govern/govern_test.go b/actors/builtin/govern/govern_test.go new file mode 100644 index 000000000..52bfbec4a --- /dev/null +++ b/actors/builtin/govern/govern_test.go @@ -0,0 +1,12 @@ +package govern_test + +import ( + "testing" + + "github.com/filecoin-project/specs-actors/v2/actors/builtin/govern" + "github.com/filecoin-project/specs-actors/v2/support/mock" +) + +func TestExports(t *testing.T) { + mock.CheckActorExports(t, govern.Actor{}) +} diff --git a/actors/builtin/knowledge/knowledge_state.go b/actors/builtin/knowledge/knowledge_state.go index cfa8c04c1..ce8673320 100644 --- a/actors/builtin/knowledge/knowledge_state.go +++ b/actors/builtin/knowledge/knowledge_state.go @@ -21,7 +21,7 @@ func ConstructState(store adt.Store, initialPayee address.Address) (*State, erro return nil, xerrors.New("intial payee address must be an ID address") } - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } diff --git a/actors/builtin/market/index_multimap.go b/actors/builtin/market/index_multimap.go index cedddcc47..0764df900 100644 --- a/actors/builtin/market/index_multimap.go +++ b/actors/builtin/market/index_multimap.go @@ -2,7 +2,6 @@ package market import ( "github.com/filecoin-project/go-address" - "github.com/filecoin-project/go-hamt-ipld/v3" "github.com/filecoin-project/go-state-types/abi" cid "github.com/ipfs/go-cid" "github.com/pkg/errors" @@ -29,14 +28,21 @@ func AsIndexMultimap(s adt.Store, r cid.Cid, outerBitwidth, innerBitwidth int) ( } // Creates a new map backed by an empty HAMT and flushes it to the store. -func MakeEmptyIndexMultimap(s adt.Store, bitwidth int) *IndexMultimap { - m := adt.MakeEmptyMap(s, bitwidth) - return &IndexMultimap{mp: m, store: s, innerBitwidth: bitwidth} +func MakeEmptyIndexMultimap(s adt.Store, bitwidth int) (*IndexMultimap, error) { + m, err := adt.MakeEmptyMap(s, bitwidth) + if err != nil { + return nil, err + } + return &IndexMultimap{mp: m, store: s, innerBitwidth: bitwidth}, nil } // Writes a new empty map to the store and returns its CID. func StoreEmptyIndexMultimap(s adt.Store, bitwidth int) (cid.Cid, error) { - return MakeEmptyIndexMultimap(s, bitwidth).Root() + mm, err := MakeEmptyIndexMultimap(s, bitwidth) + if err != nil { + return cid.Undef, err + } + return mm.Root() } // Returns the root cid of the underlying HAMT. @@ -56,7 +62,10 @@ func (mm *IndexMultimap) Put(epoch abi.ChainEpoch, providerIndexes map[address.A return err } if !found { - pimap = adt.MakeEmptyMap(mm.store, mm.innerBitwidth) + pimap, err = adt.MakeEmptyMap(mm.store, mm.innerBitwidth) + if err != nil { + return err + } } for provider, indexes := range providerIndexes { @@ -115,18 +124,8 @@ func (mm *IndexMultimap) Put(epoch abi.ChainEpoch, providerIndexes map[address.A // Removes all values for a piece. func (mm *IndexMultimap) RemoveAll(key abi.ChainEpoch) error { - k := abi.UIntKey(uint64(key)) - found, err := mm.mp.Has(k) - if err != nil { - return err - } - if !found { - return nil - } - - err = mm.mp.Delete(k) - if err != nil && !xerrors.Is(err, hamt.ErrNotFound) { - return xerrors.Errorf("failed to delete map key %v: %w", key, err) + if _, err := mm.mp.TryDelete(abi.UIntKey(uint64(key))); err != nil { + return xerrors.Errorf("failed to delete set key %v: %w", key, err) } return nil } diff --git a/actors/builtin/market/market_actor.go b/actors/builtin/market/market_actor.go index 76bafe476..b593e0c3d 100644 --- a/actors/builtin/market/market_actor.go +++ b/actors/builtin/market/market_actor.go @@ -585,7 +585,6 @@ func (a Actor) CronTick(rt Runtime, _ *abi.EmptyValue) *abi.EmptyValue { pdErr := msm.pendingDeals.Delete(abi.CidKey(dcid)) builtin.RequireNoErr(rt, pdErr, exitcode.ErrIllegalState, "failed to delete pending proposal %v", dcid) - return nil } @@ -736,8 +735,8 @@ func genTerminateEpoch(currEpoch abi.ChainEpoch, dealID abi.DealID) (abi.ChainEp func deleteDealProposalAndState(dealId abi.DealID, states *DealMetaArray, proposals *DealArray, removeProposal bool, removeState bool) error { if removeProposal { - if err := proposals.Delete(uint64(dealId)); err != nil { - return xerrors.Errorf("failed to delete deal proposal: %w", err) + if err := proposals.Delete(dealId); err != nil { + return xerrors.Errorf("failed to delete proposal %d : %w", dealId, err) } } diff --git a/actors/builtin/market/market_test.go b/actors/builtin/market/market_test.go index 8553935c5..9b05f87f2 100644 --- a/actors/builtin/market/market_test.go +++ b/actors/builtin/market/market_test.go @@ -52,7 +52,8 @@ func TestRemoveAllError(t *testing.T) { rt := builder.Build(t) store := adt.AsStore(rt) - smm := market.MakeEmptySetMultimap(store, builtin.DefaultHamtBitwidth) + smm, err := market.MakeEmptySetMultimap(store, builtin.DefaultHamtBitwidth) + require.NoError(t, err) if err := smm.RemoveAll(42); err != nil { t.Fatalf("expected no error, got: %s", err) @@ -98,7 +99,7 @@ func TestMarketActor(t *testing.T) { emptyStatesArrayCid, err := adt.StoreEmptyArray(store, market.StatesAmtBitwidth) assert.NoError(t, err) - emptyMultiMap, err := market.MakeEmptySetMultimap(store, builtin.DefaultHamtBitwidth).Root() + emptyMultiMap, err := market.StoreEmptySetMultimap(store, builtin.DefaultHamtBitwidth) assert.NoError(t, err) var state market.State @@ -3372,7 +3373,7 @@ func (h *marketActorTestHarness) deleteDealProposal(rt *mock.Runtime, dealId abi rt.GetState(&st) deals, err := market.AsDealProposalArray(adt.AsStore(rt), st.Proposals) require.NoError(h.t, err) - require.NoError(h.t, deals.Delete(uint64(dealId))) + require.NoError(h.t, deals.Delete(dealId)) st.Proposals, err = deals.Root() require.NoError(h.t, err) rt.ReplaceState(&st) diff --git a/actors/builtin/market/set_multimap.go b/actors/builtin/market/set_multimap.go index 427186bd8..e1db09c7e 100644 --- a/actors/builtin/market/set_multimap.go +++ b/actors/builtin/market/set_multimap.go @@ -3,7 +3,6 @@ package market import ( "reflect" - "github.com/filecoin-project/go-hamt-ipld/v3" "github.com/filecoin-project/go-state-types/abi" cid "github.com/ipfs/go-cid" "github.com/pkg/errors" @@ -31,15 +30,21 @@ func AsSetMultimap(s adt.Store, r cid.Cid, outerBitwidth, innerBitwidth int) (*S // Creates a new map backed by an empty HAMT and flushes it to the store. // Both inner and outer HAMTs have branching factor 2^bitwidth. -func MakeEmptySetMultimap(s adt.Store, bitwidth int) *SetMultimap { - m := adt.MakeEmptyMap(s, bitwidth) - return &SetMultimap{mp: m, store: s, innerBitwidth: bitwidth} +func MakeEmptySetMultimap(s adt.Store, bitwidth int) (*SetMultimap, error) { + m, err := adt.MakeEmptyMap(s, bitwidth) + if err != nil { + return nil, err + } + return &SetMultimap{mp: m, store: s, innerBitwidth: bitwidth}, nil } // Writes a new empty map to the store and returns its CID. func StoreEmptySetMultimap(s adt.Store, bitwidth int) (cid.Cid, error) { - m := MakeEmptySetMultimap(s, bitwidth) - return m.Root() + mm, err := MakeEmptySetMultimap(s, bitwidth) + if err != nil { + return cid.Undef, err + } + return mm.Root() } // Returns the root cid of the underlying HAMT. @@ -55,7 +60,10 @@ func (mm *SetMultimap) Put(epoch abi.ChainEpoch, v abi.DealID) error { return err } if !found { - set = adt.MakeEmptySet(mm.store, mm.innerBitwidth) + set, err = adt.MakeEmptySet(mm.store, mm.innerBitwidth) + if err != nil { + return err + } } // Add to the set. @@ -84,7 +92,10 @@ func (mm *SetMultimap) PutMany(epoch abi.ChainEpoch, vs []abi.DealID) error { return err } if !found { - set = adt.MakeEmptySet(mm.store, mm.innerBitwidth) + set, err = adt.MakeEmptySet(mm.store, mm.innerBitwidth) + if err != nil { + return err + } } // Add to the set. @@ -109,8 +120,7 @@ func (mm *SetMultimap) PutMany(epoch abi.ChainEpoch, vs []abi.DealID) error { // Removes all values for a key. func (mm *SetMultimap) RemoveAll(key abi.ChainEpoch) error { - err := mm.mp.Delete(abi.UIntKey(uint64(key))) - if err != nil && !xerrors.Is(err, hamt.ErrNotFound) { + if _, err := mm.mp.TryDelete(abi.UIntKey(uint64(key))); err != nil { return xerrors.Errorf("failed to delete set key %v: %w", key, err) } return nil diff --git a/actors/builtin/market/types.go b/actors/builtin/market/types.go index 901d70795..2d6431110 100644 --- a/actors/builtin/market/types.go +++ b/actors/builtin/market/types.go @@ -38,8 +38,8 @@ func (t *DealArray) Set(k abi.DealID, value *DealProposal) error { return t.Array.Set(uint64(k), value) } -func (t *DealArray) Delete(key uint64) error { - return t.Array.Delete(key) +func (t *DealArray) Delete(id abi.DealID) error { + return t.Array.Delete(uint64(id)) } // A specialization of a array to deals. diff --git a/actors/builtin/miner/bitfield_queue.go b/actors/builtin/miner/bitfield_queue.go index 90b4d078a..b8d3938fa 100644 --- a/actors/builtin/miner/bitfield_queue.go +++ b/actors/builtin/miner/bitfield_queue.go @@ -83,7 +83,7 @@ func (q BitfieldQueue) Cut(toCut bitfield.BitField) error { }); err != nil { return xerrors.Errorf("failed to cut from bitfield queue: %w", err) } - if err := q.BatchDelete(epochsToRemove); err != nil { + if err := q.BatchDelete(epochsToRemove, true); err != nil { return xerrors.Errorf("failed to remove empty epochs from bitfield queue: %w", err) } return nil @@ -141,7 +141,7 @@ func (q BitfieldQueue) PopUntil(until abi.ChainEpoch) (values bitfield.BitField, return bitfield.New(), false, nil } - if err = q.BatchDelete(poppedKeys); err != nil { + if err = q.BatchDelete(poppedKeys, true); err != nil { return bitfield.BitField{}, false, err } merged, err := bitfield.MultiMerge(poppedValues...) diff --git a/actors/builtin/miner/deadline_state.go b/actors/builtin/miner/deadline_state.go index 646f393d9..95046ff17 100644 --- a/actors/builtin/miner/deadline_state.go +++ b/actors/builtin/miner/deadline_state.go @@ -1142,21 +1142,16 @@ func (dl *Deadline) TakePoStProofs(store adt.Store, idx uint64) (partitions bitf if err != nil { return bitfield.New(), nil, xerrors.Errorf("failed to load proofs: %w", err) } + + // Extract and remove the proof from the proofs array, leaving a hole. + // This will not affect concurrent attempts to refute other proofs. var post WindowedPoSt - found, err := proofArr.Get(idx, &post) - if err != nil { + if found, err := proofArr.Pop(idx, &post); err != nil { return bitfield.New(), nil, xerrors.Errorf("failed to retrieve proof %d: %w", idx, err) } else if !found { return bitfield.New(), nil, xc.ErrIllegalArgument.Wrapf("proof %d not found", idx) } - // Delete the proof from the proofs array, leaving a hole. - // This will not affect concurrent attempts to refute other proofs. - err = proofArr.Delete(idx) - if err != nil { - return bitfield.New(), nil, xerrors.Errorf("failed to delete proof %d: %w", idx, err) - } - root, err := proofArr.Root() if err != nil { return bitfield.New(), nil, xerrors.Errorf("failed to save proofs: %w", err) diff --git a/actors/builtin/miner/expiration_queue.go b/actors/builtin/miner/expiration_queue.go index 718daba4d..b37dabfcb 100644 --- a/actors/builtin/miner/expiration_queue.go +++ b/actors/builtin/miner/expiration_queue.go @@ -334,7 +334,7 @@ func (q ExpirationQueue) RescheduleAllAsFaults(faultExpiration abi.ChainEpoch) e } // Trim the rescheduled epochs from the queue. - if err = q.BatchDelete(rescheduledEpochs); err != nil { + if err = q.BatchDelete(rescheduledEpochs, true); err != nil { return err } @@ -564,7 +564,7 @@ func (q ExpirationQueue) PopUntil(until abi.ChainEpoch) (*ExpirationSet, error) return NewExpirationSetEmpty(), nil } - if err := q.Array.BatchDelete(poppedKeys); err != nil { + if err := q.Array.BatchDelete(poppedKeys, true); err != nil { return nil, err } @@ -674,7 +674,7 @@ func (q ExpirationQueue) traverseMutate(f func(epoch abi.ChainEpoch, es *Expirat }); err != nil && err != errStop { return err } - if err := q.Array.BatchDelete(epochsEmptied); err != nil { + if err := q.Array.BatchDelete(epochsEmptied, true); err != nil { return err } return nil diff --git a/actors/builtin/miner/partition_state.go b/actors/builtin/miner/partition_state.go index 112a18843..72a48960f 100644 --- a/actors/builtin/miner/partition_state.go +++ b/actors/builtin/miner/partition_state.go @@ -803,7 +803,7 @@ func (p *Partition) PopEarlyTerminations(store adt.Store, maxSectors uint64) (re } // Update early terminations - err = earlyTerminatedQ.BatchDelete(processed) + err = earlyTerminatedQ.BatchDelete(processed, true) if err != nil { return TerminationResult{}, false, xerrors.Errorf("failed to remove entries from early terminations queue: %w", err) } diff --git a/actors/builtin/multisig/multisig_actor.go b/actors/builtin/multisig/multisig_actor.go index b09ce0835..b91f5b347 100644 --- a/actors/builtin/multisig/multisig_actor.go +++ b/actors/builtin/multisig/multisig_actor.go @@ -270,10 +270,13 @@ func (a Actor) Cancel(rt runtime.Runtime, params *TxnIDParams) *abi.EmptyValue { ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending txns") - txn, err := getPendingTransaction(ptx, params.ID) - if err != nil { - rt.Abortf(exitcode.ErrNotFound, "failed to get transaction for cancel: %v", err) + var txn Transaction + found, err := ptx.Pop(params.ID, &txn) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to pop transaction %v for cancel", params.ID) + if !found { + rt.Abortf(exitcode.ErrNotFound, "no such transaction %v to cancel", params.ID) } + proposer := txn.Approved[0] if proposer != callerAddr { rt.Abortf(exitcode.ErrForbidden, "Cannot cancel another signers transaction") @@ -286,9 +289,6 @@ func (a Actor) Cancel(rt runtime.Runtime, params *TxnIDParams) *abi.EmptyValue { rt.Abortf(exitcode.ErrIllegalState, "hash does not match proposal params (ensure requester is an ID address)") } - err = ptx.Delete(params.ID) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete pending transaction") - st.PendingTxns, err = ptx.Root() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") }) @@ -496,13 +496,12 @@ func (a Actor) approveTransaction(rt runtime.Runtime, txnID TxnID, txn *Transact } func getTransaction(rt runtime.Runtime, ptx *adt.Map, txnID TxnID, proposalHash []byte, checkHash bool) *Transaction { - var txn Transaction - // get transaction from the state trie - var err error - txn, err = getPendingTransaction(ptx, txnID) - if err != nil { - rt.Abortf(exitcode.ErrNotFound, "failed to get transaction for approval: %v", err) + var txn Transaction + found, err := ptx.Get(txnID, &txn) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load transaction %v for approval", txnID) + if !found { + rt.Abortf(exitcode.ErrNotFound, "no such transaction %v for approval", txnID) } // confirm the hashes match @@ -544,34 +543,12 @@ func executeTransactionIfApproved(rt runtime.Runtime, st State, txnID TxnID, txn ptx, err := adt.AsMap(adt.AsStore(rt), st.PendingTxns, builtin.DefaultHamtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load pending transactions") - txnExists, err := ptx.Has(txnID) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check existance of transaction %v for cleanup", txnID) - if !txnExists { - // do nothing - return - } - - err = ptx.Delete(txnID) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete transaction %v for cleanup", txnID) - - /* // Prior to version 6 we attempt to delete all transactions, even those - // no longer in the pending txns map because they have been purged. - shouldDelete := true - // Starting at version 6 we first check if the transaction exists before - // deleting. This allows 1 out of n multisig swaps and removes initiated - // by the swapped/removed signer to go through without an illegal state error - if nv >= network.Version6 { - txnExists, err := ptx.Has(txnID) - builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to check existance of transaction %v for cleanup", txnID) - shouldDelete = txnExists + // Allow transaction not to be found when deleting. + // This allows 1 out of n multisig swaps and removes initiated by the swapped/removed signer to go through cleanly. + if _, err := ptx.TryDelete(txnID); err != nil { + rt.Abortf(exitcode.ErrIllegalState, "failed to delete transaction for cleanup: %v", err) } - if shouldDelete { - if err := ptx.Delete(txnID); err != nil { - rt.Abortf(exitcode.ErrIllegalState, "failed to delete transaction for cleanup: %v", err) - } - } */ - st.PendingTxns, err = ptx.Root() builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush pending transactions") }) diff --git a/actors/builtin/multisig/multisig_state.go b/actors/builtin/multisig/multisig_state.go index 4d5f3c43d..6e012c8e1 100644 --- a/actors/builtin/multisig/multisig_state.go +++ b/actors/builtin/multisig/multisig_state.go @@ -4,7 +4,6 @@ import ( address "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" - exitcode "github.com/filecoin-project/go-state-types/exitcode" cid "github.com/ipfs/go-cid" "golang.org/x/xerrors" @@ -144,18 +143,6 @@ func (st *State) assertAvailable(currBalance abi.TokenAmount, amountToSpend abi. return nil } -func getPendingTransaction(ptx *adt.Map, txnID TxnID) (Transaction, error) { - var out Transaction - found, err := ptx.Get(txnID, &out) - if err != nil { - return Transaction{}, xerrors.Errorf("failed to read transaction: %w", err) - } - if !found { - return Transaction{}, exitcode.ErrNotFound.Wrapf("failed to find transaction %v", txnID) - } - return out, nil -} - // An adt.Map key that just preserves the underlying string. type StringKey string diff --git a/actors/builtin/power/power_actor.go b/actors/builtin/power/power_actor.go index 3f07600ef..11a31db9a 100644 --- a/actors/builtin/power/power_actor.go +++ b/actors/builtin/power/power_actor.go @@ -267,10 +267,11 @@ func (a Actor) SubmitPoRepForBulkVerify(rt Runtime, sealInfo *proof.SealVerifyIn store := adt.AsStore(rt) var mmap *adt.Multimap + var err error if st.ProofValidationBatch == nil { - mmap = adt.MakeEmptyMultimap(store, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) + mmap, err = adt.MakeEmptyMultimap(store, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to create empty proof validation set") } else { - var err error mmap, err = adt.AsMultimap(adt.AsStore(rt), *st.ProofValidationBatch, builtin.DefaultHamtBitwidth, ProofValidationBatchAmtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load proof batch set") } @@ -486,10 +487,13 @@ func (a Actor) processDeferredCronEvents(rt Runtime) { // Remove miner claim and leave miner frozen for _, minerAddr := range failedMinerCrons { - err := st.deleteClaim(claims, minerAddr) + found, err := st.deleteClaim(claims, minerAddr) if err != nil { rt.Log(rtt.ERROR, "failed to delete claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err) continue + } else if !found { + rt.Log(rtt.ERROR, "can't find claim for miner %s after failing OnDeferredCronEvent: %s", minerAddr, err) + continue } // Decrement miner count to keep stats consistent. diff --git a/actors/builtin/power/power_state.go b/actors/builtin/power/power_state.go index 31d98b36e..244328ad8 100644 --- a/actors/builtin/power/power_state.go +++ b/actors/builtin/power/power_state.go @@ -1,6 +1,7 @@ package power import ( + "fmt" "reflect" addr "github.com/filecoin-project/go-address" @@ -93,7 +94,7 @@ func ConstructState(store adt.Store) (*State, error) { if err != nil { return nil, xerrors.Errorf("failed to create empty claim map: %w", err) } - emptyCronQueueMMapCid, err := adt.MakeEmptyMultimap(store, CronQueueHamtBitwidth, CronQueueAmtBitwidth).Root() + emptyCronQueueMMapCid, err := adt.StoreEmptyMultimap(store, CronQueueHamtBitwidth, CronQueueAmtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty multimap: %w", err) } @@ -262,23 +263,25 @@ func (st *State) updateStatsForNewMiner(windowPoStProof abi.RegisteredPoStProof) return nil } -func (st *State) deleteClaim(claims *adt.Map, miner addr.Address) error { +func (st *State) deleteClaim(claims *adt.Map, miner addr.Address) (bool, error) { + // Note: this flow loads the claim multiple times, unnecessarily. + // We should refactor to use claims.Pop(). oldClaim, ok, err := getClaim(claims, miner) if err != nil { - return xerrors.Errorf("failed to get claim: %w", err) + return false, fmt.Errorf("failed to get claim: %w", err) } if !ok { - return nil // no record, we're done + return false, nil // no record, we're done } // subtract from stats as if we were simply removing power err = st.addToClaim(claims, miner, oldClaim.RawBytePower.Neg(), oldClaim.QualityAdjPower.Neg(), oldClaim.TotalMiningPledge.Neg()) if err != nil { - return xerrors.Errorf("failed to subtract miner power before deleting claim: %w", err) + return false, fmt.Errorf("failed to subtract miner power before deleting claim: %w", err) } // delete claim from state to invalidate miner - return claims.Delete(abi.AddrKey(miner)) + return true, claims.Delete(abi.AddrKey(miner)) } func getClaim(claims *adt.Map, a addr.Address) (*Claim, bool, error) { diff --git a/actors/builtin/retrieval/retrieval_state.go b/actors/builtin/retrieval/retrieval_state.go index 7bfacc489..a3d376c41 100644 --- a/actors/builtin/retrieval/retrieval_state.go +++ b/actors/builtin/retrieval/retrieval_state.go @@ -53,11 +53,11 @@ type LockedState struct { // ConstructState retrieval construct func ConstructState(store adt.Store) (*State, error) { - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } - emptyRetrievalBatchMMapCid, err := adt.MakeEmptyMultimap(store, builtin.DefaultHamtBitwidth, builtin.DefaultHamtBitwidth).Root() + emptyRetrievalBatchMMapCid, err := adt.StoreEmptyMultimap(store, builtin.DefaultHamtBitwidth, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty multi map: %w", err) } diff --git a/actors/builtin/verifreg/verified_registry_actor.go b/actors/builtin/verifreg/verified_registry_actor.go index be59c1d26..dfe705bf3 100644 --- a/actors/builtin/verifreg/verified_registry_actor.go +++ b/actors/builtin/verifreg/verified_registry_actor.go @@ -1,316 +1,319 @@ package verifreg -// import ( -// addr "github.com/filecoin-project/go-address" -// "github.com/filecoin-project/go-state-types/abi" -// "github.com/filecoin-project/go-state-types/cbor" -// "github.com/ipfs/go-cid" - -// "github.com/filecoin-project/go-state-types/big" -// "github.com/filecoin-project/go-state-types/exitcode" - -// "github.com/filecoin-project/specs-actors/v2/actors/builtin" -// "github.com/filecoin-project/specs-actors/v2/actors/runtime" -// . "github.com/filecoin-project/specs-actors/v2/actors/util" -// "github.com/filecoin-project/specs-actors/v2/actors/util/adt" -// ) - -// type Actor struct{} - -// func (a Actor) Exports() []interface{} { -// return []interface{}{ -// builtin.MethodConstructor: a.Constructor, -// 2: a.AddVerifier, -// 3: a.RemoveVerifier, -// 4: a.AddVerifiedClient, -// 5: a.UseBytes, -// 6: a.RestoreBytes, -// } -// } - -// func (a Actor) Code() cid.Cid { -// return builtin.VerifiedRegistryActorCodeID -// } - -// func (a Actor) IsSingleton() bool { -// return true -// } - -// func (a Actor) State() cbor.Er { -// return new(State) -// } - -// var _ runtime.VMActor = Actor{} - -// //////////////////////////////////////////////////////////////////////////////// -// // Actor methods -// //////////////////////////////////////////////////////////////////////////////// - -// func (a Actor) Constructor(rt runtime.Runtime, rootKey *addr.Address) *abi.EmptyValue { -// rt.ValidateImmediateCallerIs(builtin.SystemActorAddr) - -// // root should be an ID address -// idAddr, ok := rt.ResolveAddress(*rootKey) -// builtin.RequireParam(rt, ok, "root should be an ID address") - -// emptyMap, err := adt.MakeEmptyMap(adt.AsStore(rt)).Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to create state") - -// st := ConstructState(emptyMap, idAddr) -// rt.StateCreate(st) -// return nil -// } - -// type AddVerifierParams struct { -// Address addr.Address -// Allowance DataCap -// } - -// func (a Actor) AddVerifier(rt runtime.Runtime, params *AddVerifierParams) *abi.EmptyValue { -// if params.Allowance.LessThan(MinVerifiedDealSize) { -// rt.Abortf(exitcode.ErrIllegalArgument, "Allowance %d below MinVerifiedDealSize for add verifier %v", params.Allowance, params.Address) -// } - -// verifier, err := builtin.ResolveToIDAddr(rt, params.Address) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verifier address %v to ID address", params.Address) - -// var st State -// rt.StateReadonly(&st) -// rt.ValidateImmediateCallerIs(st.RootKey) - -// if verifier == st.RootKey { -// rt.Abortf(exitcode.ErrIllegalArgument, "Rootkey cannot be added as verifier") -// } -// rt.StateTransaction(&st, func() { -// verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") - -// verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") - -// // A verified client cannot become a verifier -// found, err := verifiedClients.Get(abi.AddrKey(verifier), nil) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed get verified client state for %v", verifier) -// if found { -// rt.Abortf(exitcode.ErrIllegalArgument, "verified client %v cannot become a verifier", verifier) -// } - -// err = verifiers.Put(abi.AddrKey(verifier), ¶ms.Allowance) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to add verifier") - -// st.Verifiers, err = verifiers.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") -// }) - -// return nil -// } - -// func (a Actor) RemoveVerifier(rt runtime.Runtime, verifierAddr *addr.Address) *abi.EmptyValue { -// verifier, err := builtin.ResolveToIDAddr(rt, *verifierAddr) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verifier address %v to ID address", *verifierAddr) - -// var st State -// rt.StateReadonly(&st) -// rt.ValidateImmediateCallerIs(st.RootKey) - -// rt.StateTransaction(&st, func() { -// verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") - -// err = verifiers.Delete(abi.AddrKey(verifier)) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to remove verifier") - -// st.Verifiers, err = verifiers.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") -// }) - -// return nil -// } - -// type AddVerifiedClientParams struct { -// Address addr.Address -// Allowance DataCap -// } - -// func (a Actor) AddVerifiedClient(rt runtime.Runtime, params *AddVerifiedClientParams) *abi.EmptyValue { -// // The caller will be verified by checking the verifiers table below. -// rt.ValidateImmediateCallerAcceptAny() - -// if params.Allowance.LessThan(MinVerifiedDealSize) { -// rt.Abortf(exitcode.ErrIllegalArgument, "allowance %d below MinVerifiedDealSize for add verified client %v", params.Allowance, params.Address) -// } - -// client, err := builtin.ResolveToIDAddr(rt, params.Address) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client address %v", params.Address) - -// var st State -// rt.StateReadonly(&st) -// if st.RootKey == client { -// rt.Abortf(exitcode.ErrIllegalArgument, "Rootkey cannot be added as a verified client") -// } - -// rt.StateTransaction(&st, func() { -// verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") - -// verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") - -// // Validate caller is one of the verifiers. -// verifier := rt.Caller() -// var verifierCap DataCap -// found, err := verifiers.Get(abi.AddrKey(verifier), &verifierCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verifier %v", verifier) -// if !found { -// rt.Abortf(exitcode.ErrNotFound, "no such verifier %v", verifier) -// } - -// // Validate client to be added isn't a verifier -// found, err = verifiers.Get(abi.AddrKey(client), nil) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verifier") -// if found { -// rt.Abortf(exitcode.ErrIllegalArgument, "verifier %v cannot be added as a verified client", client) -// } - -// // Compute new verifier cap and update. -// if verifierCap.LessThan(params.Allowance) { -// rt.Abortf(exitcode.ErrIllegalArgument, "add more DataCap (%d) for VerifiedClient than allocated %d", params.Allowance, verifierCap) -// } -// newVerifierCap := big.Sub(verifierCap, params.Allowance) - -// err = verifiers.Put(abi.AddrKey(verifier), &newVerifierCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update new verifier cap (%d) for %v", newVerifierCap, verifier) - -// // This is a one-time, upfront allocation. -// // This allowance cannot be changed by calls to AddVerifiedClient as long as the client has not been removed. -// // If parties need more allowance, they need to create a new verified client or use up the the current allowance -// // and then create a new verified client. -// found, err = verifiedClients.Get(abi.AddrKey(client), &verifierCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) -// if found { -// rt.Abortf(exitcode.ErrIllegalArgument, "verified client already exists: %v", client) -// } - -// err = verifiedClients.Put(abi.AddrKey(client), ¶ms.Allowance) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to add verified client %v with cap %d", client, params.Allowance) - -// st.Verifiers, err = verifiers.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") - -// st.VerifiedClients, err = verifiedClients.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verified clients") -// }) - -// return nil -// } - -// type UseBytesParams struct { -// Address addr.Address // Address of verified client. -// DealSize abi.StoragePower // Number of bytes to use. -// } - -// // Called by StorageMarketActor during PublishStorageDeals. -// // Do not allow partially verified deals (DealSize must be greater than equal to allowed cap). -// // Delete VerifiedClient if remaining DataCap is smaller than minimum VerifiedDealSize. -// func (a Actor) UseBytes(rt runtime.Runtime, params *UseBytesParams) *abi.EmptyValue { -// rt.ValidateImmediateCallerIs(builtin.StorageMarketActorAddr) - -// client, err := builtin.ResolveToIDAddr(rt, params.Address) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client address %v", params.Address) - -// if params.DealSize.LessThan(MinVerifiedDealSize) { -// rt.Abortf(exitcode.ErrIllegalArgument, "VerifiedDealSize: %d below minimum in UseBytes", params.DealSize) -// } - -// var st State -// rt.StateTransaction(&st, func() { -// verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") - -// var vcCap DataCap -// found, err := verifiedClients.Get(abi.AddrKey(client), &vcCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) -// if !found { -// rt.Abortf(exitcode.ErrNotFound, "no such verified client %v", client) -// } -// Assert(vcCap.GreaterThanEqual(big.Zero())) - -// if params.DealSize.GreaterThan(vcCap) { -// rt.Abortf(exitcode.ErrIllegalArgument, "DealSize %d exceeds allowable cap: %d for VerifiedClient %v", params.DealSize, vcCap, client) -// } - -// newVcCap := big.Sub(vcCap, params.DealSize) -// if newVcCap.LessThan(MinVerifiedDealSize) { -// // Delete entry if remaining DataCap is less than MinVerifiedDealSize. -// // Will be restored later if the deal did not get activated with a ProvenSector. -// // -// // NOTE: Technically, client could lose up to MinVerifiedDealSize worth of DataCap. -// // See: https://github.com/filecoin-project/specs-actors/issues/727 -// err = verifiedClients.Delete(abi.AddrKey(client)) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete verified client %v", client) -// } else { -// err = verifiedClients.Put(abi.AddrKey(client), &newVcCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update verified client %v with %v", client, newVcCap) -// } - -// st.VerifiedClients, err = verifiedClients.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verified clients") -// }) - -// return nil -// } - -// type RestoreBytesParams struct { -// Address addr.Address -// DealSize abi.StoragePower -// } - -// // Called by HandleInitTimeoutDeals from StorageMarketActor when a VerifiedDeal fails to init. -// // Restore allowable cap for the client, creating new entry if the client has been deleted. -// func (a Actor) RestoreBytes(rt runtime.Runtime, params *RestoreBytesParams) *abi.EmptyValue { -// rt.ValidateImmediateCallerIs(builtin.StorageMarketActorAddr) - -// if params.DealSize.LessThan(MinVerifiedDealSize) { -// rt.Abortf(exitcode.ErrIllegalArgument, "Below minimum VerifiedDealSize requested in RestoreBytes: %d", params.DealSize) -// } - -// client, err := builtin.ResolveToIDAddr(rt, params.Address) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client addr %v", params.Address) - -// var st State -// rt.StateReadonly(&st) -// if st.RootKey == client { -// rt.Abortf(exitcode.ErrIllegalArgument, "Cannot restore allowance for Rootkey") -// } - -// rt.StateTransaction(&st, func() { -// verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") - -// verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") - -// // validate we are NOT attempting to do this for a verifier -// found, err := verifiers.Get(abi.AddrKey(client), nil) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed tp get verifier") -// if found { -// rt.Abortf(exitcode.ErrIllegalArgument, "cannot restore allowance for a verifier") -// } - -// var vcCap DataCap -// found, err = verifiedClients.Get(abi.AddrKey(client), &vcCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) -// if !found { -// vcCap = big.Zero() -// } - -// newVcCap := big.Add(vcCap, params.DealSize) -// err = verifiedClients.Put(abi.AddrKey(client), &newVcCap) -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to put verified client %v with %v", client, newVcCap) - -// st.VerifiedClients, err = verifiedClients.Root() -// builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") -// }) - -// return nil -// } +/*import ( + addr "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/cbor" + "github.com/ipfs/go-cid" + + "github.com/filecoin-project/go-state-types/big" + "github.com/filecoin-project/go-state-types/exitcode" + verifreg0 "github.com/filecoin-project/specs-actors/actors/builtin/verifreg" + + "github.com/filecoin-project/specs-actors/v3/actors/builtin" + "github.com/filecoin-project/specs-actors/v3/actors/runtime" + "github.com/filecoin-project/specs-actors/v3/actors/util/adt" +) + +type Actor struct{} + +func (a Actor) Exports() []interface{} { + return []interface{}{ + builtin.MethodConstructor: a.Constructor, + 2: a.AddVerifier, + 3: a.RemoveVerifier, + 4: a.AddVerifiedClient, + 5: a.UseBytes, + 6: a.RestoreBytes, + } +} + +func (a Actor) Code() cid.Cid { + return builtin.VerifiedRegistryActorCodeID +} + +func (a Actor) IsSingleton() bool { + return true +} + +func (a Actor) State() cbor.Er { + return new(State) +} + +var _ runtime.VMActor = Actor{} + +//////////////////////////////////////////////////////////////////////////////// +// Actor methods +//////////////////////////////////////////////////////////////////////////////// + +func (a Actor) Constructor(rt runtime.Runtime, rootKey *addr.Address) *abi.EmptyValue { + rt.ValidateImmediateCallerIs(builtin.SystemActorAddr) + + // root should be an ID address + idAddr, ok := rt.ResolveAddress(*rootKey) + builtin.RequireParam(rt, ok, "root should be an ID address") + + st, err := ConstructState(adt.AsStore(rt), idAddr) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to construct state") + rt.StateCreate(st) + return nil +} + +//type AddVerifierParams struct { +// Address addr.Address +// Allowance DataCap +//} +type AddVerifierParams = verifreg0.AddVerifierParams + +func (a Actor) AddVerifier(rt runtime.Runtime, params *AddVerifierParams) *abi.EmptyValue { + if params.Allowance.LessThan(MinVerifiedDealSize) { + rt.Abortf(exitcode.ErrIllegalArgument, "Allowance %d below MinVerifiedDealSize for add verifier %v", params.Allowance, params.Address) + } + + verifier, err := builtin.ResolveToIDAddr(rt, params.Address) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verifier address %v to ID address", params.Address) + + var st State + rt.StateReadonly(&st) + rt.ValidateImmediateCallerIs(st.RootKey) + + if verifier == st.RootKey { + rt.Abortf(exitcode.ErrIllegalArgument, "Rootkey cannot be added as verifier") + } + rt.StateTransaction(&st, func() { + verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") + + verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") + + // A verified client cannot become a verifier + found, err := verifiedClients.Get(abi.AddrKey(verifier), nil) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed get verified client state for %v", verifier) + if found { + rt.Abortf(exitcode.ErrIllegalArgument, "verified client %v cannot become a verifier", verifier) + } + + err = verifiers.Put(abi.AddrKey(verifier), ¶ms.Allowance) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to add verifier") + + st.Verifiers, err = verifiers.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") + }) + + return nil +} + +func (a Actor) RemoveVerifier(rt runtime.Runtime, verifierAddr *addr.Address) *abi.EmptyValue { + verifier, err := builtin.ResolveToIDAddr(rt, *verifierAddr) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verifier address %v to ID address", *verifierAddr) + + var st State + rt.StateReadonly(&st) + rt.ValidateImmediateCallerIs(st.RootKey) + + rt.StateTransaction(&st, func() { + verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") + + found, err := verifiers.TryDelete(abi.AddrKey(verifier)) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to remove verifier") + builtin.RequireParam(rt, found, "no such verifier %v", verifierAddr) + + st.Verifiers, err = verifiers.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") + }) + + return nil +} + +//type AddVerifiedClientParams struct { +// Address addr.Address +// Allowance DataCap +//} +type AddVerifiedClientParams = verifreg0.AddVerifiedClientParams + +func (a Actor) AddVerifiedClient(rt runtime.Runtime, params *AddVerifiedClientParams) *abi.EmptyValue { + // The caller will be verified by checking the verifiers table below. + rt.ValidateImmediateCallerAcceptAny() + + if params.Allowance.LessThan(MinVerifiedDealSize) { + rt.Abortf(exitcode.ErrIllegalArgument, "allowance %d below MinVerifiedDealSize for add verified client %v", params.Allowance, params.Address) + } + + client, err := builtin.ResolveToIDAddr(rt, params.Address) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client address %v", params.Address) + + var st State + rt.StateReadonly(&st) + if st.RootKey == client { + rt.Abortf(exitcode.ErrIllegalArgument, "Rootkey cannot be added as a verified client") + } + + rt.StateTransaction(&st, func() { + verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") + + verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") + + // Validate caller is one of the verifiers. + verifier := rt.Caller() + var verifierCap DataCap + found, err := verifiers.Get(abi.AddrKey(verifier), &verifierCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verifier %v", verifier) + if !found { + rt.Abortf(exitcode.ErrNotFound, "no such verifier %v", verifier) + } + + // Validate client to be added isn't a verifier + found, err = verifiers.Get(abi.AddrKey(client), nil) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verifier") + if found { + rt.Abortf(exitcode.ErrIllegalArgument, "verifier %v cannot be added as a verified client", client) + } + + // Compute new verifier cap and update. + if verifierCap.LessThan(params.Allowance) { + rt.Abortf(exitcode.ErrIllegalArgument, "add more DataCap (%d) for VerifiedClient than allocated %d", params.Allowance, verifierCap) + } + newVerifierCap := big.Sub(verifierCap, params.Allowance) + + err = verifiers.Put(abi.AddrKey(verifier), &newVerifierCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update new verifier cap (%d) for %v", newVerifierCap, verifier) + + // This is a one-time, upfront allocation. + // This allowance cannot be changed by calls to AddVerifiedClient as long as the client has not been removed. + // If parties need more allowance, they need to create a new verified client or use up the the current allowance + // and then create a new verified client. + found, err = verifiedClients.Get(abi.AddrKey(client), nil) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) + if found { + rt.Abortf(exitcode.ErrIllegalArgument, "verified client already exists: %v", client) + } + + err = verifiedClients.Put(abi.AddrKey(client), ¶ms.Allowance) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to add verified client %v with cap %d", client, params.Allowance) + + st.Verifiers, err = verifiers.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verifiers") + + st.VerifiedClients, err = verifiedClients.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verified clients") + }) + + return nil +} + +//type UseBytesParams struct { +// Address addr.Address // Address of verified client. +// DealSize abi.StoragePower // Number of bytes to use. +//} +type UseBytesParams = verifreg0.UseBytesParams + +// Called by StorageMarketActor during PublishStorageDeals. +// Do not allow partially verified deals (DealSize must be greater than equal to allowed cap). +// Delete VerifiedClient if remaining DataCap is smaller than minimum VerifiedDealSize. +func (a Actor) UseBytes(rt runtime.Runtime, params *UseBytesParams) *abi.EmptyValue { + rt.ValidateImmediateCallerIs(builtin.StorageMarketActorAddr) + + client, err := builtin.ResolveToIDAddr(rt, params.Address) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client address %v", params.Address) + + if params.DealSize.LessThan(MinVerifiedDealSize) { + rt.Abortf(exitcode.ErrIllegalArgument, "VerifiedDealSize: %d below minimum in UseBytes", params.DealSize) + } + + var st State + rt.StateTransaction(&st, func() { + verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") + + var vcCap DataCap + found, err := verifiedClients.Get(abi.AddrKey(client), &vcCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) + if !found { + rt.Abortf(exitcode.ErrNotFound, "no such verified client %v", client) + } + builtin.RequireState(rt, vcCap.GreaterThanEqual(big.Zero()), "negative cap for client %v: %v", client, vcCap) + + if params.DealSize.GreaterThan(vcCap) { + rt.Abortf(exitcode.ErrIllegalArgument, "DealSize %d exceeds allowable cap: %d for VerifiedClient %v", params.DealSize, vcCap, client) + } + + newVcCap := big.Sub(vcCap, params.DealSize) + if newVcCap.LessThan(MinVerifiedDealSize) { + // Delete entry if remaining DataCap is less than MinVerifiedDealSize. + // Will be restored later if the deal did not get activated with a ProvenSector. + // + // NOTE: Technically, client could lose up to MinVerifiedDealSize worth of DataCap. + // See: https://github.com/filecoin-project/specs-actors/issues/727 + err = verifiedClients.Delete(abi.AddrKey(client)) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to delete verified client %v", client) + } else { + err = verifiedClients.Put(abi.AddrKey(client), &newVcCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to update verified client %v with %v", client, newVcCap) + } + + st.VerifiedClients, err = verifiedClients.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to flush verified clients") + }) + + return nil +} + +//type RestoreBytesParams struct { +// Address addr.Address +// DealSize abi.StoragePower +//} +type RestoreBytesParams = verifreg0.RestoreBytesParams + +// Called by HandleInitTimeoutDeals from StorageMarketActor when a VerifiedDeal fails to init. +// Restore allowable cap for the client, creating new entry if the client has been deleted. +func (a Actor) RestoreBytes(rt runtime.Runtime, params *RestoreBytesParams) *abi.EmptyValue { + rt.ValidateImmediateCallerIs(builtin.StorageMarketActorAddr) + + if params.DealSize.LessThan(MinVerifiedDealSize) { + rt.Abortf(exitcode.ErrIllegalArgument, "Below minimum VerifiedDealSize requested in RestoreBytes: %d", params.DealSize) + } + + client, err := builtin.ResolveToIDAddr(rt, params.Address) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to resolve verified client addr %v", params.Address) + + var st State + rt.StateReadonly(&st) + if st.RootKey == client { + rt.Abortf(exitcode.ErrIllegalArgument, "Cannot restore allowance for Rootkey") + } + + rt.StateTransaction(&st, func() { + verifiedClients, err := adt.AsMap(adt.AsStore(rt), st.VerifiedClients, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verified clients") + + verifiers, err := adt.AsMap(adt.AsStore(rt), st.Verifiers, builtin.DefaultHamtBitwidth) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") + + // validate we are NOT attempting to do this for a verifier + found, err := verifiers.Get(abi.AddrKey(client), nil) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed tp get verifier") + if found { + rt.Abortf(exitcode.ErrIllegalArgument, "cannot restore allowance for a verifier") + } + + var vcCap DataCap + found, err = verifiedClients.Get(abi.AddrKey(client), &vcCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get verified client %v", client) + if !found { + vcCap = big.Zero() + } + + newVcCap := big.Add(vcCap, params.DealSize) + err = verifiedClients.Put(abi.AddrKey(client), &newVcCap) + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to put verified client %v with %v", client, newVcCap) + + st.VerifiedClients, err = verifiedClients.Root() + builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to load verifiers") + }) + + return nil +} diff --git a/actors/builtin/verifreg/verified_registry_test.go b/actors/builtin/verifreg/verified_registry_test.go index 32d749d76..7681f17f0 100644 --- a/actors/builtin/verifreg/verified_registry_test.go +++ b/actors/builtin/verifreg/verified_registry_test.go @@ -194,7 +194,7 @@ func TestRemoveVerifier(t *testing.T) { rt.ExpectValidateCallerAddr(ac.rootkey) rt.SetCaller(ac.rootkey, builtin.VerifiedRegistryActorCodeID) v := tutil.NewIDAddr(t, 501) - rt.ExpectAbort(exitcode.ErrIllegalState, func() { + rt.ExpectAbort(exitcode.ErrIllegalArgument, func() { rt.Call(ac.RemoveVerifier, &v) }) ac.checkState(rt) diff --git a/actors/builtin/vote/vote_actor.go b/actors/builtin/vote/vote_actor.go index 37e0041da..3436f8bb0 100644 --- a/actors/builtin/vote/vote_actor.go +++ b/actors/builtin/vote/vote_actor.go @@ -114,7 +114,7 @@ func (a Actor) Vote(rt Runtime, candidate *addr.Address) *abi.EmptyValue { voter, found, err := getVoter(voters, rt.Caller()) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to get voter") if !found { - tally, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + tally, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) builtin.RequireNoErr(rt, err, exitcode.ErrIllegalState, "failed to make tally for voter") voter = &Voter{ SettleEpoch: rt.CurrEpoch(), diff --git a/actors/builtin/vote/vote_state.go b/actors/builtin/vote/vote_state.go index 6dba777da..d595777c4 100644 --- a/actors/builtin/vote/vote_state.go +++ b/actors/builtin/vote/vote_state.go @@ -79,7 +79,7 @@ func ConstructState(store adt.Store, fallback address.Address) (*State, error) { return nil, xerrors.New("fallback not a ID-Address") } - emptyMapCid, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth).Root() + emptyMapCid, err := adt.StoreEmptyMap(store, builtin.DefaultHamtBitwidth) if err != nil { return nil, xerrors.Errorf("failed to create empty map: %w", err) } diff --git a/actors/builtin/vote/vote_state_test.go b/actors/builtin/vote/vote_state_test.go new file mode 100644 index 000000000..1d3df4334 --- /dev/null +++ b/actors/builtin/vote/vote_state_test.go @@ -0,0 +1,83 @@ +package vote_test + +import ( + "context" + "testing" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/specs-actors/v2/actors/builtin" + "github.com/filecoin-project/specs-actors/v2/actors/builtin/vote" + "github.com/filecoin-project/specs-actors/v2/actors/util/adt" + "github.com/filecoin-project/specs-actors/v2/support/ipld" + "github.com/stretchr/testify/require" + + tutils "github.com/filecoin-project/specs-actors/v2/support/testing" +) + +func TestConstruct(t *testing.T) { + harness := constructStateHarness(t) + require.Equal(t, abi.NewTokenAmount(0), harness.s.CumEarningsPerVote) + require.Equal(t, abi.NewTokenAmount(0), harness.s.TotalVotes) + require.Equal(t, abi.NewTokenAmount(0), harness.s.UnownedFunds) + require.True(t, harness.s.FallbackReceiver != address.Undef) +} + +func TestBlockCandidates(t *testing.T) { + harness := constructStateHarness(t) + harness.setCumEarningsPerVote(abi.NewTokenAmount(100)) + + t.Run("candidate not found", func(t *testing.T) { + candidates, err := adt.AsMap(harness.store, harness.s.Candidates, builtin.DefaultHamtBitwidth) + require.NoError(t, err) + + _, err = harness.s.BlockCandidates(candidates, map[address.Address]struct{}{ + tutils.NewBLSAddr(t, 2): {}, + }, abi.ChainEpoch(1)) + require.Error(t, err) + require.Contains(t, err.Error(), "candidate not found") + }) +} + +type stateHarness struct { + t testing.TB + + s *vote.State + store adt.Store + + fallback address.Address +} + +func (h *stateHarness) setCumEarningsPerVote(val abi.TokenAmount) { + h.s.CumEarningsPerVote = val +} + +func (h *stateHarness) blockCandidates(curr abi.ChainEpoch, addrs ...address.Address) { + m := make(map[address.Address]struct{}) + for _, addr := range addrs { + m[addr] = struct{}{} + } + candidates, err := adt.AsMap(h.store, h.s.Candidates, builtin.DefaultHamtBitwidth) + require.NoError(h.t, err) + + n, err := h.s.BlockCandidates(candidates, m, curr) + require.NoError(h.t, err) + require.True(h.t, n == len(addrs)) + + h.s.Candidates, err = candidates.Root() + require.NoError(h.t, err) +} + +func constructStateHarness(t *testing.T) *stateHarness { + store := ipld.NewADTStore(context.Background()) + fallback := tutils.NewIDAddr(t, 1) + state, err := vote.ConstructState(store, fallback) + require.NoError(t, err) + + return &stateHarness{ + t: t, + s: state, + store: store, + fallback: fallback, + } +} diff --git a/actors/builtin/vote/vote_test.go b/actors/builtin/vote/vote_test.go new file mode 100644 index 000000000..e6458657b --- /dev/null +++ b/actors/builtin/vote/vote_test.go @@ -0,0 +1,12 @@ +package vote_test + +import ( + "testing" + + "github.com/filecoin-project/specs-actors/v2/actors/builtin/vote" + "github.com/filecoin-project/specs-actors/v2/support/mock" +) + +func TestExports(t *testing.T) { + mock.CheckActorExports(t, vote.Actor{}) +} diff --git a/actors/migration/nv10/market.go b/actors/migration/nv10/market.go index 18e15cbc8..d3ccccdbf 100644 --- a/actors/migration/nv10/market.go +++ b/actors/migration/nv10/market.go @@ -79,7 +79,10 @@ func (a marketMigrator) MapPendingProposals(ctx context.Context, store cbor.Ipld return cid.Undef, err } - newPendingProposals := adt3.MakeEmptySet(adt3.WrapStore(ctx, store), builtin3.DefaultHamtBitwidth) + newPendingProposals, err := adt3.MakeEmptySet(adt3.WrapStore(ctx, store), builtin3.DefaultHamtBitwidth) + if err != nil { + return cid.Undef, err + } err = oldPendingProposals.ForEach(nil, func(key string) error { return newPendingProposals.Put(StringKey(key)) diff --git a/actors/migration/nv10/power.go b/actors/migration/nv10/power.go index badfed92c..2c70b8891 100644 --- a/actors/migration/nv10/power.go +++ b/actors/migration/nv10/power.go @@ -76,7 +76,10 @@ func (m powerMigrator) migrateClaims(ctx context.Context, store cbor.IpldStore, if err != nil { return cid.Undef, err } - outClaims := adt3.MakeEmptyMap(astore, builtin3.DefaultHamtBitwidth) + outClaims, err := adt3.MakeEmptyMap(astore, builtin3.DefaultHamtBitwidth) + if err != nil { + return cid.Undef, err + } var inClaim power2.Claim if err = inClaims.ForEach(&inClaim, func(key string) error { diff --git a/actors/migration/nv10/util.go b/actors/migration/nv10/util.go index af9087ab8..0f63a8f94 100644 --- a/actors/migration/nv10/util.go +++ b/actors/migration/nv10/util.go @@ -27,7 +27,10 @@ func migrateHAMTRaw(ctx context.Context, store cbor.IpldStore, root cid.Cid, new } newOpts := append(adt3.DefaultHamtOptions, hamt3.UseTreeBitWidth(newBitwidth)) - outRootNode := hamt3.NewNode(store, newOpts...) + outRootNode, err := hamt3.NewNode(store, newOpts...) + if err != nil { + return cid.Undef, err + } if err = inRootNode.ForEach(ctx, func(k string, val interface{}) error { return outRootNode.SetRaw(ctx, k, val.(*cbg.Deferred).Raw) @@ -49,7 +52,7 @@ func migrateAMTRaw(ctx context.Context, store cbor.IpldStore, root cid.Cid, newB return cid.Undef, err } - newOpts := append(adt3.DefaultAmtOptions, amt3.UseTreeBitWidth(newBitwidth)) + newOpts := append(adt3.DefaultAmtOptions, amt3.UseTreeBitWidth(uint(newBitwidth))) outRootNode, err := amt3.NewAMT(store, newOpts...) if err != nil { return cid.Undef, err @@ -72,7 +75,10 @@ func migrateHAMTHAMTRaw(ctx context.Context, store cbor.IpldStore, root cid.Cid, } newOptsOuter := append(adt3.DefaultHamtOptions, hamt3.UseTreeBitWidth(newOuterBitwidth)) - outRootNodeOuter := hamt3.NewNode(store, newOptsOuter...) + outRootNodeOuter, err := hamt3.NewNode(store, newOptsOuter...) + if err != nil { + return cid.Undef, err + } if err = inRootNodeOuter.ForEach(ctx, func(k string, val interface{}) error { var inInner cbg.CborCid @@ -102,7 +108,10 @@ func migrateHAMTAMTRaw(ctx context.Context, store cbor.IpldStore, root cid.Cid, return cid.Undef, err } newOptsOuter := append(adt3.DefaultHamtOptions, hamt3.UseTreeBitWidth(newOuterBitwidth)) - outRootNodeOuter := hamt3.NewNode(store, newOptsOuter...) + outRootNodeOuter, err := hamt3.NewNode(store, newOptsOuter...) + if err != nil { + return cid.Undef, err + } if err = inRootNodeOuter.ForEach(ctx, func(k string, val interface{}) error { var inInner cbg.CborCid diff --git a/actors/states/tree.go b/actors/states/tree.go index 6c6d85f1e..60bfbdf79 100644 --- a/actors/states/tree.go +++ b/actors/states/tree.go @@ -31,7 +31,10 @@ type Tree struct { // Initializes a new, empty state tree backed by a store. func NewTree(store adt.Store) (*Tree, error) { - emptyMap := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + emptyMap, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + if err != nil { + return nil, err + } return &Tree{ Map: emptyMap, Store: store, diff --git a/actors/util/adt/array.go b/actors/util/adt/array.go index 0cf9696ad..c7739eb83 100644 --- a/actors/util/adt/array.go +++ b/actors/util/adt/array.go @@ -7,7 +7,6 @@ import ( "github.com/filecoin-project/go-state-types/cbor" cid "github.com/ipfs/go-cid" - errors "github.com/pkg/errors" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" ) @@ -22,7 +21,7 @@ type Array struct { // AsArray interprets a store as an AMT-based array with root `r`. func AsArray(s Store, r cid.Cid, bitwidth int) (*Array, error) { - options := append(DefaultAmtOptions, amt.UseTreeBitWidth(bitwidth)) + options := append(DefaultAmtOptions, amt.UseTreeBitWidth(uint(bitwidth))) root, err := amt.LoadAMT(s.Context(), s, r, options...) if err != nil { return nil, xerrors.Errorf("failed to root: %w", err) @@ -36,7 +35,7 @@ func AsArray(s Store, r cid.Cid, bitwidth int) (*Array, error) { // Creates a new array backed by an empty AMT. func MakeEmptyArray(s Store, bitwidth int) (*Array, error) { - options := append(DefaultAmtOptions, amt.UseTreeBitWidth(bitwidth)) + options := append(DefaultAmtOptions, amt.UseTreeBitWidth(uint(bitwidth))) root, err := amt.NewAMT(s, options...) if err != nil { return nil, err @@ -65,28 +64,41 @@ func (a *Array) Root() (cid.Cid, error) { // If the array isn't continuous use Set and a separate counter func (a *Array) AppendContinuous(value cbor.Marshaler) error { if err := a.root.Set(a.store.Context(), a.root.Len(), value); err != nil { - return errors.Wrapf(err, "array append failed to set index %v value %v in root %v, ", a.root.Len(), value, a.root) + return xerrors.Errorf("append failed to set index %v value %v in root %v: %w", a.root.Len(), value, a.root, err) } return nil } func (a *Array) Set(i uint64, value cbor.Marshaler) error { if err := a.root.Set(a.store.Context(), i, value); err != nil { - return xerrors.Errorf("array set failed to set index %v in root %v: %w", i, a.root, err) + return xerrors.Errorf("failed to set index %v value %v in root %v: %w", i, value, a.root, err) } return nil } +// Removes the value at index `i` from the AMT, if it exists. +// Returns whether the index was previously present. +func (a *Array) TryDelete(i uint64) (bool, error) { + if found, err := a.root.Delete(a.store.Context(), i); err != nil { + return false, xerrors.Errorf("array delete failed to delete index %v in root %v: %w", i, a.root, err) + } else { + return found, nil + } +} + +// Removes the value at index `i` from the AMT, expecting it to exist. func (a *Array) Delete(i uint64) error { - if err := a.root.Delete(a.store.Context(), i); err != nil { - return xerrors.Errorf("array delete failed to delete index %v in root %v: %w", i, a.root, err) + if found, err := a.root.Delete(a.store.Context(), i); err != nil { + return xerrors.Errorf("failed to delete index %v in root %v: %w", i, a.root, err) + } else if !found { + return xerrors.Errorf("no such index %v in root %v to delete: %w", i, a.root, err) } return nil } -func (a *Array) BatchDelete(ix []uint64) error { - if err := a.root.BatchDelete(a.store.Context(), ix); err != nil { - return xerrors.Errorf("array delete failed to batchdelete: %w", err) +func (a *Array) BatchDelete(ix []uint64, strict bool) error { + if _, err := a.root.BatchDelete(a.store.Context(), ix, strict); err != nil { + return xerrors.Errorf("failed to batch delete keys %v: %w", ix, err) } return nil } @@ -115,12 +127,26 @@ func (a *Array) Length() uint64 { // Get retrieves array element into the 'out' unmarshaler, returning a boolean // indicating whether the element was found in the array func (a *Array) Get(k uint64, out cbor.Unmarshaler) (bool, error) { + if found, err := a.root.Get(a.store.Context(), k, out); err != nil { + return false, xerrors.Errorf("failed to get index %v in root %v: %w", k, a.root, err) + } else { + return found, nil + } +} - if err := a.root.Get(a.store.Context(), k, out); err == nil { - return true, nil - } else if _, nf := err.(*amt.ErrNotFound); nf { +// Retrieves an array value into the 'out' unmarshaler (if non-nil), and removes the entry. +// Returns a boolean indicating whether the element was previously in the array. +func (a *Array) Pop(k uint64, out cbor.Unmarshaler) (bool, error) { + if found, err := a.root.Get(a.store.Context(), k, out); err != nil { + return false, xerrors.Errorf("failed to get index %v in root %v: %w", k, a.root, err) + } else if !found { return false, nil - } else { - return false, err } + + if found, err := a.root.Delete(a.store.Context(), k); err != nil { + return false, xerrors.Errorf("failed to delete index %v in root %v: %w", k, a.root, err) + } else if !found { + return false, xerrors.Errorf("can't find index %v to delete in root %v", k, a.root) + } + return true, nil } diff --git a/actors/util/adt/balancetable_test.go b/actors/util/adt/balancetable_test.go index 70c8b5939..7ab7eacda 100644 --- a/actors/util/adt/balancetable_test.go +++ b/actors/util/adt/balancetable_test.go @@ -20,7 +20,8 @@ func TestBalanceTable(t *testing.T) { buildBalanceTable := func() *adt.BalanceTable { rt := mock.NewBuilder(context.Background(), address.Undef).Build(t) store := adt.AsStore(rt) - emptyMap := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + emptyMap, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + require.NoError(t, err) bt, err := adt.AsBalanceTable(store, tutil.MustRoot(t, emptyMap)) require.NoError(t, err) @@ -129,7 +130,8 @@ func TestSubtractWithMinimum(t *testing.T) { buildBalanceTable := func() *adt.BalanceTable { rt := mock.NewBuilder(context.Background(), address.Undef).Build(t) store := adt.AsStore(rt) - emptyMap := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + emptyMap, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + require.NoError(t, err) bt, err := adt.AsBalanceTable(store, tutil.MustRoot(t, emptyMap)) require.NoError(t, err) diff --git a/actors/util/adt/map.go b/actors/util/adt/map.go index b898be544..4ba2a3136 100644 --- a/actors/util/adt/map.go +++ b/actors/util/adt/map.go @@ -8,7 +8,6 @@ import ( "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/cbor" cid "github.com/ipfs/go-cid" - errors "github.com/pkg/errors" cbg "github.com/whyrusleeping/cbor-gen" "golang.org/x/xerrors" ) @@ -47,19 +46,25 @@ func AsMap(s Store, root cid.Cid, bitwidth int) (*Map, error) { } // Creates a new map backed by an empty HAMT. -func MakeEmptyMap(s Store, bitwidth int) *Map { +func MakeEmptyMap(s Store, bitwidth int) (*Map, error) { options := append(DefaultHamtOptions, hamt.UseTreeBitWidth(bitwidth)) - nd := hamt.NewNode(s, options...) + nd, err := hamt.NewNode(s, options...) + if err != nil { + return nil, err + } return &Map{ lastCid: cid.Undef, root: nd, store: s, - } + }, nil } // Creates and stores a new empty map, returning its CID. func StoreEmptyMap(s Store, bitwidth int) (cid.Cid, error) { - m := MakeEmptyMap(s, bitwidth) + m, err := MakeEmptyMap(s, bitwidth) + if err != nil { + return cid.Undef, err + } return m.Root() } @@ -81,39 +86,47 @@ func (m *Map) Root() (cid.Cid, error) { // Put adds value `v` with key `k` to the hamt store. func (m *Map) Put(k abi.Keyer, v cbor.Marshaler) error { if err := m.root.Set(m.store.Context(), k.Key(), v); err != nil { - return errors.Wrapf(err, "map put failed set in node %v with key %v value %v", m.lastCid, k.Key(), v) + return xerrors.Errorf("failed to set key %v value %v in node %v: %w", k.Key(), v, m.lastCid, err) } return nil } -// Get puts the value at `k` into `out`. +// Get retrieves the value at `k` into `out`, if the `k` is present and `out` is non-nil. +// Returns whether the key was found. func (m *Map) Get(k abi.Keyer, out cbor.Unmarshaler) (bool, error) { - if err := m.root.Find(m.store.Context(), k.Key(), out); err != nil { - if err == hamt.ErrNotFound { - return false, nil - } - return false, errors.Wrapf(err, "map get failed find in node %v with key %v", m.lastCid, k.Key()) + if found, err := m.root.Find(m.store.Context(), k.Key(), out); err != nil { + return false, xerrors.Errorf("failed to get key %v in node %v: %w", m.lastCid, k.Key(), err) + } else { + return found, nil } - return true, nil } -// Has checks for the existance of a key without deserializing its value. +// Has checks for the existence of a key without deserializing its value. func (m *Map) Has(k abi.Keyer) (bool, error) { - if _, err := m.root.FindRaw(m.store.Context(), k.Key()); err != nil { - if err == hamt.ErrNotFound { - return false, nil - } - return false, errors.Wrapf(err, "map get failed find in node %v with key %v", m.lastCid, k.Key()) + if found, err := m.root.Find(m.store.Context(), k.Key(), nil); err != nil { + return false, xerrors.Errorf("failed to check key %v in node %v: %w", m.lastCid, k.Key(), err) + } else { + return found, nil } - return true, nil } -// Delete removes the value at `k` from the hamt store. -func (m *Map) Delete(k abi.Keyer) error { - if err := m.root.Delete(m.store.Context(), k.Key()); err != nil { - return errors.Wrapf(err, "map delete failed in node %v key %v", m.root, k.Key()) +// Removes the value at `k` from the hamt store, if it exists. +// Returns whether the key was previously present. +func (m *Map) TryDelete(k abi.Keyer) (bool, error) { + if found, err := m.root.Delete(m.store.Context(), k.Key()); err != nil { + return false, xerrors.Errorf("failed to delete key %v in node %v: %v", k.Key(), m.root, err) + } else { + return found, nil } +} +// Removes the value at `k` from the hamt store, expecting it to exist. +func (m *Map) Delete(k abi.Keyer) error { + if found, err := m.root.Delete(m.store.Context(), k.Key()); err != nil { + return xerrors.Errorf("failed to delete key %v in node %v: %v", k.Key(), m.root, err) + } else if !found { + return xerrors.Errorf("no such key %v to delete in node %v", k.Key(), m.root) + } return nil } @@ -122,10 +135,10 @@ func (m *Map) Delete(k abi.Keyer) error { // Iteration halts if the function returns an error. // If the output parameter is nil, deserialization is skipped. func (m *Map) ForEach(out cbor.Unmarshaler, fn func(key string) error) error { - return m.root.ForEach(m.store.Context(), func(k string, val interface{}) error { + return m.root.ForEach(m.store.Context(), func(k string, val *cbg.Deferred) error { if out != nil { // Why doesn't hamt.ForEach() just return the value as bytes? - err := out.UnmarshalCBOR(bytes.NewReader(val.(*cbg.Deferred).Raw)) + err := out.UnmarshalCBOR(bytes.NewReader(val.Raw)) if err != nil { return err } @@ -142,3 +155,19 @@ func (m *Map) CollectKeys() (out []string, err error) { }) return } + +// Retrieves the value for `k` into the 'out' unmarshaler (if non-nil), and removes the entry. +// Returns a boolean indicating whether the element was previously in the map. +func (m *Map) Pop(k abi.Keyer, out cbor.Unmarshaler) (bool, error) { + key := k.Key() + if found, err := m.root.Find(m.store.Context(), key, out); err != nil || !found { + return found, err + } + + if found, err := m.root.Delete(m.store.Context(), key); err != nil { + return false, err + } else if !found { + return false, xerrors.Errorf("failed to find key %v to delete", k.Key()) + } + return true, nil +} diff --git a/actors/util/adt/multimap.go b/actors/util/adt/multimap.go index 8b327c2b4..9074dc7c9 100644 --- a/actors/util/adt/multimap.go +++ b/actors/util/adt/multimap.go @@ -29,9 +29,21 @@ func AsMultimap(s Store, r cid.Cid, outerBitwidth, innerBitwidth int) (*Multimap // Creates a new map backed by an empty HAMT and flushes it to the store. // The outer map has a branching factor of 2^bitwidth. -func MakeEmptyMultimap(s Store, outerBitwidth, innerBitwidth int) *Multimap { - m := MakeEmptyMap(s, outerBitwidth) - return &Multimap{m, innerBitwidth} +func MakeEmptyMultimap(s Store, outerBitwidth, innerBitwidth int) (*Multimap, error) { + m, err := MakeEmptyMap(s, outerBitwidth) + if err != nil { + return nil, err + } + return &Multimap{m, innerBitwidth}, nil +} + +// Creates and stores a new empty multimap, returning its CID. +func StoreEmptyMultimap(store Store, outerBitwidth, innerBitwidth int) (cid.Cid, error) { + mmap, err := MakeEmptyMultimap(store, outerBitwidth, innerBitwidth) + if err != nil { + return cid.Undef, err + } + return mmap.Root() } // Returns the root cid of the underlying HAMT. @@ -74,8 +86,7 @@ func (mm *Multimap) Add(key abi.Keyer, value cbor.Marshaler) error { // Removes all values for a key. func (mm *Multimap) RemoveAll(key abi.Keyer) error { - err := mm.mp.Delete(key) - if err != nil { + if _, err := mm.mp.TryDelete(key); err != nil { return errors.Wrapf(err, "failed to delete multimap key %v root %v", key, mm.mp.root) } return nil diff --git a/actors/util/adt/set.go b/actors/util/adt/set.go index 5a973811a..7f275cc7b 100644 --- a/actors/util/adt/set.go +++ b/actors/util/adt/set.go @@ -25,9 +25,12 @@ func AsSet(s Store, r cid.Cid, bitwidth int) (*Set, error) { // NewSet creates a new HAMT with root `r` and store `s`. // The HAMT has branching factor 2^bitwidth. -func MakeEmptySet(s Store, bitwidth int) *Set { - m := MakeEmptyMap(s, bitwidth) - return &Set{m} +func MakeEmptySet(s Store, bitwidth int) (*Set, error) { + m, err := MakeEmptyMap(s, bitwidth) + if err != nil { + return nil, err + } + return &Set{m}, nil } // Root return the root cid of HAMT. @@ -45,7 +48,13 @@ func (h *Set) Has(k abi.Keyer) (bool, error) { return h.m.Get(k, nil) } -// Delete removes `k` from the set. +// Removes `k` from the set, if present. +// Returns whether the key was previously present. +func (h *Set) TryDelete(k abi.Keyer) (bool, error) { + return h.m.TryDelete(k) +} + +// Removes `k` from the set, expecting it to be present. func (h *Set) Delete(k abi.Keyer) error { return h.m.Delete(k) } diff --git a/go.mod b/go.mod index 02a0373c2..fc4461999 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.13 require ( github.com/dchest/blake2b v1.0.0 github.com/filecoin-project/go-address v0.0.3 - github.com/filecoin-project/go-amt-ipld/v3 v3.0.0-20201124192204-2b387ce1bab7 + github.com/filecoin-project/go-amt-ipld/v3 v3.0.0 github.com/filecoin-project/go-bitfield v0.2.0 - github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0-20201203140949-5cdbb5191437 + github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0 github.com/filecoin-project/go-state-types v0.0.0-20210119062722-4adba5aaea71 github.com/ipfs/go-block-format v0.0.2 github.com/ipfs/go-cid v0.0.7 diff --git a/go.sum b/go.sum index da6659eb4..a3f359f6b 100644 --- a/go.sum +++ b/go.sum @@ -7,14 +7,14 @@ github.com/dchest/blake2b v1.0.0 h1:KK9LimVmE0MjRl9095XJmKqZ+iLxWATvlcpVFRtaw6s= github.com/dchest/blake2b v1.0.0/go.mod h1:U034kXgbJpCle2wSk5ybGIVhOSHCVLMDqOzcPEA0F7s= github.com/filecoin-project/go-address v0.0.3 h1:eVfbdjEbpbzIrbiSa+PiGUY+oDK9HnUn+M1R/ggoHf8= github.com/filecoin-project/go-address v0.0.3/go.mod h1:jr8JxKsYx+lQlQZmF5i2U0Z+cGQ59wMIps/8YW/lDj8= -github.com/filecoin-project/go-amt-ipld/v3 v3.0.0-20201124192204-2b387ce1bab7 h1:HHKfkAfweGbpnJV60OuG6M0ZAXKrELklLvyjcKI/nKI= -github.com/filecoin-project/go-amt-ipld/v3 v3.0.0-20201124192204-2b387ce1bab7/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= +github.com/filecoin-project/go-amt-ipld/v3 v3.0.0 h1:Ou/q82QeHGOhpkedvaxxzpBYuqTxLCcj5OChkDNx4qc= +github.com/filecoin-project/go-amt-ipld/v3 v3.0.0/go.mod h1:Qa95YNAbtoVCTSVtX38aAC1ptBnJfPma1R/zZsKmx4o= github.com/filecoin-project/go-bitfield v0.2.0 h1:gCtLcjskIPtdg4NfN7gQZSQF9yrBQ7mkT0qCJxzGI2Q= github.com/filecoin-project/go-bitfield v0.2.0/go.mod h1:CNl9WG8hgR5mttCnUErjcQjGvuiZjRqK9rHVBsQF4oM= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03 h1:2pMXdBnCiXjfCYx/hLqFxccPoqsSveQFxVLvNxy9bus= github.com/filecoin-project/go-crypto v0.0.0-20191218222705-effae4ea9f03/go.mod h1:+viYnvGtUTgJRdy6oaeF4MTFKAfatX071MPDPBL11EQ= -github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0-20201203140949-5cdbb5191437 h1:nu2XYXvzy6BNASKVpfCkmUFr6YDf43LMjkMMab+3Z1E= -github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0-20201203140949-5cdbb5191437/go.mod h1:HBuSIxbIKqWjV0/bcJ/e14iIMr1FckG6cyzAsdfttyU= +github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0 h1:aEOgJxSMbJ7XtuX3WxXvbpkBDp4Sqn3jyx/umGyL8s4= +github.com/filecoin-project/go-hamt-ipld/v3 v3.0.0/go.mod h1:gXpNmr3oQx8l3o7qkGyDjJjYSRX7hp/FGOStdqrWyDI= github.com/filecoin-project/go-state-types v0.0.0-20210119062722-4adba5aaea71 h1:Cas/CUB4ybYpdxvW7LouaydE16cpwdq3vvS3qgZuU+Q= github.com/filecoin-project/go-state-types v0.0.0-20210119062722-4adba5aaea71/go.mod h1:ezYnPf0bNkTsDibL/psSz5dy4B5awOJ/E7P2Saeep8g= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= @@ -28,6 +28,7 @@ github.com/gxed/hashland/murmur3 v0.0.1/go.mod h1:KjXop02n4/ckmZSnY2+HKcLud/tcmv github.com/ipfs/go-block-format v0.0.2 h1:qPDvcP19izTjU8rgo6p7gTXZlkMkF5bz5G3fqIsSCPE= github.com/ipfs/go-block-format v0.0.2/go.mod h1:AWR46JfpcObNfg3ok2JHDUfdiHRgWhJgCQF+KIgOPJY= github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= +github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM= github.com/ipfs/go-cid v0.0.5/go.mod h1:plgt+Y5MnOey4vO4UlUazGqdbEXuFYitED67FexhXog= github.com/ipfs/go-cid v0.0.6/go.mod h1:6Ux9z5e+HpkQdckYoX1PG/6xqKspzlEIR5SDmgqgC/I= @@ -39,6 +40,8 @@ github.com/ipfs/go-ipld-cbor v0.0.4 h1:Aw3KPOKXjvrm6VjwJvFf1F1ekR/BH3jdof3Bk7OTi github.com/ipfs/go-ipld-cbor v0.0.4/go.mod h1:BkCduEx3XBCO6t2Sfo5BaHzuok7hbhdMm9Oh8B2Ftq4= github.com/ipfs/go-ipld-format v0.0.1 h1:HCu4eB/Gh+KD/Q0M8u888RFkorTWNIL3da4oc5dwc80= github.com/ipfs/go-ipld-format v0.0.1/go.mod h1:kyJtbkDALmFHv3QR6et67i35QzO3S0dCDnkOJhcZkms= +github.com/ipfs/go-ipld-format v0.0.2 h1:OVAGlyYT6JPZ0pEfGntFPS40lfrDmaDbQwNHEY2G9Zs= +github.com/ipfs/go-ipld-format v0.0.2/go.mod h1:4B6+FM2u9OJ9zCV+kSbgFAZlOrv1Hqbf0INGQgiKf9k= github.com/ipfs/go-log v1.0.4 h1:6nLQdX4W8P9yZZFH7mO+X/PzjN8Laozm/lMJ6esdgzY= github.com/ipfs/go-log v1.0.4/go.mod h1:oDCg2FkjogeFOhqqb+N39l2RpTNPL6F/StPkB3kPgcs= github.com/ipfs/go-log/v2 v2.0.5 h1:fL4YI+1g5V/b1Yxr1qAiXTMg1H8z9vx/VmJxBuQMHvU= @@ -137,6 +140,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190219092855-153ac476189d/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= diff --git a/support/vm/vm.go b/support/vm/vm.go index dab5daae0..cbd70dd5d 100644 --- a/support/vm/vm.go +++ b/support/vm/vm.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/filecoin-project/go-address" - hamt "github.com/filecoin-project/go-hamt-ipld/v3" "github.com/filecoin-project/go-state-types/abi" "github.com/filecoin-project/go-state-types/big" "github.com/filecoin-project/go-state-types/cbor" @@ -72,7 +71,10 @@ type Invocation struct { // NewVM creates a new runtime for executing messages. func NewVM(ctx context.Context, actorImpls ActorImplLookup, store adt.Store) *VM { - actors := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + actors, err := adt.MakeEmptyMap(store, builtin.DefaultHamtBitwidth) + if err != nil { + panic(err) + } actorRoot, err := actors.Root() if err != nil { panic(err) @@ -235,11 +237,8 @@ func (vm *VM) SetActorState(ctx context.Context, key address.Address, state cbor // This behaviour is based on a principle that some store implementations might not be able to determine // whether something exists before deleting it. func (vm *VM) deleteActor(_ context.Context, key address.Address) error { - err := vm.actors.Delete(abi.AddrKey(key)) - vm.actorsDirty = true - if err == hamt.ErrNotFound { - return nil - } + found, err := vm.actors.TryDelete(abi.AddrKey(key)) + vm.actorsDirty = found return err }