diff --git a/api/api_full.go b/api/api_full.go index 412e223cd42..6b43fb8ad15 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -113,6 +113,11 @@ type FullNode interface { // will be returned. ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + // ChainGetTipSetAfterHeight looks back for a tipset at the specified epoch. + // If there are no blocks at the specified epoch, the first non-nil tipset at a later epoch + // will be returned. + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) //perm:read + // ChainReadObj reads ipld nodes referenced by the specified CID from chain // blockstore and returns raw bytes. ChainReadObj(context.Context, cid.Cid) ([]byte, error) //perm:read diff --git a/api/api_gateway.go b/api/api_gateway.go index 6db1c8e45a1..29cd8ce24e1 100644 --- a/api/api_gateway.go +++ b/api/api_gateway.go @@ -35,6 +35,7 @@ type Gateway interface { ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainNotify(context.Context) (<-chan []*HeadChange, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) GasEstimateMessageGas(ctx context.Context, msg *types.Message, spec *MessageSendSpec, tsk types.TipSetKey) (*types.Message, error) diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index 124532c14f4..02ad126aef5 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -343,6 +343,21 @@ func (mr *MockFullNodeMockRecorder) ChainGetTipSet(arg0, arg1 interface{}) *gomo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSet", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSet), arg0, arg1) } +// ChainGetTipSetAfterHeight mocks base method. +func (m *MockFullNode) ChainGetTipSetAfterHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainGetTipSetAfterHeight", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.TipSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainGetTipSetAfterHeight indicates an expected call of ChainGetTipSetAfterHeight. +func (mr *MockFullNodeMockRecorder) ChainGetTipSetAfterHeight(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainGetTipSetAfterHeight", reflect.TypeOf((*MockFullNode)(nil).ChainGetTipSetAfterHeight), arg0, arg1, arg2) +} + // ChainGetTipSetByHeight mocks base method. func (m *MockFullNode) ChainGetTipSetByHeight(arg0 context.Context, arg1 abi.ChainEpoch, arg2 types.TipSetKey) (*types.TipSet, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index a4feb7be157..7e830572579 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -132,6 +132,8 @@ type FullNodeStruct struct { ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `perm:"read"` ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `perm:"read"` @@ -474,6 +476,8 @@ type GatewayStruct struct { ChainGetTipSet func(p0 context.Context, p1 types.TipSetKey) (*types.TipSet, error) `` + ChainGetTipSetAfterHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` + ChainGetTipSetByHeight func(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) `` ChainHasObj func(p0 context.Context, p1 cid.Cid) (bool, error) `` @@ -1171,6 +1175,17 @@ func (s *FullNodeStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (* return nil, ErrNotSupported } +func (s *FullNodeStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *FullNodeStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { if s.Internal.ChainGetTipSetByHeight == nil { return nil, ErrNotSupported @@ -2997,6 +3012,17 @@ func (s *GatewayStub) ChainGetTipSet(p0 context.Context, p1 types.TipSetKey) (*t return nil, ErrNotSupported } +func (s *GatewayStruct) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + if s.Internal.ChainGetTipSetAfterHeight == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainGetTipSetAfterHeight(p0, p1, p2) +} + +func (s *GatewayStub) ChainGetTipSetAfterHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { + return nil, ErrNotSupported +} + func (s *GatewayStruct) ChainGetTipSetByHeight(p0 context.Context, p1 abi.ChainEpoch, p2 types.TipSetKey) (*types.TipSet, error) { if s.Internal.ChainGetTipSetByHeight == nil { return nil, ErrNotSupported diff --git a/build/openrpc/full.json.gz b/build/openrpc/full.json.gz index db41fb2d3a1..daf522a99f1 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 57a56d94fff..9197e335322 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 07740e5e2cc..5c6e4a866be 100644 Binary files a/build/openrpc/worker.json.gz and b/build/openrpc/worker.json.gz differ diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index cbaed82af3b..4ed1509e053 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -27,6 +27,7 @@ * [ChainGetRandomnessFromBeacon](#ChainGetRandomnessFromBeacon) * [ChainGetRandomnessFromTickets](#ChainGetRandomnessFromTickets) * [ChainGetTipSet](#ChainGetTipSet) + * [ChainGetTipSetAfterHeight](#ChainGetTipSetAfterHeight) * [ChainGetTipSetByHeight](#ChainGetTipSetByHeight) * [ChainHasObj](#ChainHasObj) * [ChainHead](#ChainHead) @@ -766,6 +767,38 @@ Response: } ``` +### ChainGetTipSetAfterHeight +ChainGetTipSetAfterHeight looks back for a tipset at the specified epoch. +If there are no blocks at the specified epoch, the first non-nil tipset at a later epoch +will be returned. + + +Perms: read + +Inputs: +```json +[ + 10101, + [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ] +] +``` + +Response: +```json +{ + "Cids": null, + "Blocks": null, + "Height": 0 +} +``` + ### ChainGetTipSetByHeight ChainGetTipSetByHeight looks back for a tipset at the specified epoch. If there are no blocks at the specified epoch, a tipset at an earlier epoch diff --git a/gateway/node.go b/gateway/node.go index 3c7a67196a0..84b616f26f3 100644 --- a/gateway/node.go +++ b/gateway/node.go @@ -36,6 +36,7 @@ type TargetAPI interface { ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainHasObj(context.Context, cid.Cid) (bool, error) ChainHead(ctx context.Context) (*types.TipSet, error) ChainNotify(context.Context) (<-chan []*api.HeadChange, error) @@ -163,32 +164,48 @@ func (gw *Node) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types } func (gw *Node) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + + return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) +} + +func (gw *Node) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + if err := gw.checkTipSetHeight(ctx, h, tsk); err != nil { + return nil, err + } + + return gw.target.ChainGetTipSetAfterHeight(ctx, h, tsk) +} + +func (gw *Node) checkTipSetHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) error { var ts *types.TipSet if tsk.IsEmpty() { head, err := gw.target.ChainHead(ctx) if err != nil { - return nil, err + return err } ts = head } else { gts, err := gw.target.ChainGetTipSet(ctx, tsk) if err != nil { - return nil, err + return err } ts = gts } // Check if the tipset key refers to gw tipset that's too far in the past if err := gw.checkTipset(ts); err != nil { - return nil, err + return err } // Check if the height is too far in the past if err := gw.checkTipsetHeight(ts, h); err != nil { - return nil, err + return err } - return gw.target.ChainGetTipSetByHeight(ctx, h, tsk) + return nil } func (gw *Node) ChainGetNode(ctx context.Context, p string) (*api.IpldObject, error) { diff --git a/node/impl/full/chain.go b/node/impl/full/chain.go index c5c2334ad7a..433573010bf 100644 --- a/node/impl/full/chain.go +++ b/node/impl/full/chain.go @@ -50,6 +50,7 @@ type ChainModuleAPI interface { ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainGetTipSet(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error) } @@ -266,6 +267,14 @@ func (m *ChainModule) ChainGetTipSetByHeight(ctx context.Context, h abi.ChainEpo return m.Chain.GetTipsetByHeight(ctx, h, ts, true) } +func (m *ChainModule) ChainGetTipSetAfterHeight(ctx context.Context, h abi.ChainEpoch, tsk types.TipSetKey) (*types.TipSet, error) { + ts, err := m.Chain.GetTipSetFromKey(tsk) + if err != nil { + return nil, xerrors.Errorf("loading tipset %s: %w", tsk, err) + } + return m.Chain.GetTipsetByHeight(ctx, h, ts, false) +} + func (m *ChainModule) ChainReadObj(ctx context.Context, obj cid.Cid) ([]byte, error) { blk, err := m.ExposedBlockstore.Get(obj) if err != nil { diff --git a/storage/miner.go b/storage/miner.go index 59c64eb41a4..5308a3e3b7a 100644 --- a/storage/miner.go +++ b/storage/miner.go @@ -113,6 +113,7 @@ type fullNodeFilteredAPI interface { ChainGetRandomnessFromTickets(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) ChainGetRandomnessFromBeacon(ctx context.Context, tsk types.TipSetKey, personalization crypto.DomainSeparationTag, randEpoch abi.ChainEpoch, entropy []byte) (abi.Randomness, error) ChainGetTipSetByHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) + ChainGetTipSetAfterHeight(context.Context, abi.ChainEpoch, types.TipSetKey) (*types.TipSet, error) ChainGetBlockMessages(context.Context, cid.Cid) (*api.BlockMessages, error) ChainGetMessage(ctx context.Context, mc cid.Cid) (*types.Message, error) ChainReadObj(context.Context, cid.Cid) ([]byte, error)