diff --git a/api/api_full.go b/api/api_full.go index 6c49cd731b3..bcdfdad1009 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -522,8 +522,10 @@ type FullNode interface { StateMarketStorageDeal(context.Context, abi.DealID, types.TipSetKey) (*MarketDeal, error) //perm:read // StateLookupID retrieves the ID address of the given address StateLookupID(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read - // StateAccountKey returns the public key address of the given ID address + // StateAccountKey returns the public key address of the given ID address for secp and bls accounts StateAccountKey(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read + // StateLookupRobustAddress returns the public key address of the given ID address for non-account addresses (multisig, miners etc) + StateLookupRobustAddress(context.Context, address.Address, types.TipSetKey) (address.Address, error) //perm:read // StateChangedActors returns all the actors whose states change between the two given state CIDs // TODO: Should this take tipset keys instead? StateChangedActors(context.Context, cid.Cid, cid.Cid) (map[string]types.Actor, error) //perm:read diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 45fcd6f53dd..5db190ba62f 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -2496,6 +2496,21 @@ func (mr *MockFullNodeMockRecorder) StateLookupID(arg0, arg1, arg2 interface{}) return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupID", reflect.TypeOf((*MockFullNode)(nil).StateLookupID), arg0, arg1, arg2) } +// StateLookupRobustAddress mocks base method. +func (m *MockFullNode) StateLookupRobustAddress(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (address.Address, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StateLookupRobustAddress", arg0, arg1, arg2) + ret0, _ := ret[0].(address.Address) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StateLookupRobustAddress indicates an expected call of StateLookupRobustAddress. +func (mr *MockFullNodeMockRecorder) StateLookupRobustAddress(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StateLookupRobustAddress", reflect.TypeOf((*MockFullNode)(nil).StateLookupRobustAddress), arg0, arg1, arg2) +} + // StateMarketBalance mocks base method. func (m *MockFullNode) StateMarketBalance(arg0 context.Context, arg1 address.Address, arg2 types.TipSetKey) (api.MarketBalance, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index e0664bca932..b5bc36ade2d 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -364,6 +364,8 @@ type FullNodeStruct struct { StateLookupID func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + StateLookupRobustAddress func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) `perm:"read"` + StateMarketBalance func(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) `perm:"read"` StateMarketDeals func(p0 context.Context, p1 types.TipSetKey) (map[string]MarketDeal, error) `perm:"read"` @@ -2528,6 +2530,17 @@ func (s *FullNodeStub) StateLookupID(p0 context.Context, p1 address.Address, p2 return *new(address.Address), ErrNotSupported } +func (s *FullNodeStruct) StateLookupRobustAddress(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + if s.Internal.StateLookupRobustAddress == nil { + return *new(address.Address), ErrNotSupported + } + return s.Internal.StateLookupRobustAddress(p0, p1, p2) +} + +func (s *FullNodeStub) StateLookupRobustAddress(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (address.Address, error) { + return *new(address.Address), ErrNotSupported +} + func (s *FullNodeStruct) StateMarketBalance(p0 context.Context, p1 address.Address, p2 types.TipSetKey) (MarketBalance, error) { if s.Internal.StateMarketBalance == nil { return *new(MarketBalance), ErrNotSupported diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index d0bdd73e916..1e579c6b886 100644 --- a/chain/stmgr/stmgr.go +++ b/chain/stmgr/stmgr.go @@ -4,6 +4,8 @@ import ( "context" "sync" + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/specs-actors/v7/actors/migration/nv15" "github.com/filecoin-project/lotus/chain/rand" @@ -22,6 +24,7 @@ import ( "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/build" + _init "github.com/filecoin-project/lotus/chain/actors/builtin/init" "github.com/filecoin-project/lotus/chain/actors/builtin/paych" "github.com/filecoin-project/lotus/chain/actors/policy" "github.com/filecoin-project/lotus/chain/state" @@ -318,6 +321,48 @@ func (sm *StateManager) LookupID(ctx context.Context, addr address.Address, ts * return state.LookupID(addr) } +func (sm *StateManager) LookupRobustAddress(ctx context.Context, idAddr address.Address, ts *types.TipSet) (address.Address, error) { + idAddrDecoded, err := address.IDFromAddress(idAddr) + if err != nil { + return address.Undef, xerrors.Errorf("address couldnt decode to id addr: %w", err) + } + + cst := cbor.NewCborStore(sm.cs.StateBlockstore()) + wrapStore := adt.WrapStore(ctx, cst) + + stateTree, err := state.LoadStateTree(cst, sm.parentState(ts)) + if err != nil { + return address.Undef, xerrors.Errorf("load state tree: %w", err) + } + + initActor, err := stateTree.GetActor(_init.Address) + if err != nil { + return address.Undef, xerrors.Errorf("load init actor: %w", err) + } + + initState, err := _init.Load(wrapStore, initActor) + if err != nil { + return address.Undef, xerrors.Errorf("load init state: %w", err) + } + robustAddr := address.Undef + + err = initState.ForEachActor(func(id abi.ActorID, addr address.Address) error { + if uint64(id) == idAddrDecoded { + robustAddr = addr + // Hacky way to early return from ForEach + return xerrors.New("robust address found") + } + return nil + }) + if err != nil { + if robustAddr == address.Undef { + return address.Undef, xerrors.Errorf("finding address: %w", err) + } + return robustAddr, nil + } + return address.Undef, xerrors.Errorf("address not found") +} + func (sm *StateManager) ValidateChain(ctx context.Context, ts *types.TipSet) error { tschain := []*types.TipSet{ts} for ts.Height() != 0 { diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index c72483e9dc2..5d4dab5339c 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -181,6 +181,7 @@ * [StateListMessages](#StateListMessages) * [StateListMiners](#StateListMiners) * [StateLookupID](#StateLookupID) + * [StateLookupRobustAddress](#StateLookupRobustAddress) * [StateMarketBalance](#StateMarketBalance) * [StateMarketDeals](#StateMarketDeals) * [StateMarketParticipants](#StateMarketParticipants) @@ -5037,7 +5038,7 @@ A nil TipSetKey can be provided as a param, this will cause the heaviest tipset ### StateAccountKey -StateAccountKey returns the public key address of the given ID address +StateAccountKey returns the public key address of the given ID address for secp and bls accounts Perms: read @@ -5783,6 +5784,29 @@ Response: StateLookupID retrieves the ID address of the given address +Perms: read + +Inputs: +```json +[ + "f01234", + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: `"f01234"` + +### StateLookupRobustAddress +StateLookupRobustAddress returns the public key address of the given ID address for non-account addresses (multisig, miners etc) + + Perms: read Inputs: diff --git a/extern/filecoin-ffi b/extern/filecoin-ffi index 38f10cf6fa0..5d00bb4365a 160000 --- a/extern/filecoin-ffi +++ b/extern/filecoin-ffi @@ -1 +1 @@ -Subproject commit 38f10cf6fa078b989fc774081cd65ba5c53636d8 +Subproject commit 5d00bb4365a97890bd038edfceecaa63ebbb3e2d diff --git a/gateway/node.go b/gateway/node.go index 87a8551b1f4..916f591fd93 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -57,6 +57,7 @@ type TargetAPI interface { StateDealProviderCollateralBounds(ctx context.Context, size abi.PaddedPieceSize, verified bool, tsk types.TipSetKey) (api.DealCollateralBounds, error) StateGetActor(ctx context.Context, actor address.Address, ts types.TipSetKey) (*types.Actor, error) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) @@ -338,6 +339,14 @@ func (gw *Node) StateLookupID(ctx context.Context, addr address.Address, tsk typ return gw.target.StateLookupID(ctx, addr, tsk) } +func (gw *Node) StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { + if err := gw.checkTipsetKey(ctx, tsk); err != nil { + return address.Undef, err + } + + return gw.target.StateLookupRobustAddress(ctx, addr, tsk) +} + func (gw *Node) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) { if err := gw.checkTipsetKey(ctx, tsk); err != nil { return api.MarketBalance{}, err diff --git a/itests/self_sent_txn_test.go b/itests/self_sent_txn_test.go index 8d608ba95ef..69c5c34c4b8 100644 --- a/itests/self_sent_txn_test.go +++ b/itests/self_sent_txn_test.go @@ -102,3 +102,23 @@ func TestSelfSentTxnV14(t *testing.T) { require.NoError(t, err) require.Equal(t, exitcode.Ok, mLookup.Receipt.ExitCode) } + +func TestStateLookupRobustAddress(t *testing.T) { + ctx := context.Background() + kit.QuietMiningLogs() + + client15, miner, ens := kit.EnsembleMinimal(t, kit.MockProofs(), kit.GenesisNetworkVersion(network.Version15)) + ens.InterconnectAll().BeginMining(10 * time.Millisecond) + + addr, err := miner.ActorAddress(ctx) + require.NoError(t, err) + + // Look up the robust address + robAddr, err := client15.StateLookupRobustAddress(ctx, addr, types.EmptyTSK) + require.NoError(t, err) + + // Check the id address for the given robust address and make sure it matches + idAddr, err := client15.StateLookupID(ctx, robAddr, types.EmptyTSK) + require.NoError(t, err) + require.Equal(t, addr, idAddr) +} diff --git a/node/impl/full/state.go b/node/impl/full/state.go index dfd1c69d95c..f7a6a436e6f 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -50,6 +50,7 @@ type StateModuleAPI interface { StateGetActor(ctx context.Context, actor address.Address, tsk types.TipSetKey) (*types.Actor, error) StateListMiners(ctx context.Context, tsk types.TipSetKey) ([]address.Address, error) StateLookupID(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) + StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) StateMarketBalance(ctx context.Context, addr address.Address, tsk types.TipSetKey) (api.MarketBalance, error) StateMarketStorageDeal(ctx context.Context, dealId abi.DealID, tsk types.TipSetKey) (*api.MarketDeal, error) StateMinerInfo(ctx context.Context, actor address.Address, tsk types.TipSetKey) (miner.MinerInfo, error) @@ -449,6 +450,21 @@ func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, t return m.StateManager.LookupID(ctx, addr, ts) } +func (m *StateModule) StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { + ts, err := m.Chain.GetTipSetFromKey(ctx, tsk) + if err != nil { + return address.Undef, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + if ts.Height() > policy.ChainFinality { + ts, err = m.StateManager.ChainStore().GetTipsetByHeight(ctx, ts.Height()-policy.ChainFinality, ts, true) + if err != nil { + return address.Undef, xerrors.Errorf("failed to load lookback tipset: %w", err) + } + } + + return m.StateManager.LookupRobustAddress(ctx, addr, ts) +} + func (m *StateModule) StateAccountKey(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { ts, err := m.Chain.GetTipSetFromKey(ctx, tsk) if err != nil {