Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/providers/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,16 @@ export class DataCIDSize {
PieceCID!: Cid;
}

export type IpldObject = {
Cid: Cid;
Obj: any;
}

export type PruneOpts = {
MovingGC: boolean
RetainState: number
}

/**
* Interface to be implemented by all providers.
*
Expand Down
200 changes: 147 additions & 53 deletions src/providers/method-groups/chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import {
BlockMessages, ChainEpoch,
Cid,
HeadChange,
IpldObject,
Message,
MessageReceipt,
ObjStat,
PruneOpts,
TipSet, TipSetKey,
WrappedMessage,
} from '../Types';
Expand All @@ -24,41 +26,6 @@ export class JsonRpcChainMethodGroup {
this.conn = conn;
}

/**
* reads ipld nodes referenced by the specified CID from chain blockstore and returns raw bytes.
* @param cid
*/
public async readObj(cid: Cid): Promise<string> {
const ret = await this.conn.request({ method: 'Filecoin.ChainReadObj', params: [cid] });
return ret as string;
}

/**
* deletes node referenced by the given CID
* @param cid
*/
public async deleteObj(cid: Cid): Promise<string> {
const error = await this.conn.request({ method: 'Filecoin.ChainDeleteObj', params: [cid] });
return error as string;
}

/**
* returns messages stored in the specified block.
* @param blockCid
*/
public async getBlockMessages(blockCid: Cid): Promise<BlockMessages> {
const ret = await this.conn.request({ method: 'Filecoin.ChainGetBlockMessages', params: [blockCid] });
return ret as BlockMessages;
}

/**
* returns the current head of the chain
*/
public async getHead(): Promise<TipSet> {
const ret = await this.conn.request({ method: 'Filecoin.ChainHead' });
return ret as TipSet;
}

/**
* call back on chain head updates.
* @param cb
Expand All @@ -81,12 +48,12 @@ export class JsonRpcChainMethodGroup {
}
}

public stopChainNotify(id?: any) {
if (this.conn instanceof HttpJsonRpcConnector && id) {
clearInterval(id);
} else if (this.conn instanceof WsJsonRpcConnector) {
this.conn.closeSubscription(id);
}
/**
* returns the current head of the chain
*/
public async getHead(): Promise<TipSet> {
const ret = await this.conn.request({ method: 'Filecoin.ChainHead' });
return ret as TipSet;
}

/**
Expand All @@ -99,16 +66,37 @@ export class JsonRpcChainMethodGroup {
}

/**
* reads a message referenced by the specified CID from the chain blockstore
* @param messageCid
* Returns the tipset specified by the given TipSetKey.
* @param tipSetKey
*/
public async getMessage(messageCid: Cid): Promise<Message> {
const ret = await this.conn.request({ method: 'Filecoin.ChainGetMessage', params: [messageCid] });
return ret as Message;
public async getTipSet(tipSetKey: TipSetKey): Promise<TipSet> {
const ret: TipSet = await this.conn.request({ method: 'Filecoin.ChainGetTipSet', params: [tipSetKey] });
return ret;
}

/**
* returns receipts for messages in parent tipset of the specified block
* returns messages stored in the specified block.
* @param blockCid
*
* Note: If there are multiple blocks in a tipset, it's likely that some
* messages will be duplicated. It's also possible for blocks in a tipset to have
* different messages from the same sender at the same nonce. When that happens,
* only the first message (in a block with lowest ticket) will be considered
* for execution
*
* NOTE: THIS METHOD SHOULD ONLY BE USED FOR GETTING MESSAGES IN A SPECIFIC BLOCK
*
* DO NOT USE THIS METHOD TO GET MESSAGES INCLUDED IN A TIPSET
* Use ChainGetParentMessages, which will perform correct message deduplication
*/
public async getBlockMessages(blockCid: Cid): Promise<BlockMessages> {
const ret = await this.conn.request({ method: 'Filecoin.ChainGetBlockMessages', params: [blockCid] });
return ret as BlockMessages;
}

/**
* returns receipts for messages in parent tipset of the specified block. The receipts in the list returned are one-to-one with the
* messages returned by a call to ChainGetParentMessages with the same blockCid.
* @param blockCid
*/
public async getParentReceipts(blockCid: Cid): Promise<MessageReceipt[]> {
Expand All @@ -125,6 +113,53 @@ export class JsonRpcChainMethodGroup {
return ret as WrappedMessage[];
}


/**
* returns message stores in current tipset
* @param tipSetKey
*/
public async getMessagesInTipset(tipSetKey: TipSetKey): Promise<WrappedMessage[]> {
const ret = await this.conn.request({ method: 'Filecoin.ChainGetMessagesInTipset', params: [tipSetKey] });
return ret as WrappedMessage[];
}

/**
* looks back for a tipset at the specified epoch. If there are no blocks at the specified epoch, a tipset at an earlier epoch will be returned.
* @param epochNumber
*/
public async getTipSetByHeight(epochNumber: number): Promise<TipSet> {
const ret: TipSet = await this.conn.request({ method: 'Filecoin.ChainGetTipSetByHeight', params: [epochNumber, []] });
return ret;
}

/**
* 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.
* @param epochNumber
*/
public async getTipSetAfterHeight(epochNumber: number, tipSetKey?: TipSetKey): Promise<TipSet> {
const ret: TipSet = await this.conn.request({ method: 'Filecoin.ChainGetTipSetAfterHeight', params: [epochNumber, tipSetKey] });
return ret;
}

/**
* reads ipld nodes referenced by the specified CID from chain blockstore and returns raw bytes.
* @param cid
*/
public async readObj(cid: Cid): Promise<string> {
const ret = await this.conn.request({ method: 'Filecoin.ChainReadObj', params: [cid] });
return ret as string;
}

/**
* deletes node referenced by the given CID
* @param cid
*/
public async deleteObj(cid: Cid): Promise<string> {
const error = await this.conn.request({ method: 'Filecoin.ChainDeleteObj', params: [cid] });
return error as string;
}

/**
* checks if a given CID exists in the chain blockstore
* @param cid
Expand Down Expand Up @@ -173,16 +208,25 @@ export class JsonRpcChainMethodGroup {
}

/**
* looks back for a tipset at the specified epoch.
* @param epochNumber
* @param p
*/
public async getTipSetByHeight(epochNumber: number): Promise<TipSet> {
const ret: TipSet = await this.conn.request({ method: 'Filecoin.ChainGetTipSetByHeight', params: [epochNumber, []] });
return ret;
public async getNode(p: string): Promise<IpldObject> {
const node: IpldObject = await this.conn.request({ method: 'Filecoin.ChainGetNode', params: [p] });
return node;
}

/**
* Returns a set of revert/apply operations needed to get from
* reads a message referenced by the specified CID from the chain blockstore
* @param messageCid
*/
public async getMessage(messageCid: Cid): Promise<Message> {
const ret = await this.conn.request({ method: 'Filecoin.ChainGetMessage', params: [messageCid] });
return ret as Message;
}

/**
* Returns a set of revert/apply operations needed to get from one tipset to another, for example:
*
* @param from
* @param to
*/
Expand All @@ -203,4 +247,54 @@ export class JsonRpcChainMethodGroup {
const path: HeadChange[] = await this.conn.request({ method: 'Filecoin.ChainExport', params: [nroots, oldmsgskip, tipSetKey] });
return path;
}

/**
* Prunes the stored chain state and garbage collects; only supported if you are using the splitstore
*
* @param opts
*/
public async prune(opts: PruneOpts) {
return this.conn.request({ method: 'Filecoin.ChainPrune', params: [opts] });
}

/**
* Performs an (asynchronous) health check on the chain/state blockstore if supported by the underlying implementation.
*/
public async checkBlockstore() {
return this.conn.request({ method: 'Filecoin.ChainCheckBlockstore', params: [] });
}

/**
* Returns some basic information about the blockstore
*/
public async blockstoreInfo(): Promise<any> {
return this.conn.request({ method: 'Filecoin.ChainBlockstoreInfo', params: [] });
}

/**
* Estimates gas fee cap
* @param message
* @param n
* @param tipSetKey
*/
public async gasEstimateFeeCap(message: Message, n: number, tipSetKey?: TipSetKey): Promise<string> {
return this.conn.request({ method: 'Filecoin.GasEstimateFeeCap', params: [message, n, tipSetKey] });
}

/**
* Estimates gas used by the message and returns it. It fails if message fails to execute.
* @param message
* @param tipSetKey
*/
public async gasEstimateGasLimit(message: Message, tipSetKey?: TipSetKey): Promise<number> {
return this.conn.request({ method: 'Filecoin.GasEstimateGasLimit', params: [message, tipSetKey] });
}

public stopChainNotify(id?: any) {
if (this.conn instanceof HttpJsonRpcConnector && id) {
clearInterval(id);
} else if (this.conn instanceof WsJsonRpcConnector) {
this.conn.closeSubscription(id);
}
}
}
68 changes: 68 additions & 0 deletions tests/src/test-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,74 @@ describe("Chain methods", function() {
await provider.release();
});

it("should get tipset [http]", async function() {
const provider = new LotusClient(httpConnector);
const tipSetKey = await provider.chain.getTipSetByHeight(10);
await provider.chain.getTipSet([tipSetKey.Cids[0]]);
});

it("should get tipset [ws]", async function() {
const provider = new LotusClient(wsConnector);
const tipSetKey = await provider.chain.getTipSetByHeight(10);
await provider.chain.getTipSet([tipSetKey.Cids[0]]);
await provider.release();
});

it("should check tipset after height [http]", async function() {
const con = new LotusClient(httpConnector);
const tipSet = await con.chain.getTipSetAfterHeight(1);
assert.strictEqual(typeof tipSet.Height === "number", true, "invalid tipset after height");
});

it("should check tipset after height [ws]", async function() {
const provider = new LotusClient(wsConnector);
const tipSet = await provider.chain.getTipSetAfterHeight(1);
assert.strictEqual(typeof tipSet.Height === "number", true, "invalid tipset after height");
await provider.release();
});

it("should get gas fee cap [http]", async function() {
const con = new LotusClient(httpConnector);
const messages = await con.state.listMessages({
To: 't01000'
});
const message = await con.chain.getMessage(messages[0]);
const cap = await con.chain.gasEstimateFeeCap(message, 0);
assert.strictEqual(typeof cap === "string", true, "invalid gas fee cap");
});

it("should get gas fee cap [ws]", async function() {
const provider = new LotusClient(wsConnector);
const messages = await provider.state.listMessages({
To: 't01000'
});
const message = await provider.chain.getMessage(messages[0]);
const cap = await provider.chain.gasEstimateFeeCap(message, 0);
assert.strictEqual(typeof cap === "string", true, "invalid gas fee cap");
await provider.release();
});

it("should get estimate gas limit [http]", async function() {
const con = new LotusClient(httpConnector);
const messages = await con.state.listMessages({
To: 't01000'
});
const message = await con.chain.getMessage(messages[0]);
const cap = await con.chain.gasEstimateGasLimit(message);
assert.strictEqual(typeof cap === "number", true, "invalid estimate gas limit");
});

it("should get estimate gas limit [ws]", async function() {
const provider = new LotusClient(wsConnector);
const messages = await provider.state.listMessages({
To: 't01000'
});
const message = await provider.chain.getMessage(messages[0]);
const cap = await provider.chain.gasEstimateGasLimit(message);
assert.strictEqual(typeof cap === "number", true, "invalid estimate gas limit");
await provider.release();
});

// This test fails in the current testnet setup with the following error: RPC Error: method 'Filecoin.ChainExport' not supported in this mode (no out channel support)
// it("export chain", async () => {
// const provider = new LotusClient(httpConnector);
Expand Down