diff --git a/.circleci/config.yml b/.circleci/config.yml index f863d6817..bc7737860 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -874,6 +874,11 @@ workflows: suite: itest-get_messages_in_ts target: "./itests/get_messages_in_ts_test.go" + - test: + name: test-itest-lookup_robust_address + suite: itest-lookup_robust_address + target: "./itests/lookup_robust_address_test.go" + - test: name: test-itest-mempool suite: itest-mempool diff --git a/api/api_full.go b/api/api_full.go index 6c49cd731..bcdfdad10 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 45fcd6f53..5db190ba6 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 e0664bca9..b5bc36ade 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/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index ce2ffcaee..1eaaf6a84 100644 Binary files a/build/openrpc/full.json.gz and b/build/openrpc/full.json.gz differ diff --git a/build/openrpc/miner.json.gz b/build/openrpc/miner.json.gz index a531555ad..7b1191138 100644 Binary files a/build/openrpc/miner.json.gz and b/build/openrpc/miner.json.gz differ diff --git a/build/openrpc/worker.json.gz b/build/openrpc/worker.json.gz index 240804a04..554082fd6 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/chain/stmgr/stmgr.go b/chain/stmgr/stmgr.go index d0bdd73e9..7b10cd805 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("failed to decode provided address as 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 robustAddr == address.Undef { + if err == nil { + return address.Undef, xerrors.Errorf("Address %s not found", idAddr.String()) + } + return address.Undef, xerrors.Errorf("finding address: %w", err) + } + return robustAddr, nil +} + 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 c72483e9d..5d4dab533 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/itests/lookup_robust_address_test.go b/itests/lookup_robust_address_test.go new file mode 100644 index 000000000..d2d49af8b --- /dev/null +++ b/itests/lookup_robust_address_test.go @@ -0,0 +1,32 @@ +package itests + +import ( + "context" + "testing" + "time" + + "github.com/filecoin-project/go-state-types/network" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/itests/kit" + "github.com/stretchr/testify/require" +) + +func TestStateLookupRobustAddress(t *testing.T) { + ctx := context.Background() + kit.QuietMiningLogs() + + client, 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 := client.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 := client.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 dfd1c69d9..51cb0dfbc 100644 --- a/node/impl/full/state.go +++ b/node/impl/full/state.go @@ -449,6 +449,15 @@ func (m *StateModule) StateLookupID(ctx context.Context, addr address.Address, t return m.StateManager.LookupID(ctx, addr, ts) } +func (a *StateAPI) StateLookupRobustAddress(ctx context.Context, addr address.Address, tsk types.TipSetKey) (address.Address, error) { + ts, err := a.Chain.GetTipSetFromKey(ctx, tsk) + if err != nil { + return address.Undef, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + + return a.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 {