diff --git a/CHANGELOG.md b/CHANGELOG.md index 47ecf7d0768..493b2e5c761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ # Lotus changelog # UNRELEASED + (`ChainIndexer`) to index Filecoin chain state such as tipsets, messages, events and ETH transactions for accurate and faster RPC responses. The `ChainIndexer` replaces the existing `MsgIndex`, `EthTxIndex` and `EventIndex` implementations in Lotus, which [suffer from a multitude of known problems](https://github.com/filecoin-project/lotus/issues/12293). If you are an RPC provider/node operator, please refer to the [ChainIndexer documentation for RPC providers](TODO: URL) for information on how to enable, configure and use the new Indexer. +Add `EthGetBlockReceipts` RPC method to retrieve transaction receipts for a specified block. This method allows users to obtain Ethereum format receipts of all transactions included in a given tipset as specified by its Ethereum block equivalent. ([filecoin-project/lotus#12478](https://github.com/filecoin-project/lotus/pull/12478)) + + +## ☢️ Upgrade Warnings ☢️ + +- Minimum go-version been updated to v1.22.7 ([filecoin-project/lotus#12459](https://github.com/filecoin-project/lotus/pull/12459)) ## New features - Update `EthGetBlockByNumber` to return a pointer to ethtypes.EthBlock or nil for null rounds. ([filecoin-project/lotus#12529](https://github.com/filecoin-project/lotus/pull/12529)) diff --git a/api/api_full.go b/api/api_full.go index 698bdde37a8..b107c083f88 100644 --- a/api/api_full.go +++ b/api/api_full.go @@ -64,6 +64,37 @@ type FullNode interface { Common Net + // MethodGroup: ChainIndexer + // The ChainIndexer method group contains methods for interacting with the chain indexer. + + // ChainValidateIndex validates the integrity of and optionally backfills + // the chain index at a specific epoch. + // + // It can be used to: + // + // 1. Validate the chain index at a specific epoch: + // - Ensures consistency between indexed data and actual chain state + // - Reports any errors found during validation (i.e. the indexed data does not match the actual chain state, missing data, etc.) + // + // 2. Optionally backfill missing data: + // - Backfills data if the index is missing information for the specified epoch + // - Backfilling only occurs when the `backfill` parameter is set to `true` + // + // 3. Detect "holes" in the index: + // - If `backfill` is `false` and the index lacks data for the specified epoch, the API returns an error indicating missing data + // + // Parameters: + // - epoch: The specific chain epoch for which to validate/backfill the index. + // - backfill: A boolean flag indicating whether to attempt backfilling of missing data if the index does not have data for the + // specified epoch. + // + // Returns: + // - *types.IndexValidation: A pointer to an IndexValidation struct containing the results of the validation/backfill. + // - error: An error object if the validation/backfill fails. The error message will contain details about the index + // corruption if the call fails because of an incosistency between indexed data and the actual chain state. + // Note: The API returns an error if the index does not have data for the specified epoch and backfill is set to false. + ChainValidateIndex(ctx context.Context, epoch abi.ChainEpoch, backfill bool) (*types.IndexValidation, error) //perm:write + // MethodGroup: Chain // The Chain method group contains methods for interacting with the // blockchain, but that do not require any form of state computation. diff --git a/api/mocks/mock_full.go b/api/mocks/mock_full.go index c574753d8f4..e5d827d18ae 100644 --- a/api/mocks/mock_full.go +++ b/api/mocks/mock_full.go @@ -511,6 +511,21 @@ func (mr *MockFullNodeMockRecorder) ChainTipSetWeight(arg0, arg1 interface{}) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainTipSetWeight", reflect.TypeOf((*MockFullNode)(nil).ChainTipSetWeight), arg0, arg1) } +// ChainValidateIndex mocks base method. +func (m *MockFullNode) ChainValidateIndex(arg0 context.Context, arg1 abi.ChainEpoch, arg2 bool) (*types.IndexValidation, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ChainValidateIndex", arg0, arg1, arg2) + ret0, _ := ret[0].(*types.IndexValidation) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ChainValidateIndex indicates an expected call of ChainValidateIndex. +func (mr *MockFullNodeMockRecorder) ChainValidateIndex(arg0, arg1, arg2 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChainValidateIndex", reflect.TypeOf((*MockFullNode)(nil).ChainValidateIndex), arg0, arg1, arg2) +} + // Closing mocks base method. func (m *MockFullNode) Closing(arg0 context.Context) (<-chan struct{}, error) { m.ctrl.T.Helper() diff --git a/api/proxy_gen.go b/api/proxy_gen.go index e0b39ded673..1d7bf7392e5 100644 --- a/api/proxy_gen.go +++ b/api/proxy_gen.go @@ -170,6 +170,8 @@ type FullNodeMethods struct { ChainTipSetWeight func(p0 context.Context, p1 types.TipSetKey) (types.BigInt, error) `perm:"read"` + ChainValidateIndex func(p0 context.Context, p1 abi.ChainEpoch, p2 bool) (*types.IndexValidation, error) `perm:"write"` + CreateBackup func(p0 context.Context, p1 string) error `perm:"admin"` EthAccounts func(p0 context.Context) ([]ethtypes.EthAddress, error) `perm:"read"` @@ -1649,6 +1651,17 @@ func (s *FullNodeStub) ChainTipSetWeight(p0 context.Context, p1 types.TipSetKey) return *new(types.BigInt), ErrNotSupported } +func (s *FullNodeStruct) ChainValidateIndex(p0 context.Context, p1 abi.ChainEpoch, p2 bool) (*types.IndexValidation, error) { + if s.Internal.ChainValidateIndex == nil { + return nil, ErrNotSupported + } + return s.Internal.ChainValidateIndex(p0, p1, p2) +} + +func (s *FullNodeStub) ChainValidateIndex(p0 context.Context, p1 abi.ChainEpoch, p2 bool) (*types.IndexValidation, error) { + return nil, ErrNotSupported +} + func (s *FullNodeStruct) CreateBackup(p0 context.Context, p1 string) error { if s.Internal.CreateBackup == nil { return ErrNotSupported diff --git a/build/openrpc/full.json b/build/openrpc/full.json index 863576e0f96..6c7581c27a2 100644 --- a/build/openrpc/full.json +++ b/build/openrpc/full.json @@ -37,7 +37,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1344" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1346" } }, { @@ -60,7 +60,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1355" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1357" } }, { @@ -103,7 +103,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1366" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1368" } }, { @@ -214,7 +214,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1388" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1390" } }, { @@ -454,7 +454,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1399" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1401" } }, { @@ -685,7 +685,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1410" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1412" } }, { @@ -784,7 +784,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1421" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1423" } }, { @@ -816,7 +816,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1432" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1434" } }, { @@ -922,7 +922,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1443" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1445" } }, { @@ -1019,7 +1019,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1454" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1456" } }, { @@ -1078,7 +1078,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1465" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1467" } }, { @@ -1171,7 +1171,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1476" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1478" } }, { @@ -1255,7 +1255,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1487" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1489" } }, { @@ -1355,7 +1355,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1498" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1500" } }, { @@ -1411,7 +1411,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1509" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1511" } }, { @@ -1484,7 +1484,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1520" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1522" } }, { @@ -1557,7 +1557,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1531" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1533" } }, { @@ -1604,7 +1604,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1542" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1544" } }, { @@ -1636,7 +1636,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1553" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1555" } }, { @@ -1691,7 +1691,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1564" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1566" } }, { @@ -1743,7 +1743,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1586" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1588" } }, { @@ -1780,7 +1780,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1597" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1599" } }, { @@ -1827,7 +1827,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1608" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1610" } }, { @@ -1874,7 +1874,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1619" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1621" } }, { @@ -1954,7 +1954,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1630" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1632" } }, { @@ -2006,7 +2006,111 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1641" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1643" + } + }, + { + "name": "Filecoin.ChainValidateIndex", + "description": "```go\nfunc (s *FullNodeStruct) ChainValidateIndex(p0 context.Context, p1 abi.ChainEpoch, p2 bool) (*types.IndexValidation, error) {\n\tif s.Internal.ChainValidateIndex == nil {\n\t\treturn nil, ErrNotSupported\n\t}\n\treturn s.Internal.ChainValidateIndex(p0, p1, p2)\n}\n```", + "summary": "ChainValidateIndex validates the integrity of and optionally backfills\nthe chain index at a specific epoch.\n\nIt can be used to:\n\n1. Validate the chain index at a specific epoch:\n - Ensures consistency between indexed data and actual chain state\n - Reports any errors found during validation (i.e. the indexed data does not match the actual chain state, missing data, etc.)\n\n2. Optionally backfill missing data:\n - Backfills data if the index is missing information for the specified epoch\n - Backfilling only occurs when the `backfill` parameter is set to `true`\n\n3. Detect \"holes\" in the index:\n - If `backfill` is `false` and the index lacks data for the specified epoch, the API returns an error indicating missing data\n\nParameters:\n - epoch: The specific chain epoch for which to validate/backfill the index.\n - backfill: A boolean flag indicating whether to attempt backfilling of missing data if the index does not have data for the\n specified epoch.\n\nReturns:\n - *types.IndexValidation: A pointer to an IndexValidation struct containing the results of the validation/backfill.\n - error: An error object if the validation/backfill fails. The error message will contain details about the index\n corruption if the call fails because of an incosistency between indexed data and the actual chain state.\n Note: The API returns an error if the index does not have data for the specified epoch and backfill is set to false.\n", + "paramStructure": "by-position", + "params": [ + { + "name": "p1", + "description": "abi.ChainEpoch", + "summary": "", + "schema": { + "title": "number", + "description": "Number is a number", + "examples": [ + 10101 + ], + "type": [ + "number" + ] + }, + "required": true, + "deprecated": false + }, + { + "name": "p2", + "description": "bool", + "summary": "", + "schema": { + "examples": [ + true + ], + "type": [ + "boolean" + ] + }, + "required": true, + "deprecated": false + } + ], + "result": { + "name": "*types.IndexValidation", + "description": "*types.IndexValidation", + "summary": "", + "schema": { + "examples": [ + { + "TipSetKey": [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ], + "Height": 10101, + "IndexedMessagesCount": 42, + "IndexedEventsCount": 42, + "IndexedEventEntriesCount": 42, + "Backfilled": true, + "IsNullRound": true + } + ], + "additionalProperties": false, + "properties": { + "Backfilled": { + "type": "boolean" + }, + "Height": { + "title": "number", + "type": "number" + }, + "IndexedEventEntriesCount": { + "title": "number", + "type": "number" + }, + "IndexedEventsCount": { + "title": "number", + "type": "number" + }, + "IndexedMessagesCount": { + "title": "number", + "type": "number" + }, + "IsNullRound": { + "type": "boolean" + }, + "TipSetKey": { + "additionalProperties": false, + "type": "object" + } + }, + "type": [ + "object" + ] + }, + "required": true, + "deprecated": false + }, + "deprecated": false, + "externalDocs": { + "description": "Github remote link", + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1654" } }, { @@ -2045,7 +2149,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1652" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1665" } }, { @@ -2092,7 +2196,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1663" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1676" } }, { @@ -2147,7 +2251,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1674" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1687" } }, { @@ -2176,7 +2280,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1685" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1698" } }, { @@ -2313,7 +2417,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1696" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1709" } }, { @@ -2342,7 +2446,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1707" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1720" } }, { @@ -2396,7 +2500,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1718" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1731" } }, { @@ -2487,7 +2591,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1729" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1742" } }, { @@ -2515,7 +2619,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1740" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1753" } }, { @@ -2605,7 +2709,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1751" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1764" } }, { @@ -2861,7 +2965,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1762" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1775" } }, { @@ -3106,7 +3210,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1773" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1786" } }, { @@ -3382,7 +3486,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1784" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1797" } }, { @@ -3675,7 +3779,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1795" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1808" } }, { @@ -3731,7 +3835,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1806" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1819" } }, { @@ -3778,7 +3882,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1817" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1830" } }, { @@ -3876,7 +3980,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1828" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1841" } }, { @@ -3942,7 +4046,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1839" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1852" } }, { @@ -4008,7 +4112,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1850" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1863" } }, { @@ -4117,7 +4221,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1861" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1874" } }, { @@ -4175,7 +4279,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1872" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1885" } }, { @@ -4297,7 +4401,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1883" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1896" } }, { @@ -4506,7 +4610,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1894" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1907" } }, { @@ -4706,7 +4810,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1905" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1918" } }, { @@ -4898,7 +5002,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1916" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1929" } }, { @@ -5107,7 +5211,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1927" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1940" } }, { @@ -5198,7 +5302,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1938" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1951" } }, { @@ -5256,7 +5360,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1949" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1962" } }, { @@ -5514,7 +5618,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1960" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1973" } }, { @@ -5789,7 +5893,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1971" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1984" } }, { @@ -5817,7 +5921,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1982" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1995" } }, { @@ -5855,7 +5959,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L1993" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2006" } }, { @@ -5963,7 +6067,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2004" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2017" } }, { @@ -6001,7 +6105,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2015" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2028" } }, { @@ -6030,7 +6134,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2026" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2039" } }, { @@ -6093,7 +6197,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2037" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2050" } }, { @@ -6156,7 +6260,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2048" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2061" } }, { @@ -6219,7 +6323,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2059" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2072" } }, { @@ -6264,7 +6368,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2070" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2083" } }, { @@ -6386,7 +6490,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2081" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2094" } }, { @@ -6562,7 +6666,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2092" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2105" } }, { @@ -6717,7 +6821,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2103" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2116" } }, { @@ -6839,7 +6943,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2114" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2127" } }, { @@ -6893,7 +6997,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2125" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2138" } }, { @@ -6947,7 +7051,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2136" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2149" } }, { @@ -7132,7 +7236,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2147" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2160" } }, { @@ -7215,7 +7319,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2158" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2171" } }, { @@ -7298,7 +7402,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2169" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2182" } }, { @@ -7465,7 +7569,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2180" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2193" } }, { @@ -7670,7 +7774,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2191" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2204" } }, { @@ -7764,7 +7868,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2202" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2215" } }, { @@ -7810,7 +7914,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2213" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2226" } }, { @@ -7837,7 +7941,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2224" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2237" } }, { @@ -7916,7 +8020,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2235" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2248" } }, { @@ -7979,7 +8083,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2246" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2259" } }, { @@ -8122,7 +8226,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2257" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2270" } }, { @@ -8249,7 +8353,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2268" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2281" } }, { @@ -8351,7 +8455,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2279" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2292" } }, { @@ -8574,7 +8678,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2290" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2303" } }, { @@ -8757,7 +8861,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2301" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2314" } }, { @@ -8837,7 +8941,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2312" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2325" } }, { @@ -8882,7 +8986,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2323" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2336" } }, { @@ -8938,7 +9042,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2334" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2347" } }, { @@ -9018,7 +9122,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2345" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2358" } }, { @@ -9098,7 +9202,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2356" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2369" } }, { @@ -9583,7 +9687,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2367" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2380" } }, { @@ -9777,7 +9881,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2378" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2391" } }, { @@ -9932,7 +10036,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2389" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2402" } }, { @@ -10181,7 +10285,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2400" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2413" } }, { @@ -10336,7 +10440,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2411" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2424" } }, { @@ -10513,7 +10617,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2422" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2435" } }, { @@ -10611,7 +10715,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2433" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2446" } }, { @@ -10776,7 +10880,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2444" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2457" } }, { @@ -10815,7 +10919,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2455" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2468" } }, { @@ -10880,7 +10984,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2466" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2479" } }, { @@ -10926,7 +11030,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2477" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2490" } }, { @@ -11076,7 +11180,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2488" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2501" } }, { @@ -11213,7 +11317,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2499" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2512" } }, { @@ -11444,7 +11548,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2510" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2523" } }, { @@ -11581,7 +11685,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2521" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2534" } }, { @@ -11746,7 +11850,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2532" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2545" } }, { @@ -11823,7 +11927,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2543" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2556" } }, { @@ -12018,7 +12122,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2565" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2578" } }, { @@ -12197,7 +12301,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2576" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2589" } }, { @@ -12359,7 +12463,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2587" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2600" } }, { @@ -12507,7 +12611,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2598" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2611" } }, { @@ -12735,7 +12839,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2609" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2622" } }, { @@ -12883,7 +12987,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2620" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2633" } }, { @@ -13095,7 +13199,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2631" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2644" } }, { @@ -13301,7 +13405,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2642" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2655" } }, { @@ -13369,7 +13473,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2653" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2666" } }, { @@ -13486,7 +13590,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2664" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2677" } }, { @@ -13577,7 +13681,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2675" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2688" } }, { @@ -13663,7 +13767,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2686" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2699" } }, { @@ -13858,7 +13962,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2697" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2710" } }, { @@ -14020,7 +14124,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2708" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2721" } }, { @@ -14216,7 +14320,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2719" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2732" } }, { @@ -14396,7 +14500,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2730" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2743" } }, { @@ -14559,7 +14663,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2741" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2754" } }, { @@ -14586,7 +14690,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2752" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2765" } }, { @@ -14613,7 +14717,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2763" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2776" } }, { @@ -14712,7 +14816,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2774" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2787" } }, { @@ -14758,7 +14862,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2785" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2798" } }, { @@ -14858,7 +14962,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2796" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2809" } }, { @@ -14974,7 +15078,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2807" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2820" } }, { @@ -15022,7 +15126,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2818" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2831" } }, { @@ -15114,7 +15218,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2829" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2842" } }, { @@ -15229,7 +15333,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2840" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2853" } }, { @@ -15277,7 +15381,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2851" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2864" } }, { @@ -15314,7 +15418,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2862" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2875" } }, { @@ -15586,7 +15690,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2873" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2886" } }, { @@ -15634,7 +15738,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2884" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2897" } }, { @@ -15692,7 +15796,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2895" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2908" } }, { @@ -15897,7 +16001,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2906" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2919" } }, { @@ -16100,7 +16204,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2917" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2930" } }, { @@ -16269,7 +16373,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2928" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2941" } }, { @@ -16473,7 +16577,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2939" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2952" } }, { @@ -16640,7 +16744,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2950" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2963" } }, { @@ -16847,7 +16951,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2961" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2974" } }, { @@ -16915,7 +17019,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2972" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2985" } }, { @@ -16967,7 +17071,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2983" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2996" } }, { @@ -17016,7 +17120,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L2994" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3007" } }, { @@ -17107,7 +17211,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3005" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3018" } }, { @@ -17613,7 +17717,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3016" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3029" } }, { @@ -17719,7 +17823,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3027" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3040" } }, { @@ -17771,7 +17875,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3038" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3051" } }, { @@ -18323,7 +18427,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3049" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3062" } }, { @@ -18437,7 +18541,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3060" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3073" } }, { @@ -18534,7 +18638,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3071" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3084" } }, { @@ -18634,7 +18738,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3082" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3095" } }, { @@ -18722,7 +18826,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3093" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3106" } }, { @@ -18822,7 +18926,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3104" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3117" } }, { @@ -18909,7 +19013,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3115" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3128" } }, { @@ -19000,7 +19104,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3126" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3139" } }, { @@ -19125,7 +19229,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3137" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3150" } }, { @@ -19234,7 +19338,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3148" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3161" } }, { @@ -19304,7 +19408,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3159" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3172" } }, { @@ -19407,7 +19511,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3170" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3183" } }, { @@ -19468,7 +19572,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3181" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3194" } }, { @@ -19598,7 +19702,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3192" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3205" } }, { @@ -19705,7 +19809,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3203" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3216" } }, { @@ -19924,7 +20028,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3214" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3227" } }, { @@ -20001,7 +20105,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3225" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3238" } }, { @@ -20078,7 +20182,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3236" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3249" } }, { @@ -20187,7 +20291,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3247" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3260" } }, { @@ -20296,7 +20400,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3258" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3271" } }, { @@ -20357,7 +20461,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3269" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3282" } }, { @@ -20467,7 +20571,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3280" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3293" } }, { @@ -20528,7 +20632,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3291" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3304" } }, { @@ -20596,7 +20700,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3302" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3315" } }, { @@ -20664,7 +20768,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3313" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3326" } }, { @@ -20745,7 +20849,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3324" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3337" } }, { @@ -20899,7 +21003,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3335" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3348" } }, { @@ -20971,7 +21075,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3346" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3359" } }, { @@ -21135,7 +21239,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3357" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3370" } }, { @@ -21300,7 +21404,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3368" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3381" } }, { @@ -21370,7 +21474,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3379" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3392" } }, { @@ -21438,7 +21542,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3390" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3403" } }, { @@ -21531,7 +21635,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3401" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3414" } }, { @@ -21602,7 +21706,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3412" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3425" } }, { @@ -21803,7 +21907,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3423" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3436" } }, { @@ -21935,7 +22039,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3434" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3447" } }, { @@ -22038,7 +22142,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3445" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3458" } }, { @@ -22175,7 +22279,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3456" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3469" } }, { @@ -22286,7 +22390,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3467" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3480" } }, { @@ -22418,7 +22522,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3478" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3491" } }, { @@ -22549,7 +22653,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3489" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3502" } }, { @@ -22620,7 +22724,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3500" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3513" } }, { @@ -22704,7 +22808,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3511" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3524" } }, { @@ -22790,7 +22894,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3522" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3535" } }, { @@ -22973,7 +23077,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3533" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3546" } }, { @@ -23000,7 +23104,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3544" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3557" } }, { @@ -23053,7 +23157,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3555" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3568" } }, { @@ -23141,7 +23245,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3566" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3579" } }, { @@ -23592,7 +23696,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3577" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3590" } }, { @@ -23759,7 +23863,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3588" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3601" } }, { @@ -23857,7 +23961,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3599" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3612" } }, { @@ -24030,7 +24134,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3610" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3623" } }, { @@ -24128,7 +24232,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3621" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3634" } }, { @@ -24279,7 +24383,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3632" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3645" } }, { @@ -24364,7 +24468,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3643" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3656" } }, { @@ -24432,7 +24536,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3654" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3667" } }, { @@ -24484,7 +24588,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3665" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3678" } }, { @@ -24552,7 +24656,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3676" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3689" } }, { @@ -24713,7 +24817,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3687" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3700" } }, { @@ -24760,7 +24864,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3709" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3722" } }, { @@ -24807,7 +24911,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3720" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3733" } }, { @@ -24850,7 +24954,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3742" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3755" } }, { @@ -24946,7 +25050,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3753" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3766" } }, { @@ -25212,7 +25316,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3764" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3777" } }, { @@ -25235,7 +25339,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3775" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3788" } }, { @@ -25278,7 +25382,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3786" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3799" } }, { @@ -25329,7 +25433,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3797" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3810" } }, { @@ -25374,7 +25478,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3808" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3821" } }, { @@ -25402,7 +25506,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3819" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3832" } }, { @@ -25442,7 +25546,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3830" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3843" } }, { @@ -25501,7 +25605,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3841" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3854" } }, { @@ -25545,7 +25649,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3852" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3865" } }, { @@ -25604,7 +25708,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3863" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3876" } }, { @@ -25641,7 +25745,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3874" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3887" } }, { @@ -25685,7 +25789,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3885" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3898" } }, { @@ -25725,7 +25829,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3896" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3909" } }, { @@ -25800,7 +25904,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3907" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3920" } }, { @@ -26008,7 +26112,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3918" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3931" } }, { @@ -26052,7 +26156,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3929" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3942" } }, { @@ -26142,7 +26246,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3940" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3953" } }, { @@ -26169,7 +26273,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3951" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3964" } } ] diff --git a/build/openrpc/gateway.json b/build/openrpc/gateway.json index d505b5d2769..0fb1576a48a 100644 --- a/build/openrpc/gateway.json +++ b/build/openrpc/gateway.json @@ -242,7 +242,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3962" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3975" } }, { @@ -473,7 +473,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3973" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3986" } }, { @@ -572,7 +572,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3984" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3997" } }, { @@ -604,7 +604,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L3995" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4008" } }, { @@ -710,7 +710,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4006" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4019" } }, { @@ -803,7 +803,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4017" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4030" } }, { @@ -887,7 +887,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4028" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4041" } }, { @@ -987,7 +987,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4039" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4052" } }, { @@ -1043,7 +1043,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4050" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4063" } }, { @@ -1116,7 +1116,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4061" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4074" } }, { @@ -1189,7 +1189,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4072" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4085" } }, { @@ -1236,7 +1236,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4083" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4096" } }, { @@ -1268,7 +1268,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4094" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4107" } }, { @@ -1305,7 +1305,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4116" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4129" } }, { @@ -1352,7 +1352,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4127" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4140" } }, { @@ -1392,7 +1392,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4138" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4151" } }, { @@ -1439,7 +1439,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4149" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4162" } }, { @@ -1494,7 +1494,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4160" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4173" } }, { @@ -1523,7 +1523,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4171" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4184" } }, { @@ -1660,7 +1660,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4182" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4195" } }, { @@ -1689,7 +1689,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4193" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4206" } }, { @@ -1743,7 +1743,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4204" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4217" } }, { @@ -1834,7 +1834,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4215" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4228" } }, { @@ -1862,7 +1862,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4226" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4239" } }, { @@ -1952,7 +1952,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4237" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4250" } }, { @@ -2208,7 +2208,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4248" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4261" } }, { @@ -2453,7 +2453,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4259" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4272" } }, { @@ -2729,7 +2729,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4270" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4283" } }, { @@ -3022,7 +3022,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4281" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4294" } }, { @@ -3078,7 +3078,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4292" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4305" } }, { @@ -3125,7 +3125,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4303" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4316" } }, { @@ -3223,7 +3223,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4314" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4327" } }, { @@ -3289,7 +3289,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4325" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4338" } }, { @@ -3355,7 +3355,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4336" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4349" } }, { @@ -3464,7 +3464,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4347" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4360" } }, { @@ -3522,7 +3522,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4358" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4371" } }, { @@ -3644,7 +3644,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4369" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4382" } }, { @@ -3836,7 +3836,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4380" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4393" } }, { @@ -4045,7 +4045,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4391" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4404" } }, { @@ -4136,7 +4136,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4402" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4415" } }, { @@ -4194,7 +4194,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4413" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4426" } }, { @@ -4452,7 +4452,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4424" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4437" } }, { @@ -4727,7 +4727,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4435" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4448" } }, { @@ -4755,7 +4755,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4446" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4459" } }, { @@ -4793,7 +4793,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4457" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4470" } }, { @@ -4901,7 +4901,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4468" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4481" } }, { @@ -4939,7 +4939,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4479" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4492" } }, { @@ -4968,7 +4968,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4490" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4503" } }, { @@ -5031,7 +5031,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4501" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4514" } }, { @@ -5094,7 +5094,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4512" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4525" } }, { @@ -5139,7 +5139,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4523" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4536" } }, { @@ -5261,7 +5261,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4534" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4547" } }, { @@ -5437,7 +5437,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4545" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4558" } }, { @@ -5592,7 +5592,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4556" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4569" } }, { @@ -5714,7 +5714,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4567" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4580" } }, { @@ -5768,7 +5768,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4578" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4591" } }, { @@ -5822,7 +5822,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4589" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4602" } }, { @@ -5885,7 +5885,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4600" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4613" } }, { @@ -5987,7 +5987,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4611" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4624" } }, { @@ -6210,7 +6210,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4622" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4635" } }, { @@ -6393,7 +6393,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4633" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4646" } }, { @@ -6587,7 +6587,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4644" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4657" } }, { @@ -6633,7 +6633,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4655" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4668" } }, { @@ -6783,7 +6783,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4666" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4679" } }, { @@ -6920,7 +6920,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4677" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4690" } }, { @@ -6988,7 +6988,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4688" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4701" } }, { @@ -7105,7 +7105,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4699" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4712" } }, { @@ -7196,7 +7196,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4710" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4723" } }, { @@ -7282,7 +7282,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4721" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4734" } }, { @@ -7309,7 +7309,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4732" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4745" } }, { @@ -7336,7 +7336,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4743" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4756" } }, { @@ -7404,7 +7404,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4754" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4767" } }, { @@ -7910,7 +7910,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4765" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4778" } }, { @@ -8007,7 +8007,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4776" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4789" } }, { @@ -8107,7 +8107,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4787" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4800" } }, { @@ -8207,7 +8207,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4798" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4811" } }, { @@ -8332,7 +8332,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4809" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4822" } }, { @@ -8441,7 +8441,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4820" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4833" } }, { @@ -8544,7 +8544,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4831" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4844" } }, { @@ -8674,7 +8674,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4842" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4855" } }, { @@ -8781,7 +8781,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4853" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4866" } }, { @@ -8842,7 +8842,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4864" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4877" } }, { @@ -8910,7 +8910,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4875" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4888" } }, { @@ -8991,7 +8991,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4886" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4899" } }, { @@ -9155,7 +9155,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4897" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4910" } }, { @@ -9248,7 +9248,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4908" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4921" } }, { @@ -9449,7 +9449,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4919" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4932" } }, { @@ -9560,7 +9560,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4930" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4943" } }, { @@ -9691,7 +9691,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4941" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4954" } }, { @@ -9777,7 +9777,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4952" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4965" } }, { @@ -9804,7 +9804,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4963" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4976" } }, { @@ -9857,7 +9857,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4974" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4987" } }, { @@ -9945,7 +9945,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4985" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4998" } }, { @@ -10396,7 +10396,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L4996" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5009" } }, { @@ -10563,7 +10563,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5007" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5020" } }, { @@ -10736,7 +10736,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5018" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5031" } }, { @@ -10804,7 +10804,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5029" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5042" } }, { @@ -10872,7 +10872,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5040" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5053" } }, { @@ -11033,7 +11033,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5051" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5064" } }, { @@ -11078,7 +11078,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5073" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5086" } }, { @@ -11123,7 +11123,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5084" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5097" } }, { @@ -11150,7 +11150,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5095" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5108" } } ] diff --git a/build/openrpc/miner.json b/build/openrpc/miner.json index f8a701ccfea..1ac9768be0a 100644 --- a/build/openrpc/miner.json +++ b/build/openrpc/miner.json @@ -30,7 +30,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5381" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5394" } }, { @@ -109,7 +109,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5392" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5405" } }, { @@ -155,7 +155,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5403" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5416" } }, { @@ -203,7 +203,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5414" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5427" } }, { @@ -251,7 +251,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5425" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5438" } }, { @@ -354,7 +354,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5436" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5449" } }, { @@ -428,7 +428,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5447" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5460" } }, { @@ -591,7 +591,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5458" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5471" } }, { @@ -742,7 +742,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5469" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5482" } }, { @@ -781,7 +781,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5480" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5493" } }, { @@ -913,7 +913,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5491" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5504" } }, { @@ -945,7 +945,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5502" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5515" } }, { @@ -986,7 +986,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5513" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5526" } }, { @@ -1054,7 +1054,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5524" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5537" } }, { @@ -1185,7 +1185,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5535" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5548" } }, { @@ -1316,7 +1316,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5546" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5559" } }, { @@ -1416,7 +1416,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5557" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5570" } }, { @@ -1516,7 +1516,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5568" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5581" } }, { @@ -1616,7 +1616,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5579" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5592" } }, { @@ -1716,7 +1716,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5590" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5603" } }, { @@ -1816,7 +1816,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5601" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5614" } }, { @@ -1916,7 +1916,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5612" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5625" } }, { @@ -2040,7 +2040,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5623" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5636" } }, { @@ -2164,7 +2164,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5634" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5647" } }, { @@ -2279,7 +2279,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5645" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5658" } }, { @@ -2379,7 +2379,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5656" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5669" } }, { @@ -2512,7 +2512,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5667" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5680" } }, { @@ -2636,7 +2636,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5678" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5691" } }, { @@ -2760,7 +2760,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5689" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5702" } }, { @@ -2884,7 +2884,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5700" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5713" } }, { @@ -3017,7 +3017,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5711" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5724" } }, { @@ -3117,7 +3117,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5722" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5735" } }, { @@ -3157,7 +3157,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5733" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5746" } }, { @@ -3229,7 +3229,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5744" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5757" } }, { @@ -3279,7 +3279,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5755" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5768" } }, { @@ -3323,7 +3323,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5766" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5779" } }, { @@ -3364,7 +3364,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5777" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5790" } }, { @@ -3608,7 +3608,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5788" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5801" } }, { @@ -3682,7 +3682,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5799" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5812" } }, { @@ -3732,7 +3732,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5810" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5823" } }, { @@ -3761,7 +3761,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5821" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5834" } }, { @@ -3790,7 +3790,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5832" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5845" } }, { @@ -3846,7 +3846,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5843" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5856" } }, { @@ -3869,7 +3869,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5854" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5867" } }, { @@ -3929,7 +3929,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5865" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5878" } }, { @@ -3968,7 +3968,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5876" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5889" } }, { @@ -4008,7 +4008,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5887" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5900" } }, { @@ -4081,7 +4081,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5898" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5911" } }, { @@ -4145,7 +4145,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5909" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5922" } }, { @@ -4208,7 +4208,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5920" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5933" } }, { @@ -4258,7 +4258,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5931" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5944" } }, { @@ -4817,7 +4817,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5942" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5955" } }, { @@ -4858,7 +4858,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5953" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5966" } }, { @@ -4899,7 +4899,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5964" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5977" } }, { @@ -4940,7 +4940,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5975" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5988" } }, { @@ -4981,7 +4981,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5986" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5999" } }, { @@ -5022,7 +5022,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L5997" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6010" } }, { @@ -5053,7 +5053,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6008" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6021" } }, { @@ -5103,7 +5103,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6019" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6032" } }, { @@ -5144,7 +5144,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6030" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6043" } }, { @@ -5183,7 +5183,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6041" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6054" } }, { @@ -5247,7 +5247,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6052" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6065" } }, { @@ -5305,7 +5305,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6063" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6076" } }, { @@ -5752,7 +5752,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6074" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6087" } }, { @@ -5788,7 +5788,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6085" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6098" } }, { @@ -5931,7 +5931,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6096" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6109" } }, { @@ -5987,7 +5987,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6107" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6120" } }, { @@ -6026,7 +6026,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6118" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6131" } }, { @@ -6203,7 +6203,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6129" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6142" } }, { @@ -6255,7 +6255,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6140" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6153" } }, { @@ -6447,7 +6447,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6151" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6164" } }, { @@ -6547,7 +6547,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6162" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6175" } }, { @@ -6601,7 +6601,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6173" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6186" } }, { @@ -6640,7 +6640,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6184" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6197" } }, { @@ -6725,7 +6725,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6195" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6208" } }, { @@ -6919,7 +6919,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6206" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6219" } }, { @@ -7017,7 +7017,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6217" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6230" } }, { @@ -7149,7 +7149,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6228" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6241" } }, { @@ -7203,7 +7203,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6239" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6252" } }, { @@ -7237,7 +7237,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6250" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6263" } }, { @@ -7324,7 +7324,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6261" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6274" } }, { @@ -7378,7 +7378,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6272" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6285" } }, { @@ -7478,7 +7478,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6283" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6296" } }, { @@ -7555,7 +7555,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6294" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6307" } }, { @@ -7646,7 +7646,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6305" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6318" } }, { @@ -7685,7 +7685,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6316" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6329" } }, { @@ -7801,7 +7801,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6327" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6340" } }, { @@ -9901,7 +9901,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6338" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6351" } } ] diff --git a/build/openrpc/worker.json b/build/openrpc/worker.json index 14cec75ca21..77c70b69cb0 100644 --- a/build/openrpc/worker.json +++ b/build/openrpc/worker.json @@ -161,7 +161,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6426" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6439" } }, { @@ -252,7 +252,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6437" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6450" } }, { @@ -420,7 +420,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6448" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6461" } }, { @@ -447,7 +447,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6459" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6472" } }, { @@ -597,7 +597,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6470" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6483" } }, { @@ -700,7 +700,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6481" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6494" } }, { @@ -803,7 +803,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6492" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6505" } }, { @@ -925,7 +925,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6503" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6516" } }, { @@ -1135,7 +1135,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6514" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6527" } }, { @@ -1306,7 +1306,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6525" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6538" } }, { @@ -3350,7 +3350,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6536" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6549" } }, { @@ -3470,7 +3470,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6547" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6560" } }, { @@ -3531,7 +3531,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6558" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6571" } }, { @@ -3569,7 +3569,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6569" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6582" } }, { @@ -3729,7 +3729,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6580" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6593" } }, { @@ -3913,7 +3913,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6591" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6604" } }, { @@ -4054,7 +4054,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6602" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6615" } }, { @@ -4107,7 +4107,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6613" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6626" } }, { @@ -4250,7 +4250,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6624" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6637" } }, { @@ -4474,7 +4474,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6635" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6648" } }, { @@ -4601,7 +4601,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6646" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6659" } }, { @@ -4768,7 +4768,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6657" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6670" } }, { @@ -4895,7 +4895,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6668" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6681" } }, { @@ -4933,7 +4933,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6679" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6692" } }, { @@ -4972,7 +4972,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6690" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6703" } }, { @@ -4995,7 +4995,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6701" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6714" } }, { @@ -5034,7 +5034,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6712" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6725" } }, { @@ -5057,7 +5057,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6723" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6736" } }, { @@ -5096,7 +5096,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6734" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6747" } }, { @@ -5130,7 +5130,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6745" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6758" } }, { @@ -5184,7 +5184,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6756" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6769" } }, { @@ -5223,7 +5223,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6767" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6780" } }, { @@ -5262,7 +5262,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6778" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6791" } }, { @@ -5297,7 +5297,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6789" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6802" } }, { @@ -5477,7 +5477,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6800" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6813" } }, { @@ -5506,7 +5506,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6811" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6824" } }, { @@ -5529,7 +5529,7 @@ "deprecated": false, "externalDocs": { "description": "Github remote link", - "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6822" + "url": "https://github.com/filecoin-project/lotus/blob/master/api/proxy_gen.go#L6835" } } ] diff --git a/chain/index/api.go b/chain/index/api.go new file mode 100644 index 00000000000..aca6a3303b6 --- /dev/null +++ b/chain/index/api.go @@ -0,0 +1,345 @@ +package index + +import ( + "context" + "database/sql" + "errors" + + "github.com/ipfs/go-cid" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +var ErrChainForked = xerrors.New("chain forked") + +func (si *SqliteIndexer) ChainValidateIndex(ctx context.Context, epoch abi.ChainEpoch, backfill bool) (*types.IndexValidation, error) { + // return an error if the indexer is not started + if !si.started { + return nil, errors.New("ChainValidateIndex called before indexer start") + } + + // return an error if the indexer is closed + if si.isClosed() { + return nil, errors.New("ChainValidateIndex called on closed indexer") + } + + // this API only works for epoch < head because of deferred execution in Filecoin + head := si.cs.GetHeaviestTipSet() + if epoch >= head.Height() { + return nil, xerrors.Errorf("cannot validate index at epoch %d, can only validate at an epoch less than chain head epoch %d", epoch, head.Height()) + } + + // fetch the tipset at the given epoch on the canonical chain + expectedTs, err := si.cs.GetTipsetByHeight(ctx, epoch, head, true) + if err != nil { + return nil, xerrors.Errorf("failed to get tipset at height %d: %w", epoch, err) + } + + // we need to take a write lock here so that back-filling does not race with real time chain indexing + si.writerLk.Lock() + defer si.writerLk.Unlock() + + var isIndexEmpty bool + if err := si.stmts.isIndexEmptyStmt.QueryRowContext(ctx).Scan(&isIndexEmpty); err != nil { + return nil, xerrors.Errorf("failed to check if index is empty: %w", err) + } + + // Canonical chain has a null round at the epoch -> return if index is empty otherwise validate that index also + // has a null round at this epoch i.e. it does not have anything indexed at all for this epoch + if expectedTs.Height() != epoch { + if isIndexEmpty { + return &types.IndexValidation{ + Height: epoch, + IsNullRound: true, + }, nil + } + // validate the db has a hole here and error if not, we don't attempt to repair because something must be very wrong for this to fail + return si.validateIsNullRound(ctx, epoch) + } + + // if the index is empty -> short-circuit and simply backfill if applicable + if isIndexEmpty { + if !backfill { + return nil, makeBackfillRequiredErr(epoch) + } + return si.backfillMissingTipset(ctx, expectedTs) + } + // see if the tipset at this epoch is already indexed or if we need to backfill + revertedCount, nonRevertedCount, err := si.getTipsetCountsAtHeight(ctx, epoch) + if err != nil { + if err == sql.ErrNoRows { + if !backfill { + return nil, makeBackfillRequiredErr(epoch) + } + return si.backfillMissingTipset(ctx, expectedTs) + } + return nil, xerrors.Errorf("failed to get tipset counts at height %d: %w", epoch, err) + } + + switch { + case revertedCount == 0 && nonRevertedCount == 0: + // no tipsets at this epoch in the index, backfill + if !backfill { + return nil, makeBackfillRequiredErr(epoch) + } + return si.backfillMissingTipset(ctx, expectedTs) + + case revertedCount > 0 && nonRevertedCount == 0: + return nil, xerrors.Errorf("index corruption: height %d only has reverted tipsets", epoch) + + case nonRevertedCount > 1: + return nil, xerrors.Errorf("index corruption: height %d has multiple non-reverted tipsets", epoch) + } + + // fetch the non-reverted tipset at this epoch + var indexedTsKeyCidBytes []byte + err = si.stmts.getNonRevertedTipsetAtHeightStmt.QueryRowContext(ctx, epoch).Scan(&indexedTsKeyCidBytes) + if err != nil { + return nil, xerrors.Errorf("failed to get non-reverted tipset at height %d: %w", epoch, err) + } + + indexedTsKeyCid, err := cid.Cast(indexedTsKeyCidBytes) + if err != nil { + return nil, xerrors.Errorf("failed to cast tipset key cid: %w", err) + } + expectedTsKeyCid, err := expectedTs.Key().Cid() + if err != nil { + return nil, xerrors.Errorf("failed to get tipset key cid: %w", err) + } + if !indexedTsKeyCid.Equals(expectedTsKeyCid) { + return nil, xerrors.Errorf("index corruption: indexed tipset at height %d has key %s, but canonical chain has %s", epoch, indexedTsKeyCid, expectedTsKeyCid) + } + + getAndVerifyIndexedData := func() (*indexedTipSetData, error) { + indexedData, err := si.getIndexedTipSetData(ctx, expectedTs) + if err != nil { + return nil, xerrors.Errorf("failed to get indexed data for tipset at height %d: %w", expectedTs.Height(), err) + } + if indexedData == nil { + return nil, xerrors.Errorf("nil indexed data for tipset at height %d", expectedTs.Height()) + } + if err = si.verifyIndexedData(ctx, expectedTs, indexedData); err != nil { + return nil, err + } + return indexedData, nil + } + + indexedData, err := getAndVerifyIndexedData() + var bf bool + if err != nil { + if !backfill { + return nil, xerrors.Errorf("failed to verify indexed data at height %d: %w", expectedTs.Height(), err) + } + + log.Warnf("failed to verify indexed data at height %d; err:%s; backfilling once and validating again", expectedTs.Height(), err) + if _, err := si.backfillMissingTipset(ctx, expectedTs); err != nil { + return nil, xerrors.Errorf("failed to backfill missing tipset at height %d during validation; err: %w", expectedTs.Height(), err) + } + + indexedData, err = getAndVerifyIndexedData() + if err != nil { + return nil, xerrors.Errorf("failed to verify indexed data at height %d after backfill: %w", expectedTs.Height(), err) + } + bf = true + } + + return &types.IndexValidation{ + TipSetKey: expectedTs.Key(), + Height: expectedTs.Height(), + IndexedMessagesCount: uint64(indexedData.nonRevertedMessageCount), + IndexedEventsCount: uint64(indexedData.nonRevertedEventCount), + IndexedEventEntriesCount: uint64(indexedData.nonRevertedEventEntriesCount), + Backfilled: bf, + }, nil +} + +func (si *SqliteIndexer) validateIsNullRound(ctx context.Context, epoch abi.ChainEpoch) (*types.IndexValidation, error) { + // make sure we do not have tipset(reverted or non-reverted) indexed at this epoch + var isNullRound bool + err := si.stmts.hasNullRoundAtHeightStmt.QueryRowContext(ctx, epoch).Scan(&isNullRound) + if err != nil { + return nil, xerrors.Errorf("failed to check if null round exists at height %d: %w", epoch, err) + } + if !isNullRound { + return nil, xerrors.Errorf("index corruption: height %d should be a null round but is not", epoch) + } + + return &types.IndexValidation{ + Height: epoch, + IsNullRound: true, + }, nil +} + +func (si *SqliteIndexer) getTipsetCountsAtHeight(ctx context.Context, height abi.ChainEpoch) (revertedCount, nonRevertedCount int, err error) { + err = si.stmts.countTipsetsAtHeightStmt.QueryRowContext(ctx, height).Scan(&revertedCount, &nonRevertedCount) + if err != nil { + if err == sql.ErrNoRows { + // No tipsets found at this height + return 0, 0, nil + } + return 0, 0, xerrors.Errorf("failed to query tipset counts at height %d: %w", height, err) + } + + return revertedCount, nonRevertedCount, nil +} + +type indexedTipSetData struct { + nonRevertedMessageCount int + nonRevertedEventCount int + nonRevertedEventEntriesCount int +} + +// getIndexedTipSetData fetches the indexed tipset data for a tipset +func (si *SqliteIndexer) getIndexedTipSetData(ctx context.Context, ts *types.TipSet) (*indexedTipSetData, error) { + tsKeyCidBytes, err := toTipsetKeyCidBytes(ts) + if err != nil { + return nil, xerrors.Errorf("failed to get tipset key cid: %w", err) + } + + var data indexedTipSetData + err = withTx(ctx, si.db, func(tx *sql.Tx) error { + if err = tx.Stmt(si.stmts.getNonRevertedTipsetMessageCountStmt).QueryRowContext(ctx, tsKeyCidBytes).Scan(&data.nonRevertedMessageCount); err != nil { + return xerrors.Errorf("failed to query non reverted message count: %w", err) + } + + if err = tx.Stmt(si.stmts.getNonRevertedTipsetEventCountStmt).QueryRowContext(ctx, tsKeyCidBytes).Scan(&data.nonRevertedEventCount); err != nil { + return xerrors.Errorf("failed to query non reverted event count: %w", err) + } + + if err = tx.Stmt(si.stmts.getNonRevertedTipsetEventEntriesCountStmt).QueryRowContext(ctx, tsKeyCidBytes).Scan(&data.nonRevertedEventEntriesCount); err != nil { + return xerrors.Errorf("failed to query non reverted event entries count: %w", err) + } + + return nil + }) + + return &data, err +} + +// verifyIndexedData verifies that the indexed data for a tipset is correct +// by comparing the number of messages and events in the chainstore to the number of messages and events indexed. +// +// Notes: +// +// - Events are loaded from the executed messages of the tipset at the next epoch (ts.Height() + 1). +// - This is not a comprehensive verification because we only compare counts, assuming that a match +// means that the entries are correct. A future iteration may compare message and event details to +// confirm that they are what is expected. +func (si *SqliteIndexer) verifyIndexedData(ctx context.Context, ts *types.TipSet, indexedData *indexedTipSetData) (err error) { + tsKeyCid, err := ts.Key().Cid() + if err != nil { + return xerrors.Errorf("failed to get tipset key cid at height %d: %w", ts.Height(), err) + } + + executionTs, err := si.getNextTipset(ctx, ts) + if err != nil { + return xerrors.Errorf("failed to get next tipset for height %d: %w", ts.Height(), err) + } + + // given that `ts` is on the canonical chain and `executionTs` is the next tipset in the chain + // `ts` can not have reverted events + var hasRevertedEventsInTipset bool + err = si.stmts.hasRevertedEventsInTipsetStmt.QueryRowContext(ctx, tsKeyCid.Bytes()).Scan(&hasRevertedEventsInTipset) + if err != nil { + return xerrors.Errorf("failed to check if there are reverted events in tipset for height %d: %w", ts.Height(), err) + } + if hasRevertedEventsInTipset { + return xerrors.Errorf("index corruption: reverted events found for an executed tipset %s at height %d", tsKeyCid, ts.Height()) + } + + executedMsgs, err := si.executedMessagesLoaderFunc(ctx, si.cs, ts, executionTs) + if err != nil { + return xerrors.Errorf("failed to load executed messages for height %d: %w", ts.Height(), err) + } + + var ( + totalEventsCount = 0 + totalEventEntriesCount = 0 + ) + for _, emsg := range executedMsgs { + totalEventsCount += len(emsg.evs) + for _, ev := range emsg.evs { + totalEventEntriesCount += len(ev.Entries) + } + } + + if totalEventsCount != indexedData.nonRevertedEventCount { + return xerrors.Errorf("event count mismatch for height %d: chainstore has %d, index has %d", ts.Height(), totalEventsCount, indexedData.nonRevertedEventCount) + } + + totalExecutedMsgCount := len(executedMsgs) + if totalExecutedMsgCount != indexedData.nonRevertedMessageCount { + return xerrors.Errorf("message count mismatch for height %d: chainstore has %d, index has %d", ts.Height(), totalExecutedMsgCount, indexedData.nonRevertedMessageCount) + } + + if indexedData.nonRevertedEventEntriesCount != totalEventEntriesCount { + return xerrors.Errorf("event entries count mismatch for height %d: chainstore has %d, index has %d", ts.Height(), totalEventEntriesCount, indexedData.nonRevertedEventEntriesCount) + } + + return nil +} + +func (si *SqliteIndexer) backfillMissingTipset(ctx context.Context, ts *types.TipSet) (*types.IndexValidation, error) { + // backfill the tipset in the Index + parentTs, err := si.cs.GetTipSetFromKey(ctx, ts.Parents()) + if err != nil { + return nil, xerrors.Errorf("failed to get parent tipset at height %d: %w", ts.Height(), err) + } + + executionTs, err := si.getNextTipset(ctx, ts) + if err != nil { + return nil, xerrors.Errorf("failed to get next tipset at height %d: %w", ts.Height(), err) + } + + backfillFunc := func() error { + return withTx(ctx, si.db, func(tx *sql.Tx) error { + if err := si.indexTipsetWithParentEvents(ctx, tx, ts, executionTs); err != nil { + return xerrors.Errorf("error indexing (ts, executionTs): %w", err) + } + + if err := si.indexTipsetWithParentEvents(ctx, tx, parentTs, ts); err != nil { + return xerrors.Errorf("error indexing (parentTs, ts): %w", err) + } + + return nil + }) + } + + if err := backfillFunc(); err != nil { + return nil, xerrors.Errorf("failed to backfill tipset: %w", err) + } + + indexedData, err := si.getIndexedTipSetData(ctx, ts) + if err != nil { + return nil, xerrors.Errorf("failed to get indexed tipset data: %w", err) + } + + return &types.IndexValidation{ + TipSetKey: ts.Key(), + Height: ts.Height(), + Backfilled: true, + IndexedMessagesCount: uint64(indexedData.nonRevertedMessageCount), + IndexedEventsCount: uint64(indexedData.nonRevertedEventCount), + IndexedEventEntriesCount: uint64(indexedData.nonRevertedEventEntriesCount), + }, nil +} + +func (si *SqliteIndexer) getNextTipset(ctx context.Context, ts *types.TipSet) (*types.TipSet, error) { + nextEpochTs, err := si.cs.GetTipsetByHeight(ctx, ts.Height()+1, nil, false) + if err != nil { + return nil, xerrors.Errorf("failed to get tipset at height %d: %w", ts.Height()+1, err) + } + + if nextEpochTs.Parents() != ts.Key() { + return nil, xerrors.Errorf("chain forked at height %d; please retry your request; err: %w", ts.Height(), ErrChainForked) + } + + return nextEpochTs, nil +} + +func makeBackfillRequiredErr(height abi.ChainEpoch) error { + return xerrors.Errorf("missing tipset at height %d in the chain index, set backfill flag to true to fix", height) +} diff --git a/chain/index/api_test.go b/chain/index/api_test.go new file mode 100644 index 00000000000..65d317f9e92 --- /dev/null +++ b/chain/index/api_test.go @@ -0,0 +1,499 @@ +package index + +import ( + "context" + pseudo "math/rand" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestValidateIsNullRoundSimple(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + tests := []struct { + name string + epoch abi.ChainEpoch + setupFunc func(*SqliteIndexer) + expectedResult bool + expectError bool + errorContains string + }{ + { + name: "happy path - null round", + epoch: 50, + expectedResult: true, + }, + { + name: "failure - non-null round", + epoch: 50, + setupFunc: func(si *SqliteIndexer) { + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: randomCid(t, rng).Bytes(), + height: 50, + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + }, + expectError: true, + errorContains: "index corruption", + }, + { + name: "edge case - epoch 0", + epoch: 0, + expectedResult: true, + }, + { + name: "edge case - epoch above head", + epoch: headHeight + 1, + expectedResult: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + si, _, _ := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + + if tt.setupFunc != nil { + tt.setupFunc(si) + } + + res, err := si.validateIsNullRound(ctx, tt.epoch) + + if tt.expectError { + require.Error(t, err) + if tt.errorContains != "" { + require.ErrorContains(t, err, tt.errorContains) + } + } else { + require.NoError(t, err) + require.NotNil(t, res) + require.Equal(t, tt.expectedResult, res.IsNullRound) + require.Equal(t, tt.epoch, res.Height) + } + }) + } +} + +func TestFailureHeadHeight(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + si, head, _ := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + si.Start() + + _, err := si.ChainValidateIndex(ctx, head.Height(), false) + require.Error(t, err) + require.Contains(t, err.Error(), "cannot validate index at epoch") +} + +func TestBackfillNullRound(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + si.Start() + + nullRoundEpoch := abi.ChainEpoch(50) + nonNullRoundEpoch := abi.ChainEpoch(51) + + // Create a tipset with a height different from the requested epoch + nonNullTs := fakeTipSet(t, rng, nonNullRoundEpoch, []cid.Cid{}) + + // Set up the chainstore to return the non-null tipset for the null round epoch + cs.SetTipsetByHeightAndKey(nullRoundEpoch, nonNullTs.Key(), nonNullTs) + + // Attempt to validate the null round epoch + result, err := si.ChainValidateIndex(ctx, nullRoundEpoch, true) + require.NoError(t, err) + require.NotNil(t, result) + require.False(t, result.Backfilled) + require.True(t, result.IsNullRound) +} + +func TestBackfillReturnsError(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + si.Start() + + missingEpoch := abi.ChainEpoch(50) + + // Create a tipset for the missing epoch, but don't index it + missingTs := fakeTipSet(t, rng, missingEpoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(missingEpoch, missingTs.Key(), missingTs) + + // Attempt to validate the missing epoch with backfill flag set to false + _, err := si.ChainValidateIndex(ctx, missingEpoch, false) + require.Error(t, err) + require.ErrorContains(t, err, "missing tipset at height 50 in the chain index") +} + +func TestBackfillMissingEpoch(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + si.Start() + + // Initialize address resolver + si.SetActorToDelegatedAddresFunc(func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + idAddr, err := address.NewIDAddress(uint64(emitter)) + if err != nil { + return address.Undef, false + } + return idAddr, true + }) + + missingEpoch := abi.ChainEpoch(50) + + parentTs := fakeTipSet(t, rng, missingEpoch-1, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(missingEpoch-1, parentTs.Key(), parentTs) + + missingTs := fakeTipSet(t, rng, missingEpoch, parentTs.Cids()) + cs.SetTipsetByHeightAndKey(missingEpoch, missingTs.Key(), missingTs) + + executionTs := fakeTipSet(t, rng, missingEpoch+1, missingTs.Key().Cids()) + cs.SetTipsetByHeightAndKey(missingEpoch+1, executionTs.Key(), executionTs) + + // Create fake messages and events + fakeMsg := fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng)) + fakeEvent := fakeEvent(1, []kv{{k: "test", v: []byte("value")}, {k: "test2", v: []byte("value2")}}, nil) + + executedMsg := executedMessage{ + msg: fakeMsg, + evs: []types.Event{*fakeEvent}, + } + + cs.SetMessagesForTipset(missingTs, []types.ChainMsg{fakeMsg}) + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + if msgTs.Height() == missingTs.Height() { + return []executedMessage{executedMsg}, nil + } + return nil, nil + }) + + // Attempt to validate and backfill the missing epoch + result, err := si.ChainValidateIndex(ctx, missingEpoch, true) + require.NoError(t, err) + require.NotNil(t, result) + require.True(t, result.Backfilled) + require.EqualValues(t, missingEpoch, result.Height) + require.Equal(t, uint64(1), result.IndexedMessagesCount) + require.Equal(t, uint64(1), result.IndexedEventsCount) + require.Equal(t, uint64(2), result.IndexedEventEntriesCount) + + // Verify that the epoch is now indexed + verificationResult, err := si.ChainValidateIndex(ctx, missingEpoch, false) + require.NoError(t, err) + require.NotNil(t, verificationResult) + require.False(t, verificationResult.Backfilled) + require.Equal(t, result.IndexedMessagesCount, verificationResult.IndexedMessagesCount) + require.Equal(t, result.IndexedEventsCount, verificationResult.IndexedEventsCount) + require.Equal(t, result.IndexedEventEntriesCount, verificationResult.IndexedEventEntriesCount) +} + +func TestIndexCorruption(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(100) + + tests := []struct { + name string + setupFunc func(*testing.T, *SqliteIndexer, *dummyChainStore) + epoch abi.ChainEpoch + errorContains string + }{ + { + name: "only reverted tipsets", + setupFunc: func(t *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts) + keyBz, err := ts.Key().Cid() + require.NoError(t, err) + + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: true, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + }, + epoch: 50, + errorContains: "index corruption: height 50 only has reverted tipsets", + }, + { + name: "multiple non-reverted tipsets", + setupFunc: func(t *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts1 := fakeTipSet(t, rng, epoch, []cid.Cid{}) + ts2 := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts1.Key(), ts1) + + t1Bz, err := toTipsetKeyCidBytes(ts1) + require.NoError(t, err) + t2Bz, err := toTipsetKeyCidBytes(ts2) + require.NoError(t, err) + + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: t1Bz, + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: t2Bz, + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + }, + epoch: 50, + errorContains: "index corruption: height 50 has multiple non-reverted tipsets", + }, + { + name: "tipset key mismatch", + setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts1 := fakeTipSet(t, rng, epoch, []cid.Cid{}) + ts2 := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts1.Key(), ts1) + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: ts2.Key().Cids()[0].Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + }, + epoch: 50, + errorContains: "index corruption: indexed tipset at height 50 has key", + }, + { + name: "reverted events for executed tipset", + setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts) + keyBz, err := ts.Key().Cid() + require.NoError(t, err) + + messageID := insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + insertEvent(t, si, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 1, + emitterAddr: randomIDAddr(t, rng).Bytes(), + reverted: true, + }) + cs.SetTipsetByHeightAndKey(epoch+1, fakeTipSet(t, rng, epoch+1, ts.Key().Cids()).Key(), fakeTipSet(t, rng, epoch+1, ts.Key().Cids())) + }, + epoch: 50, + errorContains: "index corruption: reverted events found for an executed tipset", + }, + { + name: "message count mismatch", + setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts) + keyBz, err := ts.Key().Cid() + require.NoError(t, err) + + // Insert two messages in the index + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 1, + }) + + // Setup dummy event loader + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{{msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng))}}, nil + }) + + // Set up the next tipset for event execution + nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids()) + cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs) + }, + epoch: 50, + errorContains: "failed to verify indexed data at height 50: message count mismatch for height 50: chainstore has 1, index has 2", + }, + { + name: "event count mismatch", + setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts) + keyBz, err := ts.Key().Cid() + require.NoError(t, err) + + // Insert one message in the index + messageID := insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + + // Insert two events for the message + insertEvent(t, si, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 2, + emitterAddr: randomIDAddr(t, rng).Bytes(), + reverted: false, + }) + insertEvent(t, si, event{ + messageID: messageID, + eventIndex: 1, + emitterId: 3, + emitterAddr: randomIDAddr(t, rng).Bytes(), + reverted: false, + }) + + // Setup dummy event loader to return only one event + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{ + { + msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng)), + evs: []types.Event{*fakeEvent(1, []kv{{k: "test", v: []byte("value")}}, nil)}, + }, + }, nil + }) + + // Set up the next tipset for event execution + nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids()) + cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs) + }, + epoch: 50, + errorContains: "failed to verify indexed data at height 50: event count mismatch for height 50: chainstore has 1, index has 2", + }, + { + name: "event entries count mismatch", + setupFunc: func(_ *testing.T, si *SqliteIndexer, cs *dummyChainStore) { + epoch := abi.ChainEpoch(50) + ts := fakeTipSet(t, rng, epoch, []cid.Cid{}) + cs.SetTipsetByHeightAndKey(epoch, ts.Key(), ts) + keyBz, err := ts.Key().Cid() + require.NoError(t, err) + + // Insert one message in the index + messageID := insertTipsetMessage(t, si, tipsetMessage{ + tipsetKeyCid: keyBz.Bytes(), + height: uint64(epoch), + reverted: false, + messageCid: randomCid(t, rng).Bytes(), + messageIndex: 0, + }) + + // Insert one event with two entries for the message + eventID := insertEvent(t, si, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 4, + emitterAddr: randomIDAddr(t, rng).Bytes(), + reverted: false, + }) + insertEventEntry(t, si, eventEntry{ + eventID: eventID, + indexed: true, + flags: []byte{0x01}, + key: "key1", + codec: 1, + value: []byte("value1"), + }) + insertEventEntry(t, si, eventEntry{ + eventID: eventID, + indexed: true, + flags: []byte{0x00}, + key: "key2", + codec: 2, + value: []byte("value2"), + }) + + // Setup dummy event loader to return one event with only one entry + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{ + { + msg: fakeMessage(randomIDAddr(t, rng), randomIDAddr(t, rng)), + evs: []types.Event{*fakeEvent(1, []kv{{k: "key1", v: []byte("value1")}}, nil)}, + }, + }, nil + }) + + // Set up the next tipset for event execution + nextTs := fakeTipSet(t, rng, epoch+1, ts.Key().Cids()) + cs.SetTipsetByHeightAndKey(epoch+1, nextTs.Key(), nextTs) + }, + epoch: 50, + errorContains: "failed to verify indexed data at height 50: event entries count mismatch for height 50: chainstore has 1, index has 2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + si.Start() + + tt.setupFunc(t, si, cs) + + _, err := si.ChainValidateIndex(ctx, tt.epoch, false) + require.Error(t, err) + require.Contains(t, err.Error(), tt.errorContains) + }) + } +} diff --git a/chain/index/chain-indexing-overview-for-rpc-providers.MD b/chain/index/chain-indexing-overview-for-rpc-providers.MD new file mode 100644 index 00000000000..89359dcd65c --- /dev/null +++ b/chain/index/chain-indexing-overview-for-rpc-providers.MD @@ -0,0 +1,220 @@ +# ChainIndexer Documentation for RPC Providers + +## Introduction + +This document is for RPC providers and node operators who serve RPC requests walking through the configuration changes, migration flow and operations/maintenance work needed to enable, backfill and maintain the [`ChainIndexer`](#chainindexer-indexing-system). + +**Note: If you are a Storage Provider or node operator who does not serve RPC requests (i.e, if `Fevm.EnableEthRPC = false`), you can skip this document as the `ChainIndexer` is already disabled by default**. + +## ChainIndexer Config +### Enablement + +The following must be enabled on an Lotus node before starting as they are disabled by default: + +```toml +[ChainIndexer] +# Enable the ChainIndexer. + EnableIndexer = true + +[Fevm] +# Enable the ETH RPC APIs. + EnableEthRPC = true + +[Events] +# Enable the Actor Events APIs. + EnableActorEventsAPI = true +``` + +You can learn more about these configuration options and other configuration options available for the `ChainIndexer` [here](https://github.com/filecoin-project/lotus/blob/master/documentation/en/default-lotus-config.toml). + + +### Garbage Collection + +The `ChainIndexer` includes a garbage collection (GC) mechanism to manage the amount of historical data retained. By default, GC is disabled to preserve all indexed data. + +To configure GC, use the `GCRetentionEpochs` parameter in the `ChainIndexer` section of your config. + +The ChainIndexer [periodically runs](https://github.com/filecoin-project/lotus/blob/master/chain/index/gc.go#L15) GC if `GCRetentionEpochs` is > 0 and removes indexed data for epochs older than `(current_head_height - GCRetentionEpochs)`. + +```toml +[ChainIndexer] + GCRetentionEpochs = X # Replace X with your desired value +``` + +- Setting `GCRetentionEpochs` to 0 (**default**) disables GC. +- Any positive value enables GC and determines the number of epochs of historical data to retain. + +#### Recommendations + +1. **Archival Nodes**: **Keep GC disabled** (`GCRetentionEpochs` = 0) to retain all indexed data. + +2. **Non-Archival Nodes**: Set `GCRetentionEpochs` to match the amount of chain state your node retains +(*Example:* if your node is configured to retain 2 days of Filecoin chain state with the Splitstore, set `GCRetentionEpochs` to `retentionDays * epochsPerDay = 2 * 2880 = 5760`). +**Warning:** Setting this value below the chain state retention period will degrade RPC performance and reliability because the Index will lack data for epochs still present in the chain state. + +### Removed Options + +**Note: The following config options no longer exist in Lotus and have been removed in favor of the ChainIndexer config options explained above. They can be removed when upgrading to Lotus v1.31.0.** + +```toml +[Fevm] +EthTxHashMappingLifetimeDays = 0 + +[Events] +DisableHistoricFilterAPI = false +DatabasePath = "" +``` + +## Upgrade Steps + +> **Note:** One can upgrade/downgrade between [pre-ChainIndexer](#previous-indexing-system) and [with-ChainIndexer](#chainindexer-indexing-system) Lotus versions without conflict because they persist state to different directories and don't rely on each other. No backup is necessary (but extra backups don't hurt). + +Upgrading to the new `ChainIndexer` involves these steps: + +1. **Stop the Lotus Node** + - Stop your Lotus node before starting the upgrade and migration process. +2. **Update Configuration** + - Modify your Lotus configuration to enable the `ChainIndexer` as described in the [`ChainIndexer Config` section above](#chainindexer-config). +3. **Restart Lotus Node** + - Restart your Lotus node with the new configuration. + - The `ChainIndexer` will begin indexing **real-time chain state changes** immediately in the `${LOTUS_PATH}/chainindex` directory. + - **However, it will not automatically index any historical chain state (i.e., any previously existing chain state prior to the upgrade). To perform backfilling, please see the [`Backfill` section below](#backfill).** + +> **Note:** It's recommended to keep the [pre-ChainIndexer](#previous-indexing-system) indexing database directory (`${LOTUS_PATH}/sqlite`) around until you've confirmed you don't need to [downgrade](#downgrade). After sustained successful operations after the upgrade, the [pre-ChainIndexer](#previous-indexing-system) indexing database directory can be removed to reclaim disk space. + +## Backfill +There is no automated migration from [pre-ChainIndexer indices](#previous-indexing-system) to the [ChainIndex](#chainindexer-indexing-system). Instead one needs to index historical chain state (i.e., backfill) using one of the following tools. + +> **Note:** The ChainIndex will consume ~10GB of storage per month of tipsets (e.g., ~86400 epochs). Ensure you have sufficient disk space before proceeding. + +### `ChainValidateIndex` JSON RPC API + +Please refer to the [Lotus API documentation](https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-v1-unstable-methods.md) for detailed documentation of the `ChainValidateIndex` JSON RPC API. + +The `ChainValidateIndex` JSON RPC API serves a dual purpose: it validates/diagnoses the integrity of the index at a specific epoch (i.e., it ensures consistency between indexed data and actual chain state), while also providing the option to backfill the `ChainIndexer` if it does not have data for the specified epoch. + +The `ChainValidateIndex` RPC API is available for use once the Lotus daemon has started with `ChainIndexer` [enabled](#enablement). + +Here are some examples of how to use the `ChainValidateIndex` JSON RPC API for validating/ backfilling the index: + +1) Validating the index for an epoch that is a NULL round: + + ```bash + curl -X POST -H "Content-Type: application/json" --data '{ + "jsonrpc": "2.0", + "method": "Filecoin.ChainValidateIndex", + "params": [1954383, false], + "id": 1 +}' http://localhost:1234/rpc/v1 | jq . +``` +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "TipSetKey": [], + "Height": 1954383, + "IndexedMessagesCount": 0, + "IndexedEventsCount": 0, + "Backfilled": false, + "IsNullRound": true + } +} +``` + +2) Validating the Index for an epoch for which the Indexer has missing data with backfilling disabled: + +```bash +curl -X POST -H "Content-Type: application/json" --data '{ + "jsonrpc": "2.0", + "method": "Filecoin.ChainValidateIndex", + "params": [1995103, false], + "id": 1 +}' http://localhost:1234/rpc/v1 | jq . +``` +```json +{ + "error": { + "code": 1, + "message": "missing tipset at height 1995103 in the chain index, set backfill flag to true to fix" + }, + "id": 1, + "jsonrpc": "2.0" +} +``` + +3) Validating the Index for an epoch for which the Indexer has missing data with backfilling enabled: + +```bash +curl -X POST -H "Content-Type: application/json" --data '{ + "jsonrpc": "2.0", + "method": "Filecoin.ChainValidateIndex", + "params": [1995103, true], + "id": 1 +}' http://localhost:1234/rpc/v1 | jq . +``` +```json +{ + "id": 1, + "jsonrpc": "2.0", + "result": { + "TipSetKey": [ + { + "/": "bafy2bzacebvzbpbdwxsclwyorlzclv6cbsvcbtq34sajow2sn7mnksy3wehew" + }, + { + "/": "bafy2bzacedgei4ve3spkfp3oou5oajwd5cogn7lljsuvoj644fgj3gv7luamu" + }, + { + "/": "bafy2bzacebbpcnjoi46obpaheylyxfy5y2lrtdsyglqw3hx2qg64quip5u76s" + } + ], + "Height": 1995103, + "IndexedMessagesCount": 0, + "IndexedEventsCount": 0, + "Backfilled": true, + "IsNullRound": false + } +} +``` + +### `lotus-shed chainindex validate-backfill` CLI tool +The `lotus-shed chainindex validate-backfill` command is a tool for validating and optionally backfilling the chain index over a range of epochs since calling the `ChainValidateIndex` API for a single epoch at a time can be cumbersome, especially when backfilling or validating the index over a range of historical epochs, such as during a migration. It wraps the `ChainValidateIndex` API to efficiently process multiple epochs. + +**Note: This command can only be run when the Lotus daemon is already running with the [`ChainIndexer` enabled](#enablement) as it depends on the `ChainValidateIndex` RPC API.** + +#### Usage: +``` +lotus-shed chainindex validate-backfill --from --to [--backfill] [--log-good] +``` + +The command validates the chain index entries for each epoch in the specified range, checking for missing or inconsistent entries(i.e. the indexed data does not match the actual chain state). If `--backfill` is enabled (which it is by default), it will attempt to backfill any missing entries using the `ChainValidateIndex` API. + +You can learn about how to use the tool with `lotus-shed chainindex validate-backfill -h`. + +Note: If you are using a non-standard Lotus repo directory then you can run the command with `lotus-shed -repo /path/to/lotus/repo chainindex validate-backfill ...`, or by setting the `LOTUS_PATH` environment variable. + +## Downgrade + +In case you need to downgrade to the [previous indexing system](#previous-indexing-system), follow these steps: + +1. Stop your Lotus node. +2. Download or build a Lotus binary for the rollback version which has the implementation of the old `EthTxHashLookup`, `MsgIndex`, and `EventIndex` indices. +4. Ensure that you've set the correct config for the existing `EthTxHashLookup`, `MsgIndex`, and `EventIndex` indices in the `config.toml` file. +5. Restart your Lotus node. +6. Backfill the `EthTxHashLookup`, `MsgIndex`, and `EventIndex` indices using the `lotus-shed index backfill-*` CLI tooling available in the [previous indexing system](#previous-indexing-system) for the epoch range in [epochs between the upgrade to `ChainIndexer` and the rollback of `ChainIndexer`]. + +## Terminology +### Previous Indexing System +* This corresponds to the indexing system used in Lotus versions before v1.31.0. +* It has been replaced by the [ChainIndex](#chainindexer-indexing-system). +* It was composed of three indexers using three separate databases: [`EthTxHashLookup`](https://github.com/filecoin-project/lotus/blob/master/chain/ethhashlookup/eth_transaction_hash_lookup.go), [`MsgIndex`](https://github.com/filecoin-project/lotus/blob/master/chain/index/msgindex.go), and [`EventIndex`](https://github.com/filecoin-project/lotus/blob/master/chain/events/filter/index.go). +* It persisted state to the [removed option](#removed-options) for `Events.DatabasePath`, which defaulted to `${LOTUS_PATH}/sqlite`. +* It had CLI backfill tooling: `lotus-shed index backfill-*` + +### ChainIndexer Indexing System +* This corresponds to the indexing system used in Lotus versions v1.31.0 onwards. +* It replaced the [previous indexing system](#previous-indexing-system). +* It is composed of a single indexer, [`ChainIndexer`](https://github.com/filecoin-project/lotus/blob/master/chain/index/indexer.go), using a [single database for transactions, messages, and events](https://github.com/filecoin-project/lotus/blob/master/chain/index/ddls.go). +* It persists state to `${LOTUS_HOME}/chainindex`. +* It has this CLI backfill tooling: [`lotus-shed chainindex validate-backfill`](#lotus-shed-chainindex-validate-backfill-cli-tool) diff --git a/chain/index/ddls.go b/chain/index/ddls.go index ebbeb398c87..84268c0b83c 100644 --- a/chain/index/ddls.go +++ b/chain/index/ddls.go @@ -60,24 +60,50 @@ var ddls = []string{ // the preparedStatements struct. func preparedStatementMapping(ps *preparedStatements) map[**sql.Stmt]string { return map[**sql.Stmt]string{ - &ps.getNonRevertedMsgInfoStmt: "SELECT tipset_key_cid, height FROM tipset_message WHERE message_cid = ? AND reverted = 0 LIMIT 1", - &ps.getMsgCidFromEthHashStmt: "SELECT message_cid FROM eth_tx_hash WHERE tx_hash = ? LIMIT 1", - &ps.insertEthTxHashStmt: "INSERT INTO eth_tx_hash (tx_hash, message_cid) VALUES (?, ?) ON CONFLICT (tx_hash) DO UPDATE SET inserted_at = CURRENT_TIMESTAMP", - &ps.insertTipsetMessageStmt: "INSERT INTO tipset_message (tipset_key_cid, height, reverted, message_cid, message_index) VALUES (?, ?, ?, ?, ?) ON CONFLICT (tipset_key_cid, message_cid) DO UPDATE SET reverted = 0", - &ps.hasTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ?)", - &ps.updateTipsetToNonRevertedStmt: "UPDATE tipset_message SET reverted = 0 WHERE tipset_key_cid = ?", - &ps.updateTipsetToRevertedStmt: "UPDATE tipset_message SET reverted = 1 WHERE tipset_key_cid = ?", - &ps.removeTipsetsBeforeHeightStmt: "DELETE FROM tipset_message WHERE height < ?", - &ps.removeEthHashesOlderThanStmt: "DELETE FROM eth_tx_hash WHERE inserted_at < datetime('now', ?)", - &ps.updateTipsetsToRevertedFromHeightStmt: "UPDATE tipset_message SET reverted = 1 WHERE height >= ?", - &ps.updateEventsToRevertedFromHeightStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE height >= ?)", - &ps.isTipsetMessageNonEmptyStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message LIMIT 1)", - &ps.getMinNonRevertedHeightStmt: "SELECT MIN(height) FROM tipset_message WHERE reverted = 0", - &ps.hasNonRevertedTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ? AND reverted = 0)", - &ps.updateEventsToRevertedStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", - &ps.updateEventsToNonRevertedStmt: "UPDATE event SET reverted = 0 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", - &ps.getMsgIdForMsgCidAndTipsetStmt: "SELECT message_id FROM tipset_message WHERE tipset_key_cid = ? AND message_cid = ? AND reverted = 0 LIMIT 1", - &ps.insertEventStmt: "INSERT INTO event (message_id, event_index, emitter_id, emitter_addr, reverted) VALUES (?, ?, ?, ?, ?)", - &ps.insertEventEntryStmt: "INSERT INTO event_entry (event_id, indexed, flags, key, codec, value) VALUES (?, ?, ?, ?, ?, ?)", + &ps.getNonRevertedMsgInfoStmt: "SELECT tipset_key_cid, height FROM tipset_message WHERE message_cid = ? AND reverted = 0 LIMIT 1", + &ps.getMsgCidFromEthHashStmt: "SELECT message_cid FROM eth_tx_hash WHERE tx_hash = ? LIMIT 1", + &ps.insertEthTxHashStmt: "INSERT INTO eth_tx_hash (tx_hash, message_cid) VALUES (?, ?) ON CONFLICT (tx_hash) DO UPDATE SET inserted_at = CURRENT_TIMESTAMP", + &ps.insertTipsetMessageStmt: "INSERT INTO tipset_message (tipset_key_cid, height, reverted, message_cid, message_index) VALUES (?, ?, ?, ?, ?) ON CONFLICT (tipset_key_cid, message_cid) DO UPDATE SET reverted = 0", + &ps.hasTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.updateTipsetToNonRevertedStmt: "UPDATE tipset_message SET reverted = 0 WHERE tipset_key_cid = ?", + &ps.updateTipsetToRevertedStmt: "UPDATE tipset_message SET reverted = 1 WHERE tipset_key_cid = ?", + &ps.removeTipsetsBeforeHeightStmt: "DELETE FROM tipset_message WHERE height < ?", + &ps.removeEthHashesOlderThanStmt: "DELETE FROM eth_tx_hash WHERE inserted_at < datetime('now', ?)", + &ps.updateTipsetsToRevertedFromHeightStmt: "UPDATE tipset_message SET reverted = 1 WHERE height >= ?", + &ps.updateEventsToRevertedFromHeightStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE height >= ?)", + &ps.isIndexEmptyStmt: "SELECT NOT EXISTS(SELECT 1 FROM tipset_message LIMIT 1)", + &ps.getMinNonRevertedHeightStmt: "SELECT MIN(height) FROM tipset_message WHERE reverted = 0", + &ps.hasNonRevertedTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ? AND reverted = 0)", + &ps.updateEventsToRevertedStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.updateEventsToNonRevertedStmt: "UPDATE event SET reverted = 0 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.getMsgIdForMsgCidAndTipsetStmt: "SELECT message_id FROM tipset_message WHERE tipset_key_cid = ? AND message_cid = ? AND reverted = 0", + &ps.insertEventStmt: "INSERT INTO event (message_id, event_index, emitter_addr, reverted) VALUES (?, ?, ?, ?) ON CONFLICT (message_id, event_index) DO UPDATE SET reverted = 0", + &ps.insertEventEntryStmt: "INSERT INTO event_entry (event_id, indexed, flags, key, codec, value) VALUES (?, ?, ?, ?, ?, ?)", + &ps.hasNullRoundAtHeightStmt: "SELECT NOT EXISTS(SELECT 1 FROM tipset_message WHERE height = ?)", + &ps.getNonRevertedTipsetAtHeightStmt: "SELECT tipset_key_cid FROM tipset_message WHERE height = ? AND reverted = 0 LIMIT 1", + &ps.countTipsetsAtHeightStmt: "SELECT COUNT(CASE WHEN reverted = 1 THEN 1 END) AS reverted_count, COUNT(CASE WHEN reverted = 0 THEN 1 END) AS non_reverted_count FROM (SELECT tipset_key_cid, MAX(reverted) AS reverted FROM tipset_message WHERE height = ? GROUP BY tipset_key_cid) AS unique_tipsets", + &ps.getNonRevertedTipsetMessageCountStmt: "SELECT COUNT(*) FROM tipset_message WHERE tipset_key_cid = ? AND reverted = 0 AND message_cid IS NOT NULL", + &ps.getNonRevertedTipsetEventCountStmt: "SELECT COUNT(*) FROM event WHERE reverted = 0 AND message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ? AND reverted = 0)", + &ps.hasRevertedEventsInTipsetStmt: "SELECT EXISTS(SELECT 1 FROM event WHERE reverted = 1 AND message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?))", + &ps.getNonRevertedTipsetEventEntriesCountStmt: "SELECT COUNT(ee.event_id) AS entry_count FROM event_entry ee JOIN event e ON ee.event_id = e.event_id JOIN tipset_message tm ON e.message_id = tm.message_id WHERE tm.tipset_key_cid = ? AND tm.reverted = 0", + &ps.removeRevertedTipsetsBeforeHeightStmt: "DELETE FROM tipset_message WHERE reverted = 1 AND height < ?", + &ps.getNonRevertedMsgInfoStmt: "SELECT tipset_key_cid, height FROM tipset_message WHERE message_cid = ? AND reverted = 0 LIMIT 1", + &ps.getMsgCidFromEthHashStmt: "SELECT message_cid FROM eth_tx_hash WHERE tx_hash = ? LIMIT 1", + &ps.insertEthTxHashStmt: "INSERT INTO eth_tx_hash (tx_hash, message_cid) VALUES (?, ?) ON CONFLICT (tx_hash) DO UPDATE SET inserted_at = CURRENT_TIMESTAMP", + &ps.insertTipsetMessageStmt: "INSERT INTO tipset_message (tipset_key_cid, height, reverted, message_cid, message_index) VALUES (?, ?, ?, ?, ?) ON CONFLICT (tipset_key_cid, message_cid) DO UPDATE SET reverted = 0", + &ps.hasTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.updateTipsetToNonRevertedStmt: "UPDATE tipset_message SET reverted = 0 WHERE tipset_key_cid = ?", + &ps.updateTipsetToRevertedStmt: "UPDATE tipset_message SET reverted = 1 WHERE tipset_key_cid = ?", + &ps.removeTipsetsBeforeHeightStmt: "DELETE FROM tipset_message WHERE height < ?", + &ps.removeEthHashesOlderThanStmt: "DELETE FROM eth_tx_hash WHERE inserted_at < datetime('now', ?)", + &ps.updateTipsetsToRevertedFromHeightStmt: "UPDATE tipset_message SET reverted = 1 WHERE height >= ?", + &ps.updateEventsToRevertedFromHeightStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE height >= ?)", + &ps.getMinNonRevertedHeightStmt: "SELECT MIN(height) FROM tipset_message WHERE reverted = 0", + &ps.hasNonRevertedTipsetStmt: "SELECT EXISTS(SELECT 1 FROM tipset_message WHERE tipset_key_cid = ? AND reverted = 0)", + &ps.updateEventsToRevertedStmt: "UPDATE event SET reverted = 1 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.updateEventsToNonRevertedStmt: "UPDATE event SET reverted = 0 WHERE message_id IN (SELECT message_id FROM tipset_message WHERE tipset_key_cid = ?)", + &ps.getMsgIdForMsgCidAndTipsetStmt: "SELECT message_id FROM tipset_message WHERE tipset_key_cid = ? AND message_cid = ? AND reverted = 0 LIMIT 1", + &ps.insertEventStmt: "INSERT INTO event (message_id, event_index, emitter_id, emitter_addr, reverted) VALUES (?, ?, ?, ?, ?)", + &ps.insertEventEntryStmt: "INSERT INTO event_entry (event_id, indexed, flags, key, codec, value) VALUES (?, ?, ?, ?, ?, ?)", } } diff --git a/chain/index/ddls_test.go b/chain/index/ddls_test.go new file mode 100644 index 00000000000..baae0c88062 --- /dev/null +++ b/chain/index/ddls_test.go @@ -0,0 +1,743 @@ +package index + +import ( + "database/sql" + "testing" + + "github.com/stretchr/testify/require" +) + +const ( + tipsetKeyCid1 = "test_tipset_key" + tipsetKeyCid2 = "test_tipset_key_2" + messageCid1 = "test_message_cid" + messageCid2 = "test_message_cid_2" + emitterAddr1 = "test_emitter_addr" +) + +func TestHasRevertedEventsInTipsetStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // running on empty DB should return false + verifyHasRevertedEventsInTipsetStmt(t, s, []byte(tipsetKeyCid1), false) + + // Insert tipset with a reverted event + ts := tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + } + messageID := insertTipsetMessage(t, s, ts) + + // this event will be un-reverted later + insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 1, + emitterAddr: []byte(emitterAddr1), + reverted: true, + }) + + // this event should not be un-reverted + ts = tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid2), + height: 1, + reverted: false, + messageCid: []byte(messageCid2), + messageIndex: 0, + } + messageID2 := insertTipsetMessage(t, s, ts) + insertEvent(t, s, event{ + messageID: messageID2, + eventIndex: 0, + emitterId: 2, + emitterAddr: []byte(emitterAddr1), + reverted: true, + }) + + // Verify `hasRevertedEventsInTipset` returns true + verifyHasRevertedEventsInTipsetStmt(t, s, []byte(tipsetKeyCid1), true) + verifyHasRevertedEventsInTipsetStmt(t, s, []byte(tipsetKeyCid2), true) + + // change event to non-reverted + updateEventsToNonReverted(t, s, []byte(tipsetKeyCid1)) + + // Verify `hasRevertedEventsInTipset` returns false + verifyHasRevertedEventsInTipsetStmt(t, s, []byte(tipsetKeyCid1), false) + verifyHasRevertedEventsInTipsetStmt(t, s, []byte(tipsetKeyCid2), true) +} + +func TestGetNonRevertedTipsetCountStmts(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // running on empty DB should return 0 + verifyNonRevertedEventEntriesCount(t, s, []byte(tipsetKeyCid1), 0) + verifyNonRevertedEventCount(t, s, []byte(tipsetKeyCid1), 0) + verifyNonRevertedMessageCount(t, s, []byte(tipsetKeyCid1), 0) + + // Insert non-reverted tipset + messageID := insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + }) + + // Insert event + eventID1 := insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 1, + emitterAddr: []byte(emitterAddr1), + reverted: false, + }) + eventID2 := insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 1, + emitterId: 2, + emitterAddr: []byte(emitterAddr1), + reverted: false, + }) + + // Insert event entry + insertEventEntry(t, s, eventEntry{ + eventID: eventID1, + indexed: true, + flags: []byte("test_flags"), + key: "test_key", + codec: 1, + value: []byte("test_value"), + }) + insertEventEntry(t, s, eventEntry{ + eventID: eventID2, + indexed: true, + flags: []byte("test_flags2"), + key: "test_key2", + codec: 2, + value: []byte("test_value2"), + }) + + // verify 2 event entries + verifyNonRevertedEventEntriesCount(t, s, []byte(tipsetKeyCid1), 2) + + // Verify event count + verifyNonRevertedEventCount(t, s, []byte(tipsetKeyCid1), 2) + + // verify message count is 1 + verifyNonRevertedMessageCount(t, s, []byte(tipsetKeyCid1), 1) + + // mark tipset as reverted + revertTipset(t, s, []byte(tipsetKeyCid1)) + + // Verify `getNonRevertedTipsetEventEntriesCountStmt` returns 0 + verifyNonRevertedEventEntriesCount(t, s, []byte(tipsetKeyCid1), 0) + + // verify event count is 0 + verifyNonRevertedEventCount(t, s, []byte(tipsetKeyCid1), 0) + + // verify message count is 0 + verifyNonRevertedMessageCount(t, s, []byte(tipsetKeyCid1), 0) +} + +func TestUpdateTipsetToNonRevertedStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // insert a reverted tipset + ts := tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: true, + messageCid: []byte(messageCid1), + messageIndex: 0, + } + + // Insert tipset + messageId := insertTipsetMessage(t, s, ts) + + res, err := s.stmts.updateTipsetToNonRevertedStmt.Exec([]byte(tipsetKeyCid1)) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + // verify the tipset is not reverted + ts.reverted = false + verifyTipsetMessage(t, s, messageId, ts) +} + +func TestHasNullRoundAtHeightStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // running on empty DB should return true + verifyHasNullRoundAtHeightStmt(t, s, 1, true) + verifyHasNullRoundAtHeightStmt(t, s, 0, true) + + // insert tipset + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + }) + + // verify not a null round + verifyHasNullRoundAtHeightStmt(t, s, 1, false) +} + +func TestHasTipsetStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // running on empty DB should return false + verifyHasTipsetStmt(t, s, []byte(tipsetKeyCid1), false) + + // insert tipset + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + }) + + // verify tipset exists + verifyHasTipsetStmt(t, s, []byte(tipsetKeyCid1), true) + + // verify non-existent tipset + verifyHasTipsetStmt(t, s, []byte("non_existent_tipset_key"), false) +} + +func TestUpdateEventsToRevertedStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Insert a non-reverted tipset + messageID := insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte(tipsetKeyCid1), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + }) + + // Insert non-reverted events + insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 1, + emitterAddr: []byte(emitterAddr1), + reverted: false, + }) + insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 1, + emitterId: 2, + emitterAddr: []byte(emitterAddr1), + reverted: false, + }) + + // Verify events are not reverted + var count int + err = s.db.QueryRow("SELECT COUNT(*) FROM event WHERE reverted = 0 AND message_id = ?", messageID).Scan(&count) + require.NoError(t, err) + require.Equal(t, 2, count) + + // Execute updateEventsToRevertedStmt + _, err = s.stmts.updateEventsToRevertedStmt.Exec([]byte(tipsetKeyCid1)) + require.NoError(t, err) + + // Verify events are now reverted + err = s.db.QueryRow("SELECT COUNT(*) FROM event WHERE reverted = 1 AND message_id = ?", messageID).Scan(&count) + require.NoError(t, err) + require.Equal(t, 2, count) + + // Verify no non-reverted events remain + err = s.db.QueryRow("SELECT COUNT(*) FROM event WHERE reverted = 0 AND message_id = ?", messageID).Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count) +} + +func TestCountTipsetsAtHeightStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Test empty DB + verifyCountTipsetsAtHeightStmt(t, s, 1, 0, 0) + + // Test 0,1 case + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_1"), + height: 1, + reverted: false, + messageCid: []byte("test_message_cid_1"), + messageIndex: 0, + }) + verifyCountTipsetsAtHeightStmt(t, s, 1, 0, 1) + + // Test 0,2 case + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_2"), + height: 1, + reverted: false, + messageCid: []byte("test_message_cid_2"), + messageIndex: 0, + }) + verifyCountTipsetsAtHeightStmt(t, s, 1, 0, 2) + + // Test 1,2 case + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_3"), + height: 1, + reverted: true, + messageCid: []byte("test_message_cid_3"), + messageIndex: 0, + }) + verifyCountTipsetsAtHeightStmt(t, s, 1, 1, 2) + + // Test 2,2 case + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_4"), + height: 1, + reverted: true, + messageCid: []byte("test_message_cid_4"), + messageIndex: 0, + }) + verifyCountTipsetsAtHeightStmt(t, s, 1, 2, 2) +} + +func TestNonRevertedTipsetAtHeightStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Test empty DB + var et []byte + err = s.stmts.getNonRevertedTipsetAtHeightStmt.QueryRow(10).Scan(&et) + require.Equal(t, sql.ErrNoRows, err) + + // Insert non-reverted tipset + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_1"), + height: 10, + reverted: false, + messageCid: []byte("test_message_cid_1"), + messageIndex: 0, + }) + + // Insert reverted tipset at same height + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_2"), + height: 10, + reverted: true, + messageCid: []byte("test_message_cid_2"), + messageIndex: 0, + }) + + // Verify getNonRevertedTipsetAtHeightStmt returns the non-reverted tipset + var tipsetKeyCid []byte + err = s.stmts.getNonRevertedTipsetAtHeightStmt.QueryRow(10).Scan(&tipsetKeyCid) + require.NoError(t, err) + require.Equal(t, []byte("test_tipset_key_1"), tipsetKeyCid) + + // Insert another non-reverted tipset at a different height + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_3"), + height: 20, + reverted: false, + messageCid: []byte("test_message_cid_3"), + messageIndex: 0, + }) + + // Verify getNonRevertedTipsetAtHeightStmt returns the correct tipset for the new height + err = s.stmts.getNonRevertedTipsetAtHeightStmt.QueryRow(20).Scan(&tipsetKeyCid) + require.NoError(t, err) + require.Equal(t, []byte("test_tipset_key_3"), tipsetKeyCid) + + // Test with a height that has no tipset + err = s.stmts.getNonRevertedTipsetAtHeightStmt.QueryRow(30).Scan(&tipsetKeyCid) + require.Equal(t, sql.ErrNoRows, err) + + // Revert all tipsets at height 10 + _, err = s.db.Exec("UPDATE tipset_message SET reverted = 1 WHERE height = 10") + require.NoError(t, err) + + // Verify getNonRevertedTipsetAtHeightStmt returns no rows for the reverted height + err = s.stmts.getNonRevertedTipsetAtHeightStmt.QueryRow(10).Scan(&tipsetKeyCid) + require.Equal(t, sql.ErrNoRows, err) +} + +func TestMinNonRevertedHeightStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Test empty DB + var minHeight sql.NullInt64 + err = s.stmts.getMinNonRevertedHeightStmt.QueryRow().Scan(&minHeight) + require.NoError(t, err) + require.False(t, minHeight.Valid) + + // Insert non-reverted tipsets + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_1"), + height: 10, + reverted: false, + messageCid: []byte("test_message_cid_1"), + messageIndex: 0, + }) + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_2"), + height: 20, + reverted: false, + messageCid: []byte("test_message_cid_2"), + messageIndex: 0, + }) + + // Verify minimum non-reverted height + verifyMinNonRevertedHeightStmt(t, s, 10) + + // Insert reverted tipset with lower height + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key_4"), + height: 5, + reverted: true, + messageCid: []byte("test_message_cid_4"), + messageIndex: 0, + }) + + // Verify minimum non-reverted height hasn't changed + verifyMinNonRevertedHeightStmt(t, s, 10) + + // Revert all tipsets + _, err = s.db.Exec("UPDATE tipset_message SET reverted = 1") + require.NoError(t, err) + + // Verify no minimum non-reverted height + err = s.stmts.getMinNonRevertedHeightStmt.QueryRow().Scan(&minHeight) + require.NoError(t, err) + require.False(t, minHeight.Valid) +} + +func verifyMinNonRevertedHeightStmt(t *testing.T, s *SqliteIndexer, expectedMinHeight int64) { + var minHeight sql.NullInt64 + err := s.stmts.getMinNonRevertedHeightStmt.QueryRow().Scan(&minHeight) + require.NoError(t, err) + require.True(t, minHeight.Valid) + require.Equal(t, expectedMinHeight, minHeight.Int64) +} + +func TestGetMsgIdForMsgCidAndTipsetStmt(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Insert a non-reverted tipset + tipsetKeyCid := []byte(tipsetKeyCid1) + messageCid := []byte(messageCid1) + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: tipsetKeyCid, + height: 1, + reverted: false, + messageCid: messageCid, + messageIndex: 0, + }) + + // Verify getMsgIdForMsgCidAndTipset returns the correct message ID + var messageID int64 + err = s.stmts.getMsgIdForMsgCidAndTipsetStmt.QueryRow(tipsetKeyCid, messageCid).Scan(&messageID) + require.NoError(t, err) + require.Equal(t, int64(1), messageID) + + // Test with non-existent message CID + nonExistentMessageCid := []byte("non_existent_message_cid") + err = s.stmts.getMsgIdForMsgCidAndTipsetStmt.QueryRow(tipsetKeyCid, nonExistentMessageCid).Scan(&messageID) + require.Equal(t, sql.ErrNoRows, err) + + // Test with non-existent tipset key + nonExistentTipsetKeyCid := []byte("non_existent_tipset_key") + err = s.stmts.getMsgIdForMsgCidAndTipsetStmt.QueryRow(nonExistentTipsetKeyCid, messageCid).Scan(&messageID) + require.Equal(t, sql.ErrNoRows, err) + + // Insert a reverted tipset + revertedTipsetKeyCid := []byte("reverted_tipset_key") + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: revertedTipsetKeyCid, + height: 2, + reverted: true, + messageCid: messageCid, + messageIndex: 0, + }) + + // Verify getMsgIdForMsgCidAndTipset doesn't return the message ID for a reverted tipset + err = s.stmts.getMsgIdForMsgCidAndTipsetStmt.QueryRow(revertedTipsetKeyCid, messageCid).Scan(&messageID) + require.Equal(t, sql.ErrNoRows, err) +} + +func TestForeignKeyCascadeDelete(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + // Insert a tipset + messageID := insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key"), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + }) + + // Insert an event for the tipset + eventID := insertEvent(t, s, event{ + messageID: messageID, + eventIndex: 0, + emitterId: 2, + emitterAddr: []byte("test_emitter_addr"), + reverted: false, + }) + + // Insert an event entry for the event + insertEventEntry(t, s, eventEntry{ + eventID: eventID, + indexed: true, + flags: []byte("test_flags"), + key: "test_key", + codec: 1, + value: []byte("test_value"), + }) + + // Delete the tipset + res, err := s.db.Exec("DELETE FROM tipset_message WHERE tipset_key_cid = ?", []byte("test_tipset_key")) + require.NoError(t, err) + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + // verify event is deleted + verifyEventAbsent(t, s, eventID) + verifyEventEntryAbsent(t, s, eventID) +} + +func TestInsertTipsetMessage(t *testing.T) { + s, err := NewSqliteIndexer(":memory:", nil, 0, false, 0) + require.NoError(t, err) + + ts := tipsetMessage{ + tipsetKeyCid: []byte("test_tipset_key"), + height: 1, + reverted: false, + messageCid: []byte(messageCid1), + messageIndex: 0, + } + + // Insert a tipset + messageID := insertTipsetMessage(t, s, ts) + + // revert the tipset + revertTipset(t, s, []byte("test_tipset_key")) + ts.reverted = true + verifyTipsetMessage(t, s, messageID, ts) + + // inserting with the same (tipset, message) should overwrite the reverted flag + res, err := s.stmts.insertTipsetMessageStmt.Exec(ts.tipsetKeyCid, ts.height, true, ts.messageCid, ts.messageIndex) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + ts.reverted = false + verifyTipsetMessage(t, s, messageID, ts) +} + +type tipsetMessage struct { + tipsetKeyCid []byte + height uint64 + reverted bool + messageCid []byte + messageIndex int64 +} + +type event struct { + eventIndex uint64 + emitterId uint64 + emitterAddr []byte + reverted bool + messageID int64 +} + +type eventEntry struct { + eventID int64 + indexed bool + flags []byte + key string + codec int + value []byte +} + +func updateEventsToNonReverted(t *testing.T, s *SqliteIndexer, tsKeyCid []byte) { + res, err := s.stmts.updateEventsToNonRevertedStmt.Exec(tsKeyCid) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + // read all events for this tipset and verify they are not reverted using a COUNT query + var count int + err = s.db.QueryRow("SELECT COUNT(*) FROM event e JOIN tipset_message tm ON e.message_id = tm.message_id WHERE tm.tipset_key_cid = ? AND e.reverted = 1", tsKeyCid).Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count, "Expected no reverted events for this tipset") +} + +func revertTipset(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte) { + res, err := s.stmts.updateTipsetToRevertedStmt.Exec(tipsetKeyCid) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + var reverted bool + err = s.db.QueryRow("SELECT reverted FROM tipset_message WHERE tipset_key_cid = ?", tipsetKeyCid).Scan(&reverted) + require.NoError(t, err) + require.True(t, reverted) +} + +func verifyTipsetMessage(t *testing.T, s *SqliteIndexer, messageID int64, expectedTipsetMessage tipsetMessage) { + var tipsetKeyCid []byte + var height uint64 + var reverted bool + var messageCid []byte + var messageIndex int64 + err := s.db.QueryRow("SELECT tipset_key_cid, height, reverted, message_cid, message_index FROM tipset_message WHERE message_id = ?", messageID).Scan(&tipsetKeyCid, &height, &reverted, &messageCid, &messageIndex) + require.NoError(t, err) + require.Equal(t, expectedTipsetMessage.tipsetKeyCid, tipsetKeyCid) + require.Equal(t, expectedTipsetMessage.height, height) + require.Equal(t, expectedTipsetMessage.reverted, reverted) + require.Equal(t, expectedTipsetMessage.messageCid, messageCid) + require.Equal(t, expectedTipsetMessage.messageIndex, messageIndex) +} + +func verifyEventEntryAbsent(t *testing.T, s *SqliteIndexer, eventID int64) { + err := s.db.QueryRow("SELECT event_id FROM event_entry WHERE event_id = ?", eventID).Scan(&eventID) + require.Equal(t, sql.ErrNoRows, err) +} + +func verifyEventAbsent(t *testing.T, s *SqliteIndexer, eventID int64) { + var eventIndex uint64 + err := s.db.QueryRow("SELECT event_index FROM event WHERE event_id = ?", eventID).Scan(&eventIndex) + require.Equal(t, sql.ErrNoRows, err) +} + +func verifyEvent(t *testing.T, s *SqliteIndexer, eventID int64, expectedEvent event) { + var eventIndex uint64 + var emitterAddr []byte + var reverted bool + var messageID int64 + err := s.db.QueryRow("SELECT event_index, emitter_addr, reverted, message_id FROM event WHERE event_id = ?", eventID).Scan(&eventIndex, &emitterAddr, &reverted, &messageID) + require.NoError(t, err) + require.Equal(t, expectedEvent.eventIndex, eventIndex) + require.Equal(t, expectedEvent.emitterAddr, emitterAddr) + require.Equal(t, expectedEvent.reverted, reverted) + require.Equal(t, expectedEvent.messageID, messageID) +} + +func verifyCountTipsetsAtHeightStmt(t *testing.T, s *SqliteIndexer, height uint64, expectedRevertedCount, expectedNonRevertedCount int) { + var revertedCount, nonRevertedCount int + err := s.stmts.countTipsetsAtHeightStmt.QueryRow(height).Scan(&revertedCount, &nonRevertedCount) + require.NoError(t, err) + require.Equal(t, expectedRevertedCount, revertedCount) + require.Equal(t, expectedNonRevertedCount, nonRevertedCount) +} + +func verifyHasTipsetStmt(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte, expectedHas bool) { + var has bool + err := s.stmts.hasTipsetStmt.QueryRow(tipsetKeyCid).Scan(&has) + require.NoError(t, err) + require.Equal(t, expectedHas, has) +} + +func verifyHasRevertedEventsInTipsetStmt(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte, expectedHas bool) { + var hasRevertedEventsInTipset bool + err := s.stmts.hasRevertedEventsInTipsetStmt.QueryRow(tipsetKeyCid).Scan(&hasRevertedEventsInTipset) + require.NoError(t, err) + require.Equal(t, expectedHas, hasRevertedEventsInTipset) +} + +func verifyHasNullRoundAtHeightStmt(t *testing.T, s *SqliteIndexer, height uint64, expectedHasNullRound bool) { + var hasNullRound bool + err := s.stmts.hasNullRoundAtHeightStmt.QueryRow(height).Scan(&hasNullRound) + require.NoError(t, err) + require.Equal(t, expectedHasNullRound, hasNullRound) +} + +func verifyNonRevertedMessageCount(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte, expectedCount int) { + var count int + err := s.stmts.getNonRevertedTipsetMessageCountStmt.QueryRow(tipsetKeyCid).Scan(&count) + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func verifyNonRevertedEventCount(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte, expectedCount int) { + var count int + err := s.stmts.getNonRevertedTipsetEventCountStmt.QueryRow(tipsetKeyCid).Scan(&count) + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func verifyNonRevertedEventEntriesCount(t *testing.T, s *SqliteIndexer, tipsetKeyCid []byte, expectedCount int) { + var count int + err := s.stmts.getNonRevertedTipsetEventEntriesCountStmt.QueryRow(tipsetKeyCid).Scan(&count) + require.NoError(t, err) + require.Equal(t, expectedCount, count) +} + +func insertTipsetMessage(t *testing.T, s *SqliteIndexer, ts tipsetMessage) int64 { + res, err := s.stmts.insertTipsetMessageStmt.Exec(ts.tipsetKeyCid, ts.height, ts.reverted, ts.messageCid, ts.messageIndex) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + messageID, err := res.LastInsertId() + require.NoError(t, err) + require.NotEqual(t, int64(0), messageID) + + // read back the message to verify it was inserted correctly + verifyTipsetMessage(t, s, messageID, ts) + + return messageID +} + +func insertEvent(t *testing.T, s *SqliteIndexer, e event) int64 { + res, err := s.stmts.insertEventStmt.Exec(e.messageID, e.eventIndex, e.emitterId, e.emitterAddr, e.reverted) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) + + eventID, err := res.LastInsertId() + require.NoError(t, err) + require.NotEqual(t, int64(0), eventID) + + verifyEvent(t, s, eventID, e) + + return eventID +} + +func insertEventEntry(t *testing.T, s *SqliteIndexer, ee eventEntry) { + res, err := s.stmts.insertEventEntryStmt.Exec(ee.eventID, ee.indexed, ee.flags, ee.key, ee.codec, ee.value) + require.NoError(t, err) + + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) +} diff --git a/chain/index/events.go b/chain/index/events.go index bdc5a6f1672..94abb45cbb1 100644 --- a/chain/index/events.go +++ b/chain/index/events.go @@ -33,6 +33,9 @@ func (si *SqliteIndexer) indexEvents(ctx context.Context, tx *sql.Tx, msgTs *typ if si.actorToDelegatedAddresFunc == nil { return xerrors.Errorf("indexer can not index events without an address resolver") } + if si.executedMessagesLoaderFunc == nil { + return xerrors.Errorf("indexer can not index events without an event loader") + } // check if we have an event indexed for any message in the `msgTs` tipset -> if so, there's nothig to do here // this makes event inserts idempotent @@ -59,7 +62,7 @@ func (si *SqliteIndexer) indexEvents(ctx context.Context, tx *sql.Tx, msgTs *typ return nil } - ems, err := si.loadExecutedMessages(ctx, msgTs, executionTs) + ems, err := si.executedMessagesLoaderFunc(ctx, si.cs, msgTs, executionTs) if err != nil { return xerrors.Errorf("failed to load executed messages: %w", err) } @@ -129,13 +132,20 @@ func (si *SqliteIndexer) indexEvents(ctx context.Context, tx *sql.Tx, msgTs *typ return nil } -func (si *SqliteIndexer) loadExecutedMessages(ctx context.Context, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { - msgs, err := si.cs.MessagesForTipset(ctx, msgTs) +func MakeLoadExecutedMessages(recomputeTipSetStateFunc recomputeTipSetStateFunc) func(ctx context.Context, + cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return loadExecutedMessages(ctx, cs, recomputeTipSetStateFunc, msgTs, rctTs) + } +} + +func loadExecutedMessages(ctx context.Context, cs ChainStore, recomputeTipSetStateFunc recomputeTipSetStateFunc, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + msgs, err := cs.MessagesForTipset(ctx, msgTs) if err != nil { return nil, xerrors.Errorf("failed to get messages for tipset: %w", err) } - st := si.cs.ActorStore(ctx) + st := cs.ActorStore(ctx) receiptsArr, err := blockadt.AsArray(st, rctTs.Blocks()[0].ParentMessageReceipts) if err != nil { @@ -166,12 +176,12 @@ func (si *SqliteIndexer) loadExecutedMessages(ctx context.Context, msgTs, rctTs eventsArr, err := amt4.LoadAMT(ctx, st, *rct.EventsRoot, amt4.UseTreeBitWidth(types.EventAMTBitwidth)) if err != nil { - if si.recomputeTipSetStateFunc == nil { + if recomputeTipSetStateFunc == nil { return nil, xerrors.Errorf("failed to load events amt for message %s: %w", ems[i].msg.Cid(), err) } log.Warnf("failed to load events amt for message %s: %s; recomputing tipset state to regenerate events", ems[i].msg.Cid(), err) - if err := si.recomputeTipSetStateFunc(ctx, msgTs); err != nil { + if err := recomputeTipSetStateFunc(ctx, msgTs); err != nil { return nil, xerrors.Errorf("failed to recompute missing events; failed to recompute tipset state: %w", err) } @@ -253,6 +263,9 @@ func (si *SqliteIndexer) getTipsetKeyCidByHeight(ctx context.Context, height abi if err != nil { return nil, xerrors.Errorf("failed to get tipset by height: %w", err) } + if ts == nil { + return nil, xerrors.Errorf("tipset is nil for height: %d", height) + } if ts.Height() != height { // this means that this is a null round diff --git a/chain/index/events_test.go b/chain/index/events_test.go new file mode 100644 index 00000000000..93bca3882df --- /dev/null +++ b/chain/index/events_test.go @@ -0,0 +1,412 @@ +package index + +import ( + "context" + "database/sql" + "errors" + pseudo "math/rand" + "sort" + "testing" + "time" + + "github.com/ipfs/go-cid" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestGetEventsForFilterNoEvents(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + + headHeight := abi.ChainEpoch(60) + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + + // Create a fake tipset at height 1 + fakeTipSet1 := fakeTipSet(t, rng, 1, nil) + + // Set the dummy chainstore to return this tipset for height 1 + cs.SetTipsetByHeightAndKey(1, fakeTipSet1.Key(), fakeTipSet1) // empty DB + + // tipset is not indexed + f := &EventFilter{ + MinHeight: 1, + MaxHeight: 1, + } + ces, err := si.GetEventsForFilter(ctx, f, false) + require.True(t, errors.Is(err, ErrNotFound)) + require.Equal(t, 0, len(ces)) + + tsCid, err := fakeTipSet1.Key().Cid() + require.NoError(t, err) + f = &EventFilter{ + TipsetCid: tsCid, + } + + ces, err = si.GetEventsForFilter(ctx, f, false) + require.True(t, errors.Is(err, ErrNotFound)) + require.Equal(t, 0, len(ces)) + + // tipset is indexed but has no events + err = withTx(ctx, si.db, func(tx *sql.Tx) error { + return si.indexTipset(ctx, tx, fakeTipSet1) + }) + require.NoError(t, err) + + ces, err = si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 0, len(ces)) + + f = &EventFilter{ + TipsetCid: tsCid, + } + ces, err = si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 0, len(ces)) + + // search for a range that is absent + f = &EventFilter{ + MinHeight: 100, + MaxHeight: 200, + } + ces, err = si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 0, len(ces)) +} + +func TestGetEventsForFilterWithEvents(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(60) + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + + ev1 := fakeEvent( + abi.ActorID(1), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + ev2 := fakeEvent( + abi.ActorID(2), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr2")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + events := []types.Event{*ev1, *ev2} + + fm := fakeMessage(address.TestAddress, address.TestAddress) + em1 := executedMessage{ + msg: fm, + evs: events, + } + + si.SetActorToDelegatedAddresFunc(func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + idAddr, err := address.NewIDAddress(uint64(emitter)) + if err != nil { + return address.Undef, false + } + + return idAddr, true + }) + + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{em1}, nil + }) + + // Create a fake tipset at height 1 + fakeTipSet1 := fakeTipSet(t, rng, 1, nil) + fakeTipSet2 := fakeTipSet(t, rng, 2, nil) + + // Set the dummy chainstore to return this tipset for height 1 + cs.SetTipsetByHeightAndKey(1, fakeTipSet1.Key(), fakeTipSet1) // empty DB + cs.SetTipsetByHeightAndKey(2, fakeTipSet2.Key(), fakeTipSet2) // empty DB + + cs.SetMessagesForTipset(fakeTipSet1, []types.ChainMsg{fm}) + + // index tipset and events + require.NoError(t, si.Apply(ctx, fakeTipSet1, fakeTipSet2)) + + // fetch it based on height -> works + f := &EventFilter{ + MinHeight: 1, + MaxHeight: 1, + } + ces, err := si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 2, len(ces)) + + // fetch it based on cid -> works + tsCid1, err := fakeTipSet1.Key().Cid() + require.NoError(t, err) + + tsCid2, err := fakeTipSet2.Key().Cid() + require.NoError(t, err) + + f = &EventFilter{ + TipsetCid: tsCid1, + } + ces, err = si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 2, len(ces)) + + require.Equal(t, ev1.Entries, ces[0].Entries) + require.Equal(t, ev2.Entries, ces[1].Entries) + + // mark fakeTipSet2 as reverted so events for fakeTipSet1 are reverted + require.NoError(t, si.Revert(ctx, fakeTipSet2, fakeTipSet1)) + + var reverted bool + err = si.db.QueryRow("SELECT reverted FROM tipset_message WHERE tipset_key_cid = ?", tsCid2.Bytes()).Scan(&reverted) + require.NoError(t, err) + require.True(t, reverted) + + var reverted2 bool + err = si.db.QueryRow("SELECT reverted FROM tipset_message WHERE tipset_key_cid = ?", tsCid1.Bytes()).Scan(&reverted2) + require.NoError(t, err) + require.False(t, reverted2) + + // fetching events fails if excludeReverted is true + f = &EventFilter{ + TipsetCid: tsCid1, + } + ces, err = si.GetEventsForFilter(ctx, f, true) + require.NoError(t, err) + require.Equal(t, 0, len(ces)) + + // works if excludeReverted is false + ces, err = si.GetEventsForFilter(ctx, f, false) + require.NoError(t, err) + require.Equal(t, 2, len(ces)) +} + +func TestGetEventsFilterByAddress(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(60) + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + + addr1, err := address.NewIDAddress(1) + require.NoError(t, err) + addr2, err := address.NewIDAddress(2) + require.NoError(t, err) + addr3, err := address.NewIDAddress(3) + require.NoError(t, err) + + delegatedAddr1, err := address.NewFromString("f410fagkp3qx2f76maqot74jaiw3tzbxe76k76zrkl3xifk67isrnbn2sll3yua") + require.NoError(t, err) + + ev1 := fakeEvent( + abi.ActorID(1), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + ev2 := fakeEvent( + abi.ActorID(2), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr2")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + events := []types.Event{*ev1, *ev2} + + fm := fakeMessage(address.TestAddress, address.TestAddress) + em1 := executedMessage{ + msg: fm, + evs: events, + } + + si.SetActorToDelegatedAddresFunc(func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + if emitter == abi.ActorID(1) { + return delegatedAddr1, true + } + idAddr, err := address.NewIDAddress(uint64(emitter)) + if err != nil { + return address.Undef, false + } + return idAddr, true + }) + + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + return []executedMessage{em1}, nil + }) + + // Create a fake tipset at height 1 + fakeTipSet1 := fakeTipSet(t, rng, 1, nil) + fakeTipSet2 := fakeTipSet(t, rng, 2, nil) + + // Set the dummy chainstore to return this tipset for height 1 + cs.SetTipsetByHeightAndKey(1, fakeTipSet1.Key(), fakeTipSet1) // empty DB + cs.SetTipsetByHeightAndKey(2, fakeTipSet2.Key(), fakeTipSet2) // empty DB + + cs.SetMessagesForTipset(fakeTipSet1, []types.ChainMsg{fm}) + + require.NoError(t, si.Apply(ctx, fakeTipSet1, fakeTipSet2)) + + testCases := []struct { + name string + f *EventFilter + expectedCount int + expectedAddresses []address.Address + }{ + { + name: "matching single ID address (non-delegated)", + f: &EventFilter{ + Addresses: []address.Address{addr2}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 1, + expectedAddresses: []address.Address{addr2}, + }, + { + name: "matching single ID address", + f: &EventFilter{ + Addresses: []address.Address{addr1}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 1, + expectedAddresses: []address.Address{delegatedAddr1}, + }, + { + name: "matching single delegated address", + f: &EventFilter{ + Addresses: []address.Address{delegatedAddr1}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 1, + expectedAddresses: []address.Address{delegatedAddr1}, + }, + { + name: "matching multiple addresses", + f: &EventFilter{ + Addresses: []address.Address{addr1, addr2}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 2, + expectedAddresses: []address.Address{delegatedAddr1, addr2}, + }, + { + name: "no matching address", + f: &EventFilter{ + Addresses: []address.Address{addr3}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 0, + expectedAddresses: []address.Address{}, + }, + { + name: "empty address list", + f: &EventFilter{ + Addresses: []address.Address{}, + MinHeight: 1, + MaxHeight: 1, + }, + expectedCount: 2, + expectedAddresses: []address.Address{delegatedAddr1, addr2}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ces, err := si.GetEventsForFilter(ctx, tc.f, false) + require.NoError(t, err) + require.Equal(t, tc.expectedCount, len(ces)) + + actualAddresses := make([]address.Address, len(ces)) + for i, ce := range ces { + actualAddresses[i] = ce.EmitterAddr + } + + sortAddresses(tc.expectedAddresses) + sortAddresses(actualAddresses) + + require.Equal(t, tc.expectedAddresses, actualAddresses) + }) + } +} + +func sortAddresses(addrs []address.Address) { + sort.Slice(addrs, func(i, j int) bool { + return addrs[i].String() < addrs[j].String() + }) +} + +func fakeMessage(to, from address.Address) *types.Message { + return &types.Message{ + To: to, + From: from, + Nonce: 197, + Method: 1, + Params: []byte("some random bytes"), + GasLimit: 126723, + GasPremium: types.NewInt(4), + GasFeeCap: types.NewInt(120), + } +} + +func fakeEvent(emitter abi.ActorID, indexed []kv, unindexed []kv) *types.Event { + ev := &types.Event{ + Emitter: emitter, + } + + for _, in := range indexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x01, + Key: in.k, + Codec: cid.Raw, + Value: in.v, + }) + } + + for _, in := range unindexed { + ev.Entries = append(ev.Entries, types.EventEntry{ + Flags: 0x00, + Key: in.k, + Codec: cid.Raw, + Value: in.v, + }) + } + + return ev +} + +type kv struct { + k string + v []byte +} diff --git a/chain/index/gc.go b/chain/index/gc.go index 1e643b35e9d..5a7377e7263 100644 --- a/chain/index/gc.go +++ b/chain/index/gc.go @@ -25,12 +25,9 @@ func (si *SqliteIndexer) gcLoop() { defer cleanupTicker.Stop() for si.ctx.Err() == nil { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return } - si.closeLk.RUnlock() select { case <-cleanupTicker.C: @@ -70,7 +67,7 @@ func (si *SqliteIndexer) gc(ctx context.Context) { return } - log.Infof("gc'd %d tipsets before epoch %d", rows, removalEpoch) + log.Infof("gc'd %d entries before epoch %d", rows, removalEpoch) // ------------------------------------------------------------------------------------------------- // Also GC eth hashes diff --git a/chain/index/gc_test.go b/chain/index/gc_test.go new file mode 100644 index 00000000000..31ae0f69640 --- /dev/null +++ b/chain/index/gc_test.go @@ -0,0 +1,120 @@ +package index + +import ( + "context" + pseudo "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/chain/types" +) + +func TestGC(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + headHeight := abi.ChainEpoch(60) + si, _, cs := setupWithHeadIndexed(t, headHeight, rng) + t.Cleanup(func() { _ = si.Close() }) + + si.gcRetentionEpochs = 20 + + ev1 := fakeEvent( + abi.ActorID(1), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr1")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + ev2 := fakeEvent( + abi.ActorID(2), + []kv{ + {k: "type", v: []byte("approval")}, + {k: "signer", v: []byte("addr2")}, + }, + []kv{ + {k: "amount", v: []byte("2988181")}, + }, + ) + + events := []types.Event{*ev1, *ev2} + + fm := fakeMessage(address.TestAddress, address.TestAddress) + em1 := executedMessage{ + msg: fm, + evs: events, + } + + si.SetActorToDelegatedAddresFunc(func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) { + idAddr, err := address.NewIDAddress(uint64(emitter)) + if err != nil { + return address.Undef, false + } + + return idAddr, true + }) + + si.SetExecutedMessagesLoaderFunc(func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) { + if msgTs.Height() == 1 { + return []executedMessage{em1}, nil + } + return nil, nil + }) + + // Create a fake tipset at height 1 + fakeTipSet1 := fakeTipSet(t, rng, 1, nil) + fakeTipSet2 := fakeTipSet(t, rng, 10, nil) + fakeTipSet3 := fakeTipSet(t, rng, 50, nil) + + // Set the dummy chainstore to return this tipset for height 1 + cs.SetTipsetByHeightAndKey(1, fakeTipSet1.Key(), fakeTipSet1) // empty DB + cs.SetTipsetByHeightAndKey(10, fakeTipSet2.Key(), fakeTipSet2) // empty DB + cs.SetTipsetByHeightAndKey(50, fakeTipSet3.Key(), fakeTipSet3) // empty DB + + cs.SetMessagesForTipset(fakeTipSet1, []types.ChainMsg{fm}) + + // index tipset and events + require.NoError(t, si.Apply(ctx, fakeTipSet1, fakeTipSet2)) + require.NoError(t, si.Apply(ctx, fakeTipSet2, fakeTipSet3)) + + // getLogs works for height 1 + filter := &EventFilter{ + MinHeight: 1, + MaxHeight: 1, + } + ces, err := si.GetEventsForFilter(ctx, filter, false) + require.NoError(t, err) + require.Len(t, ces, 2) + + si.gc(ctx) + + // getLogs does not work for height 1 + _, err = si.GetEventsForFilter(ctx, filter, false) + require.Error(t, err) + + // Verify that the tipset at height 1 is removed + var count int + err = si.db.QueryRow("SELECT COUNT(*) FROM tipset_message WHERE height = 1").Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count) + + // Verify that the tipset at height 10 is not removed + err = si.db.QueryRow("SELECT COUNT(*) FROM tipset_message WHERE height = 10").Scan(&count) + require.NoError(t, err) + require.Equal(t, 0, count) + + // Verify that the tipset at height 50 is not removed + err = si.db.QueryRow("SELECT COUNT(*) FROM tipset_message WHERE height = 50").Scan(&count) + require.NoError(t, err) + require.Equal(t, 1, count) +} diff --git a/chain/index/helpers.go b/chain/index/helpers.go index d80b1f5effd..a4db495c99e 100644 --- a/chain/index/helpers.go +++ b/chain/index/helpers.go @@ -3,15 +3,20 @@ package index import ( "context" "database/sql" + "errors" "os" + "strings" + "time" ipld "github.com/ipfs/go-ipld-format" "golang.org/x/xerrors" - "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/types" ) +const maxRetries = 3 +const retryDelay = 150 * time.Millisecond + // PopulateFromSnapshot initializes and populates the chain index from a snapshot. // // This function creates a new Index at the specified path and populates @@ -80,27 +85,10 @@ func PopulateFromSnapshot(ctx context.Context, path string, cs ChainStore) error return nil } -func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, indexer Indexer) { - for ctx.Err() == nil { - select { - case <-ctx.Done(): - return - case u := <-ch: - if u.Type != api.MpoolAdd { - continue - } - if u.Message == nil { - continue - } - err := indexer.IndexSignedMessage(ctx, u.Message) - if err != nil { - log.Errorw("failed to index signed Mpool message", "error", err) - } - } - } -} - func toTipsetKeyCidBytes(ts *types.TipSet) ([]byte, error) { + if ts == nil { + return nil, errors.New("failed to get tipset key cid: tipset is nil") + } tsKeyCid, err := ts.Key().Cid() if err != nil { return nil, err @@ -108,29 +96,55 @@ func toTipsetKeyCidBytes(ts *types.TipSet) ([]byte, error) { return tsKeyCid.Bytes(), nil } -func withTx(ctx context.Context, db *sql.DB, fn func(*sql.Tx) error) (err error) { - var tx *sql.Tx - tx, err = db.BeginTx(ctx, nil) - if err != nil { - return xerrors.Errorf("failed to begin transaction: %w", err) - } +func withTx(ctx context.Context, db *sql.DB, fn func(*sql.Tx) error) error { + var err error + for i := 0; i < maxRetries; i++ { + if ctx.Err() != nil { + return ctx.Err() + } + var tx *sql.Tx + tx, err = db.BeginTx(ctx, nil) + if err != nil { + return xerrors.Errorf("failed to begin transaction: %w", err) + } - defer func() { - if p := recover(); p != nil { - // A panic occurred, rollback and repanic - _ = tx.Rollback() - panic(p) - } else if err != nil { - // Something went wrong, rollback - _ = tx.Rollback() - } else { - // All good, commit - err = tx.Commit() + defer func() { + if p := recover(); p != nil { + // A panic occurred, rollback and repanic + if tx != nil { + _ = tx.Rollback() + } + panic(p) + } + }() + + err = fn(tx) + if err == nil { + if commitErr := tx.Commit(); commitErr != nil { + return xerrors.Errorf("failed to commit transaction: %w", commitErr) + } + return nil } - }() - err = fn(tx) - return + _ = tx.Rollback() + + if !isRetryableError(err) { + return xerrors.Errorf("transaction failed: %w", err) + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(retryDelay): + // Retry after delay + } + } + + return xerrors.Errorf("transaction failed after %d retries; last error: %w", maxRetries, err) +} + +func isRetryableError(err error) bool { + return err != nil && strings.Contains(err.Error(), "database is locked") } func isIndexedFlag(b uint8) bool { diff --git a/chain/index/indexer.go b/chain/index/indexer.go index 46855216276..a0d8b860e82 100644 --- a/chain/index/indexer.go +++ b/chain/index/indexer.go @@ -22,6 +22,7 @@ var _ Indexer = (*SqliteIndexer)(nil) // ActorToDelegatedAddressFunc is a function type that resolves an actor ID to a DelegatedAddress if one exists for that actor, otherwise returns nil type ActorToDelegatedAddressFunc func(ctx context.Context, emitter abi.ActorID, ts *types.TipSet) (address.Address, bool) +type emsLoaderFunc func(ctx context.Context, cs ChainStore, msgTs, rctTs *types.TipSet) ([]executedMessage, error) type recomputeTipSetStateFunc func(ctx context.Context, ts *types.TipSet) error type preparedStatements struct { @@ -36,7 +37,7 @@ type preparedStatements struct { removeEthHashesOlderThanStmt *sql.Stmt updateTipsetsToRevertedFromHeightStmt *sql.Stmt updateEventsToRevertedFromHeightStmt *sql.Stmt - isTipsetMessageNonEmptyStmt *sql.Stmt + isIndexEmptyStmt *sql.Stmt getMinNonRevertedHeightStmt *sql.Stmt hasNonRevertedTipsetStmt *sql.Stmt updateEventsToRevertedStmt *sql.Stmt @@ -44,6 +45,16 @@ type preparedStatements struct { getMsgIdForMsgCidAndTipsetStmt *sql.Stmt insertEventStmt *sql.Stmt insertEventEntryStmt *sql.Stmt + + hasNullRoundAtHeightStmt *sql.Stmt + getNonRevertedTipsetAtHeightStmt *sql.Stmt + countTipsetsAtHeightStmt *sql.Stmt + + getNonRevertedTipsetMessageCountStmt *sql.Stmt + getNonRevertedTipsetEventCountStmt *sql.Stmt + getNonRevertedTipsetEventEntriesCountStmt *sql.Stmt + hasRevertedEventsInTipsetStmt *sql.Stmt + removeRevertedTipsetsBeforeHeightStmt *sql.Stmt } type SqliteIndexer struct { @@ -56,6 +67,7 @@ type SqliteIndexer struct { actorToDelegatedAddresFunc ActorToDelegatedAddressFunc recomputeTipSetStateFunc recomputeTipSetStateFunc + executedMessagesLoaderFunc emsLoaderFunc stmts *preparedStatements @@ -67,8 +79,13 @@ type SqliteIndexer struct { updateSubs map[uint64]*updateSub subIdCounter uint64 + started bool + closeLk sync.RWMutex closed bool + + // ensures writes are serialized so backfilling does not race with index updates + writerLk sync.Mutex } func NewSqliteIndexer(path string, cs ChainStore, gcRetentionEpochs int64, reconcileEmptyIndex bool, @@ -117,18 +134,19 @@ func NewSqliteIndexer(path string, cs ChainStore, gcRetentionEpochs int64, recon return si, nil } -func (si *SqliteIndexer) Start() error { +func (si *SqliteIndexer) Start() { si.wg.Add(1) go si.gcLoop() - return nil + + si.started = true } func (si *SqliteIndexer) SetActorToDelegatedAddresFunc(actorToDelegatedAddresFunc ActorToDelegatedAddressFunc) { si.actorToDelegatedAddresFunc = actorToDelegatedAddresFunc } -func (si *SqliteIndexer) SetRecomputeTipSetStateFunc(recomputeTipSetStateFunc recomputeTipSetStateFunc) { - si.recomputeTipSetStateFunc = recomputeTipSetStateFunc +func (si *SqliteIndexer) SetExecutedMessagesLoaderFunc(eventLoaderFunc emsLoaderFunc) { + si.executedMessagesLoaderFunc = eventLoaderFunc } func (si *SqliteIndexer) Close() error { @@ -165,12 +183,9 @@ func (si *SqliteIndexer) initStatements() error { } func (si *SqliteIndexer) IndexEthTxHash(ctx context.Context, txHash ethtypes.EthHash, msgCid cid.Cid) error { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return ErrClosed } - si.closeLk.RUnlock() return withTx(ctx, si.db, func(tx *sql.Tx) error { return si.indexEthTxHash(ctx, tx, txHash, msgCid) @@ -191,12 +206,10 @@ func (si *SqliteIndexer) IndexSignedMessage(ctx context.Context, msg *types.Sign if msg.Signature.Type != crypto.SigTypeDelegated { return nil } - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + + if si.isClosed() { return ErrClosed } - si.closeLk.RUnlock() return withTx(ctx, si.db, func(tx *sql.Tx) error { return si.indexSignedMessage(ctx, tx, msg) @@ -218,12 +231,11 @@ func (si *SqliteIndexer) indexSignedMessage(ctx context.Context, tx *sql.Tx, msg } func (si *SqliteIndexer) Apply(ctx context.Context, from, to *types.TipSet) error { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return ErrClosed } - si.closeLk.RUnlock() + + si.writerLk.Lock() // We're moving the chain ahead from the `from` tipset to the `to` tipset // Height(to) > Height(from) @@ -236,8 +248,10 @@ func (si *SqliteIndexer) Apply(ctx context.Context, from, to *types.TipSet) erro }) if err != nil { + si.writerLk.Unlock() return xerrors.Errorf("failed to apply tipset: %w", err) } + si.writerLk.Unlock() si.notifyUpdateSubs() @@ -335,12 +349,9 @@ func (si *SqliteIndexer) restoreTipsetIfExists(ctx context.Context, tx *sql.Tx, } func (si *SqliteIndexer) Revert(ctx context.Context, from, to *types.TipSet) error { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return ErrClosed } - si.closeLk.RUnlock() // We're reverting the chain from the tipset at `from` to the tipset at `to`. // Height(to) < Height(from) @@ -357,13 +368,21 @@ func (si *SqliteIndexer) Revert(ctx context.Context, from, to *types.TipSet) err return xerrors.Errorf("failed to get tipset key cid: %w", err) } + si.writerLk.Lock() + err = withTx(ctx, si.db, func(tx *sql.Tx) error { + // revert the `from` tipset if _, err := tx.Stmt(si.stmts.updateTipsetToRevertedStmt).ExecContext(ctx, revertTsKeyCid); err != nil { return xerrors.Errorf("failed to mark tipset %s as reverted: %w", revertTsKeyCid, err) } + // index the `to` tipset -> it is idempotent + if err := si.indexTipset(ctx, tx, to); err != nil { + return xerrors.Errorf("failed to index tipset: %w", err) + } + // events are indexed against the message inclusion tipset, not the message execution tipset. - // So we need to revert the events for the message inclusion tipset. + // So we need to revert the events for the message inclusion tipset i.e. `to` tipset. if _, err := tx.Stmt(si.stmts.updateEventsToRevertedStmt).ExecContext(ctx, eventTsKeyCid); err != nil { return xerrors.Errorf("failed to revert events for tipset %s: %w", eventTsKeyCid, err) } @@ -371,10 +390,18 @@ func (si *SqliteIndexer) Revert(ctx context.Context, from, to *types.TipSet) err return nil }) if err != nil { + si.writerLk.Unlock() return xerrors.Errorf("failed during revert transaction: %w", err) } + si.writerLk.Unlock() si.notifyUpdateSubs() return nil } + +func (si *SqliteIndexer) isClosed() bool { + si.closeLk.RLock() + defer si.closeLk.RUnlock() + return si.closed +} diff --git a/chain/index/indexer_test.go b/chain/index/indexer_test.go new file mode 100644 index 00000000000..bc4a7a70c4f --- /dev/null +++ b/chain/index/indexer_test.go @@ -0,0 +1,56 @@ +package index + +import ( + "context" + "database/sql" + pseudo "math/rand" + "testing" + "time" + + "github.com/stretchr/testify/require" +) + +func TestRestoreTipsetIfExists(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + si, _, _ := setupWithHeadIndexed(t, 10, rng) + + tsKeyCid := randomCid(t, rng) + tsKeyCidBytes := tsKeyCid.Bytes() + + err := withTx(ctx, si.db, func(tx *sql.Tx) error { + // tipset does not exist + exists, err := si.restoreTipsetIfExists(ctx, tx, tsKeyCidBytes) + require.NoError(t, err) + require.False(t, exists) + + // insert reverted tipset + _, err = tx.Stmt(si.stmts.insertTipsetMessageStmt).Exec(tsKeyCidBytes, 1, 1, randomCid(t, rng).Bytes(), 0) + require.NoError(t, err) + + // tipset exists and is NOT reverted + exists, err = si.restoreTipsetIfExists(ctx, tx, tsKeyCidBytes) + require.NoError(t, err) + require.True(t, exists) + + // Verify that the tipset is not reverted + var reverted bool + err = tx.QueryRow("SELECT reverted FROM tipset_message WHERE tipset_key_cid = ?", tsKeyCidBytes).Scan(&reverted) + require.NoError(t, err) + require.False(t, reverted, "Tipset should not be reverted") + + return nil + }) + require.NoError(t, err) + + exists, err := si.isTipsetIndexed(ctx, tsKeyCidBytes) + require.NoError(t, err) + require.True(t, exists) + + fc := randomCid(t, rng) + exists, err = si.isTipsetIndexed(ctx, fc.Bytes()) + require.NoError(t, err) + require.False(t, exists) +} diff --git a/chain/index/interface.go b/chain/index/interface.go index 0f35c8d4415..cbc5bb60830 100644 --- a/chain/index/interface.go +++ b/chain/index/interface.go @@ -50,13 +50,14 @@ type EventFilter struct { } type Indexer interface { - Start() error + Start() ReconcileWithChain(ctx context.Context, currHead *types.TipSet) error IndexSignedMessage(ctx context.Context, msg *types.SignedMessage) error IndexEthTxHash(ctx context.Context, txHash ethtypes.EthHash, c cid.Cid) error SetActorToDelegatedAddresFunc(idToRobustAddrFunc ActorToDelegatedAddressFunc) - SetRecomputeTipSetStateFunc(recomputeTipSetStateFunc recomputeTipSetStateFunc) + SetExecutedMessagesLoaderFunc(f emsLoaderFunc) + Apply(ctx context.Context, from, to *types.TipSet) error Revert(ctx context.Context, from, to *types.TipSet) error @@ -67,6 +68,8 @@ type Indexer interface { GetEventsForFilter(ctx context.Context, f *EventFilter, excludeReverted bool) ([]*CollectedEvent, error) + ChainValidateIndex(ctx context.Context, epoch abi.ChainEpoch, backfill bool) (*types.IndexValidation, error) + Close() error } diff --git a/chain/index/read.go b/chain/index/read.go index 3d03e7957ce..cee9b2c428b 100644 --- a/chain/index/read.go +++ b/chain/index/read.go @@ -16,12 +16,9 @@ import ( const headIndexedWaitTimeout = 5 * time.Second func (si *SqliteIndexer) GetCidFromHash(ctx context.Context, txHash ethtypes.EthHash) (cid.Cid, error) { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return cid.Undef, ErrClosed } - si.closeLk.RUnlock() var msgCidBytes []byte @@ -44,12 +41,9 @@ func (si *SqliteIndexer) queryMsgCidFromEthHash(ctx context.Context, txHash etht } func (si *SqliteIndexer) GetMsgInfo(ctx context.Context, messageCid cid.Cid) (*MsgInfo, error) { - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return nil, ErrClosed } - si.closeLk.RUnlock() var tipsetKeyCidBytes []byte var height int64 diff --git a/chain/index/read_test.go b/chain/index/read_test.go new file mode 100644 index 00000000000..5557a3bd06a --- /dev/null +++ b/chain/index/read_test.go @@ -0,0 +1,307 @@ +package index + +import ( + "context" + "errors" + pseudo "math/rand" + "sync" + "testing" + "time" + + "github.com/ipfs/go-cid" + mh "github.com/multiformats/go-multihash" + "github.com/stretchr/testify/require" + + "github.com/filecoin-project/go-address" + "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/go-state-types/crypto" + + "github.com/filecoin-project/lotus/chain/actors/adt" + "github.com/filecoin-project/lotus/chain/types" + "github.com/filecoin-project/lotus/chain/types/ethtypes" +) + +func TestGetCidFromHash(t *testing.T) { + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + ctx := context.Background() + + s, _, _ := setupWithHeadIndexed(t, 10, rng) + + ethTxHash := ethtypes.EthHash([32]byte{1}) + msgCid := randomCid(t, rng) + + // read from empty db -> ErrNotFound + c, err := s.GetCidFromHash(ctx, ethTxHash) + require.Error(t, err) + require.True(t, errors.Is(err, ErrNotFound)) + require.EqualValues(t, cid.Undef, c) + + // insert and read + insertEthTxHash(t, s, ethTxHash, msgCid) + c, err = s.GetCidFromHash(ctx, ethTxHash) + require.NoError(t, err) + require.EqualValues(t, msgCid, c) + + // look up some other hash -> fails + c, err = s.GetCidFromHash(ctx, ethtypes.EthHash([32]byte{2})) + require.Error(t, err) + require.True(t, errors.Is(err, ErrNotFound)) + require.EqualValues(t, cid.Undef, c) +} + +func TestGetMsgInfo(t *testing.T) { + ctx := context.Background() + seed := time.Now().UnixNano() + t.Logf("seed: %d", seed) + rng := pseudo.New(pseudo.NewSource(seed)) + s, _, _ := setupWithHeadIndexed(t, 10, rng) + + msgCid := randomCid(t, rng) + + // read from empty db -> ErrNotFound + mi, err := s.GetMsgInfo(ctx, msgCid) + require.Error(t, err) + require.True(t, errors.Is(err, ErrNotFound)) + require.Nil(t, mi) + + msgCidBytes := msgCid.Bytes() + tsKeyCid := randomCid(t, rng) + // insert and read + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: tsKeyCid.Bytes(), + height: uint64(1), + reverted: false, + messageCid: msgCidBytes, + messageIndex: 1, + }) + mi, err = s.GetMsgInfo(ctx, msgCid) + require.NoError(t, err) + require.Equal(t, msgCid, mi.Message) + require.Equal(t, tsKeyCid, mi.TipSet) + require.Equal(t, abi.ChainEpoch(1), mi.Epoch) +} + +func setupWithHeadIndexed(t *testing.T, headHeight abi.ChainEpoch, rng *pseudo.Rand) (*SqliteIndexer, *types.TipSet, *dummyChainStore) { + head := fakeTipSet(t, rng, headHeight, []cid.Cid{}) + d := newDummyChainStore() + d.SetHeaviestTipSet(head) + + s, err := NewSqliteIndexer(":memory:", d, 0, false, 0) + require.NoError(t, err) + insertHead(t, s, head, headHeight) + + return s, head, d +} + +func insertHead(t *testing.T, s *SqliteIndexer, head *types.TipSet, height abi.ChainEpoch) { + headKeyBytes, err := toTipsetKeyCidBytes(head) + require.NoError(t, err) + + insertTipsetMessage(t, s, tipsetMessage{ + tipsetKeyCid: headKeyBytes, + height: uint64(height), + reverted: false, + messageCid: nil, + messageIndex: -1, + }) +} + +func insertEthTxHash(t *testing.T, s *SqliteIndexer, ethTxHash ethtypes.EthHash, messageCid cid.Cid) { + msgCidBytes := messageCid.Bytes() + + res, err := s.stmts.insertEthTxHashStmt.Exec(ethTxHash.String(), msgCidBytes) + require.NoError(t, err) + rowsAffected, err := res.RowsAffected() + require.NoError(t, err) + require.Equal(t, int64(1), rowsAffected) +} + +type dummyChainStore struct { + mu sync.RWMutex + + heightToTipSet map[abi.ChainEpoch]*types.TipSet + messagesForTipset map[*types.TipSet][]types.ChainMsg + keyToTipSet map[types.TipSetKey]*types.TipSet + + heaviestTipSet *types.TipSet + tipSetByCid func(ctx context.Context, tsKeyCid cid.Cid) (*types.TipSet, error) + messagesForBlock func(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) + actorStore func(ctx context.Context) adt.Store +} + +func newDummyChainStore() *dummyChainStore { + return &dummyChainStore{ + heightToTipSet: make(map[abi.ChainEpoch]*types.TipSet), + messagesForTipset: make(map[*types.TipSet][]types.ChainMsg), + keyToTipSet: make(map[types.TipSetKey]*types.TipSet), + } +} + +func (d *dummyChainStore) MessagesForTipset(ctx context.Context, ts *types.TipSet) ([]types.ChainMsg, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + msgs, ok := d.messagesForTipset[ts] + if !ok { + return nil, nil + } + return msgs, nil +} + +func (d *dummyChainStore) GetHeaviestTipSet() *types.TipSet { + d.mu.RLock() + defer d.mu.RUnlock() + return d.heaviestTipSet +} + +func (d *dummyChainStore) GetTipSetByCid(ctx context.Context, tsKeyCid cid.Cid) (*types.TipSet, error) { + d.mu.RLock() + defer d.mu.RUnlock() + if d.tipSetByCid != nil { + return d.tipSetByCid(ctx, tsKeyCid) + } + return nil, nil +} + +func (d *dummyChainStore) GetTipSetFromKey(ctx context.Context, tsk types.TipSetKey) (*types.TipSet, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + return d.keyToTipSet[tsk], nil +} + +func (d *dummyChainStore) MessagesForBlock(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error) { + d.mu.RLock() + defer d.mu.RUnlock() + if d.messagesForBlock != nil { + return d.messagesForBlock(ctx, b) + } + return nil, nil, nil +} + +func (d *dummyChainStore) ActorStore(ctx context.Context) adt.Store { + d.mu.RLock() + defer d.mu.RUnlock() + if d.actorStore != nil { + return d.actorStore(ctx) + } + return nil +} + +func (d *dummyChainStore) GetTipsetByHeight(ctx context.Context, h abi.ChainEpoch, _ *types.TipSet, prev bool) (*types.TipSet, error) { + d.mu.RLock() + defer d.mu.RUnlock() + + ts, ok := d.heightToTipSet[h] + if !ok { + return nil, errors.New("tipset not found") + } + return ts, nil +} + +func (d *dummyChainStore) IsStoringEvents() bool { + return true +} + +// Setter methods to configure the mock + +func (d *dummyChainStore) SetMessagesForTipset(ts *types.TipSet, msgs []types.ChainMsg) { + d.mu.Lock() + defer d.mu.Unlock() + d.messagesForTipset[ts] = msgs +} + +func (d *dummyChainStore) SetHeaviestTipSet(ts *types.TipSet) { + d.mu.Lock() + defer d.mu.Unlock() + d.heaviestTipSet = ts +} + +func (d *dummyChainStore) SetTipSetByCid(f func(ctx context.Context, tsKeyCid cid.Cid) (*types.TipSet, error)) { + d.mu.Lock() + defer d.mu.Unlock() + d.tipSetByCid = f +} + +func (d *dummyChainStore) SetMessagesForBlock(f func(ctx context.Context, b *types.BlockHeader) ([]*types.Message, []*types.SignedMessage, error)) { + d.mu.Lock() + defer d.mu.Unlock() + d.messagesForBlock = f +} + +func (d *dummyChainStore) SetActorStore(f func(ctx context.Context) adt.Store) { + d.mu.Lock() + defer d.mu.Unlock() + d.actorStore = f +} + +func (d *dummyChainStore) SetTipsetByHeightAndKey(h abi.ChainEpoch, tsk types.TipSetKey, ts *types.TipSet) { + d.mu.Lock() + defer d.mu.Unlock() + + d.heightToTipSet[h] = ts + d.keyToTipSet[tsk] = ts +} + +func randomIDAddr(tb testing.TB, rng *pseudo.Rand) address.Address { + tb.Helper() + addr, err := address.NewIDAddress(uint64(rng.Int63())) + require.NoError(tb, err) + return addr +} + +func randomCid(tb testing.TB, rng *pseudo.Rand) cid.Cid { + tb.Helper() + cb := cid.V1Builder{Codec: cid.Raw, MhType: mh.IDENTITY} + c, err := cb.Sum(randomBytes(10, rng)) + require.NoError(tb, err) + return c +} + +func randomBytes(n int, rng *pseudo.Rand) []byte { + buf := make([]byte, n) + rng.Read(buf) + return buf +} + +func fakeTipSet(tb testing.TB, rng *pseudo.Rand, h abi.ChainEpoch, parents []cid.Cid) *types.TipSet { + tb.Helper() + ts, err := types.NewTipSet([]*types.BlockHeader{ + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte(h % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + { + Height: h, + Miner: randomIDAddr(tb, rng), + + Parents: parents, + + Ticket: &types.Ticket{VRFProof: []byte{byte((h + 1) % 2)}}, + + ParentStateRoot: randomCid(tb, rng), + Messages: randomCid(tb, rng), + ParentMessageReceipts: randomCid(tb, rng), + + BlockSig: &crypto.Signature{Type: crypto.SigTypeBLS}, + BLSAggregate: &crypto.Signature{Type: crypto.SigTypeBLS}, + }, + }) + + require.NoError(tb, err) + + return ts +} diff --git a/chain/index/reconcile.go b/chain/index/reconcile.go index a139ff79852..72a1e6ecaa6 100644 --- a/chain/index/reconcile.go +++ b/chain/index/reconcile.go @@ -31,25 +31,21 @@ func (si *SqliteIndexer) ReconcileWithChain(ctx context.Context, head *types.Tip log.Warn("chain indexer is not storing events during reconciliation; please ensure this is intentional") } - si.closeLk.RLock() - if si.closed { - si.closeLk.RUnlock() + if si.isClosed() { return ErrClosed } - si.closeLk.RUnlock() if head == nil { return nil } return withTx(ctx, si.db, func(tx *sql.Tx) error { - var hasTipset bool - err := tx.StmtContext(ctx, si.stmts.isTipsetMessageNonEmptyStmt).QueryRowContext(ctx).Scan(&hasTipset) + var isIndexEmpty bool + err := tx.StmtContext(ctx, si.stmts.isIndexEmptyStmt).QueryRowContext(ctx).Scan(&isIndexEmpty) if err != nil { - return xerrors.Errorf("failed to check if tipset message is empty: %w", err) + return xerrors.Errorf("failed to check if index is empty: %w", err) } - isIndexEmpty := !hasTipset if isIndexEmpty && !si.reconcileEmptyIndex { log.Info("chain index is empty and reconcileEmptyIndex is disabled; skipping reconciliation") return nil diff --git a/chain/types/index.go b/chain/types/index.go new file mode 100644 index 00000000000..93c44ad2e43 --- /dev/null +++ b/chain/types/index.go @@ -0,0 +1,23 @@ +package types + +import ( + "github.com/filecoin-project/go-state-types/abi" +) + +// IndexValidation contains detailed information about the validation status of a specific chain epoch. +type IndexValidation struct { + // TipSetKey is the key of the canonical tipset for this epoch. + TipSetKey TipSetKey + // Height is the epoch height at which the validation is performed. + Height abi.ChainEpoch + // IndexedMessagesCount is the number of indexed messages for the canonical tipset at this epoch. + IndexedMessagesCount uint64 + // IndexedEventsCount is the number of indexed events for the canonical tipset at this epoch. + IndexedEventsCount uint64 + // IndexedEventEntriesCount is the number of indexed event entries for the canonical tipset at this epoch. + IndexedEventEntriesCount uint64 + // Backfilled denotes whether missing data was successfully backfilled into the index during validation. + Backfilled bool + // IsNullRound indicates if the epoch corresponds to a null round and therefore does not have any indexed messages or events. + IsNullRound bool +} diff --git a/cmd/lotus-shed/chain_index.go b/cmd/lotus-shed/chain_index.go new file mode 100644 index 00000000000..701df62d596 --- /dev/null +++ b/cmd/lotus-shed/chain_index.go @@ -0,0 +1,216 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "time" + + "github.com/ipfs/go-cid" + "github.com/urfave/cli/v2" + "golang.org/x/xerrors" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/types" + lcli "github.com/filecoin-project/lotus/cli" +) + +var chainIndexCmds = &cli.Command{ + Name: "chainindex", + Usage: "Commands related to managing the chainindex", + HideHelpCommand: true, + Subcommands: []*cli.Command{ + withCategory("chainindex", validateBackfillChainIndexCmd), + }, +} + +var validateBackfillChainIndexCmd = &cli.Command{ + Name: "validate-backfill", + Usage: "Validates and optionally backfills the chainindex for a range of epochs", + Description: ` +lotus-shed chainindex validate-backfill --from --to [--backfill] [--log-good] + +The command validates the chain index entries for each epoch in the specified range, checking for missing or +inconsistent entries (i.e. the indexed data does not match the actual chain state). If '--backfill' is enabled +(which it is by default), it will attempt to backfill any missing entries using the 'ChainValidateIndex' API. + +Parameters: + - '--from' (required): The starting epoch (inclusive) for the validation range. Must be greater than 0. + - '--to' (required): The ending epoch (inclusive) for the validation range. Must be greater than 0 and less + than or equal to 'from'. + - '--backfill' (optional, default: true): Whether to backfill missing index entries during validation. + - '--log-good' (optional, default: false): Whether to log details for tipsets that have no detected problems. + +Error conditions: + - If 'from' or 'to' are invalid (<=0 or 'to' > 'from'), an error is returned. + - If the 'ChainValidateIndex' API returns an error for an epoch, indicating an inconsistency between the index + and chain state, an error message is logged for that epoch. + +Logging: + - Progress is logged every 2880 epochs (1 day worth of epochs) processed during the validation process. + - If '--log-good' is enabled, details are also logged for each epoch that has no detected problems. This includes: + - Null rounds with no messages/events. + - Epochs with a valid indexed entry. + +Example usage: + +To validate and backfill the chain index for the last 5760 epochs (2 days) and log details for all epochs: + +lotus-shed chainindex validate-backfill --from 1000000 --to 994240 --log-good + +This command is useful for backfilling the chain index over a range of historical epochs during the migration to +the new ChainIndexer. It can also be run periodically to validate the index's integrity using system schedulers +like cron. + `, + Flags: []cli.Flag{ + &cli.IntFlag{ + Name: "from", + Usage: "from specifies the starting tipset epoch for validation (inclusive)", + Required: true, + }, + &cli.IntFlag{ + Name: "to", + Usage: "to specifies the ending tipset epoch for validation (inclusive)", + Required: true, + }, + &cli.BoolFlag{ + Name: "backfill", + Usage: "backfill determines whether to backfill missing index entries during validation (default: true)", + Value: true, + }, + &cli.BoolFlag{ + Name: "log-good", + Usage: "log tipsets that have no detected problems", + Value: false, + }, + }, + Action: func(cctx *cli.Context) error { + srv, err := lcli.GetFullNodeServices(cctx) + if err != nil { + return xerrors.Errorf("failed to get full node services: %w", err) + } + defer func() { + if closeErr := srv.Close(); closeErr != nil { + log.Errorf("error closing services: %w", closeErr) + } + }() + + api := srv.FullNodeAPI() + ctx := lcli.ReqContext(cctx) + + fromEpoch := cctx.Int("from") + if fromEpoch <= 0 { + return xerrors.Errorf("invalid from epoch: %d, must be greater than 0", fromEpoch) + } + + toEpoch := cctx.Int("to") + if toEpoch <= 0 { + return xerrors.Errorf("invalid to epoch: %d, must be greater than 0", toEpoch) + } + if toEpoch > fromEpoch { + return xerrors.Errorf("to epoch (%d) must be less than or equal to from epoch (%d)", toEpoch, fromEpoch) + } + + head, err := api.ChainHead(ctx) + if err != nil { + return xerrors.Errorf("failed to get chain head: %w", err) + } + if head.Height() <= abi.ChainEpoch(fromEpoch) { + return xerrors.Errorf("from epoch (%d) must be less than chain head (%d)", fromEpoch, head.Height()) + } + + backfill := cctx.Bool("backfill") + + // Results Tracking + logGood := cctx.Bool("log-good") + + failedRPCs := 0 + successfulBackfills := 0 + successfulValidations := 0 + successfulNullRounds := 0 + + startTime := time.Now() + _, _ = fmt.Fprintf(cctx.App.Writer, "%s starting chainindex validation; from epoch: %d; to epoch: %d; backfill: %t; log-good: %t\n", currentTimeString(), + fromEpoch, toEpoch, backfill, logGood) + + totalEpochs := fromEpoch - toEpoch + 1 + for epoch := fromEpoch; epoch >= toEpoch; epoch-- { + if ctx.Err() != nil { + return ctx.Err() + } + + if (fromEpoch-epoch+1)%2880 == 0 || epoch == toEpoch { + progress := float64(fromEpoch-epoch+1) / float64(totalEpochs) * 100 + elapsed := time.Since(startTime) + _, _ = fmt.Fprintf(cctx.App.ErrWriter, "%s -------- Chain index validation progress: %.2f%%; Time elapsed: %s\n", + currentTimeString(), progress, elapsed) + } + + indexValidateResp, err := api.ChainValidateIndex(ctx, abi.ChainEpoch(epoch), backfill) + if err != nil { + tsKeyCid, err := tipsetKeyCid(ctx, abi.ChainEpoch(epoch), api) + if err != nil { + return fmt.Errorf("failed to get tipset key cid for epoch %d: %w", epoch, err) + } + _, _ = fmt.Fprintf(cctx.App.Writer, "%s ✗ Epoch %d (%s); failure: %s\n", currentTimeString(), epoch, tsKeyCid, err) + failedRPCs++ + continue + } + + if indexValidateResp.Backfilled { + successfulBackfills++ + } else if indexValidateResp.IsNullRound { + successfulNullRounds++ + } else { + successfulValidations++ + } + + if !logGood { + continue + } + + if indexValidateResp.IsNullRound { + tsKeyCid, err := tipsetKeyCid(ctx, abi.ChainEpoch(epoch), api) + if err != nil { + return fmt.Errorf("failed to get tipset key cid for epoch %d: %w", epoch, err) + } + _, _ = fmt.Fprintf(cctx.App.Writer, "%s ✓ Epoch %d (%s); null round\n", currentTimeString(), epoch, + tsKeyCid) + } else { + jsonData, err := json.Marshal(indexValidateResp) + if err != nil { + return fmt.Errorf("failed to marshal results to JSON: %w", err) + } + + _, _ = fmt.Fprintf(cctx.App.Writer, "%s ✓ Epoch %d (%s)\n", currentTimeString(), epoch, string(jsonData)) + } + } + + _, _ = fmt.Fprintf(cctx.App.Writer, "\n%s Chain index validation summary:\n", currentTimeString()) + _, _ = fmt.Fprintf(cctx.App.Writer, "Total failed RPC calls: %d\n", failedRPCs) + _, _ = fmt.Fprintf(cctx.App.Writer, "Total successful backfills: %d\n", successfulBackfills) + _, _ = fmt.Fprintf(cctx.App.Writer, "Total successful validations without backfilling: %d\n", successfulValidations) + _, _ = fmt.Fprintf(cctx.App.Writer, "Total successful Null round validations: %d\n", successfulNullRounds) + + return nil + }, +} + +func tipsetKeyCid(ctx context.Context, epoch abi.ChainEpoch, a api.FullNode) (cid.Cid, error) { + ts, err := a.ChainGetTipSetByHeight(ctx, epoch, types.EmptyTSK) + if err != nil { + return cid.Undef, fmt.Errorf("failed to get tipset for epoch %d: %w", epoch, err) + } + tsKeyCid, err := ts.Key().Cid() + if err != nil { + return cid.Undef, fmt.Errorf("failed to get tipset key cid for epoch %d: %w", epoch, err) + } + return tsKeyCid, nil +} + +func currentTimeString() string { + currentTime := time.Now().Format("2006-01-02 15:04:05.000") + return currentTime +} diff --git a/cmd/lotus-shed/main.go b/cmd/lotus-shed/main.go index 911da346e97..704a777a9ff 100644 --- a/cmd/lotus-shed/main.go +++ b/cmd/lotus-shed/main.go @@ -87,6 +87,7 @@ func main() { gasTraceCmd, replayOfflineCmd, indexesCmd, + chainIndexCmds, FevmAnalyticsCmd, mismatchesCmd, blockCmd, diff --git a/cmd/lotus/daemon.go b/cmd/lotus/daemon.go index dbea4f5e69b..b7fbd63e695 100644 --- a/cmd/lotus/daemon.go +++ b/cmd/lotus/daemon.go @@ -644,7 +644,7 @@ func ImportChain(ctx context.Context, r repo.Repo, fname string, snapshot bool) } // populate the chain Index from the snapshot - basePath, err := lr.SqlitePath() + basePath, err := lr.ChainIndexPath() if err != nil { return err } diff --git a/documentation/en/api-v1-unstable-methods.md b/documentation/en/api-v1-unstable-methods.md index 24bd9838a29..a075c7ae296 100644 --- a/documentation/en/api-v1-unstable-methods.md +++ b/documentation/en/api-v1-unstable-methods.md @@ -37,6 +37,7 @@ * [ChainSetHead](#ChainSetHead) * [ChainStatObj](#ChainStatObj) * [ChainTipSetWeight](#ChainTipSetWeight) + * [ChainValidateIndex](#ChainValidateIndex) * [Create](#Create) * [CreateBackup](#CreateBackup) * [Eth](#Eth) @@ -1240,6 +1241,65 @@ Inputs: Response: `"0"` +### ChainValidateIndex +ChainValidateIndex validates the integrity of and optionally backfills +the chain index at a specific epoch. + +It can be used to: + +1. Validate the chain index at a specific epoch: + - Ensures consistency between indexed data and actual chain state + - Reports any errors found during validation (i.e. the indexed data does not match the actual chain state, missing data, etc.) + +2. Optionally backfill missing data: + - Backfills data if the index is missing information for the specified epoch + - Backfilling only occurs when the `backfill` parameter is set to `true` + +3. Detect "holes" in the index: + - If `backfill` is `false` and the index lacks data for the specified epoch, the API returns an error indicating missing data + +Parameters: + - epoch: The specific chain epoch for which to validate/backfill the index. + - backfill: A boolean flag indicating whether to attempt backfilling of missing data if the index does not have data for the + specified epoch. + +Returns: + - *types.IndexValidation: A pointer to an IndexValidation struct containing the results of the validation/backfill. + - error: An error object if the validation/backfill fails. The error message will contain details about the index + corruption if the call fails because of an incosistency between indexed data and the actual chain state. + Note: The API returns an error if the index does not have data for the specified epoch and backfill is set to false. + + +Perms: write + +Inputs: +```json +[ + 10101, + true +] +``` + +Response: +```json +{ + "TipSetKey": [ + { + "/": "bafy2bzacea3wsdh6y3a36tb3skempjoxqpuyompjbmfeyf34fi3uy6uue42v4" + }, + { + "/": "bafy2bzacebp3shtrn43k7g3unredz7fxn4gj533d3o43tqn2p2ipxxhrvchve" + } + ], + "Height": 10101, + "IndexedMessagesCount": 42, + "IndexedEventsCount": 42, + "IndexedEventEntriesCount": 42, + "Backfilled": true, + "IsNullRound": true +} +``` + ## Create diff --git a/documentation/en/default-lotus-config.toml b/documentation/en/default-lotus-config.toml index 34906677bdc..a821f73a5bd 100644 --- a/documentation/en/default-lotus-config.toml +++ b/documentation/en/default-lotus-config.toml @@ -311,7 +311,7 @@ # The garbage collection (GC) process removes data older than this retention period. # Setting this to 0 disables GC, preserving all historical data indefinitely. # - # If set, the minimum value must be greater than a day's worth (i.e. "2880" epochs for mainnet). + # If set, the minimum value must be greater than builtin.EpochsInDay (i.e. "2880" epochs for mainnet). # This ensures a reasonable retention period for the indexed data. # # Default: 0 (GC disabled) diff --git a/go.mod b/go.mod index 80ac4baa982..e64ff12b415 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,8 @@ replace github.com/filecoin-project/test-vectors => ./extern/test-vectors // pro replace github.com/filecoin-project/filecoin-ffi => ./extern/filecoin-ffi // provided via a git submodule +replace github.com/kilic/bls12-381 v0.1.1-0.20220929213557-ca162e8a70f4 => github.com/kilic/bls12-381 v0.1.0 // to fix bls signature validation + require ( contrib.go.opencensus.io/exporter/prometheus v0.4.2 github.com/BurntSushi/toml v1.3.2 diff --git a/itests/eth_filter_test.go b/itests/eth_filter_test.go index 16991069c9c..5e18e8c0773 100644 --- a/itests/eth_filter_test.go +++ b/itests/eth_filter_test.go @@ -524,6 +524,55 @@ func TestEthGetLogsBasic(t *testing.T) { } AssertEthLogs(t, rctLogs, expected, received) + + head, err := client.ChainHead(ctx) + require.NoError(err) + + for height := 0; height < int(head.Height()); height++ { + // for each tipset + ts, err := client.ChainGetTipSetByHeight(ctx, abi.ChainEpoch(height), types.EmptyTSK) + require.NoError(err) + + if ts.Height() != abi.ChainEpoch(height) { + iv, err := client.ChainValidateIndex(ctx, abi.ChainEpoch(height), false) + require.Nil(iv) + require.NoError(err) + t.Logf("tipset %d is a null round", height) + continue + } + + totalMessageCount := 0 + totalEventCount := 0 + totalEventEntriesCount := 0 + messages, err := client.ChainGetMessagesInTipset(ctx, ts.Key()) + require.NoError(err) + totalMessageCount = len(messages) + for _, m := range messages { + receipt, err := client.StateSearchMsg(ctx, types.EmptyTSK, m.Cid, -1, false) + require.NoError(err) + require.NotNil(receipt) + // receipt + if receipt.Receipt.EventsRoot != nil { + events, err := client.ChainGetEvents(ctx, *receipt.Receipt.EventsRoot) + require.NoError(err) + totalEventCount += len(events) + for _, event := range events { + totalEventEntriesCount += len(event.Entries) + } + } + } + t.Logf("tipset %d: %d messages, %d events", height, totalMessageCount, totalEventCount) + + iv, err := client.ChainValidateIndex(ctx, abi.ChainEpoch(height), false) + require.NoError(err) + require.NotNil(iv) + t.Logf("tipset %d: %+v", height, iv) + require.EqualValues(height, iv.Height) + require.EqualValues(totalMessageCount, iv.IndexedMessagesCount) + require.EqualValues(totalEventCount, iv.IndexedEventsCount) + require.EqualValues(totalEventEntriesCount, iv.IndexedEventEntriesCount) + require.False(iv.Backfilled) + } } func TestEthSubscribeLogsNoTopicSpec(t *testing.T) { diff --git a/lib/sqlite/sqlite.go b/lib/sqlite/sqlite.go index ffb15a7b17e..96d3b5fa9ba 100644 --- a/lib/sqlite/sqlite.go +++ b/lib/sqlite/sqlite.go @@ -23,12 +23,10 @@ var pragmas = []string{ "PRAGMA synchronous = normal", "PRAGMA temp_store = memory", "PRAGMA mmap_size = 30000000000", - "PRAGMA page_size = 32768", "PRAGMA auto_vacuum = NONE", "PRAGMA automatic_index = OFF", "PRAGMA journal_mode = WAL", - "PRAGMA wal_autocheckpoint = 256", // checkpoint @ 256 pages - "PRAGMA journal_size_limit = 0", // always reset journal and wal files + "PRAGMA journal_size_limit = 0", // always reset journal and wal files "PRAGMA foreign_keys = ON", } diff --git a/node/builder_chain.go b/node/builder_chain.go index e2f5b1cd88a..e75750292f2 100644 --- a/node/builder_chain.go +++ b/node/builder_chain.go @@ -289,6 +289,7 @@ func ConfigFullNode(c interface{}) Option { ApplyIf(isFullNode, Override(new(index.Indexer), modules.ChainIndexer(cfg.ChainIndexer)), + Override(new(full.ChainIndexerAPI), modules.ChainIndexHandler(cfg.ChainIndexer)), If(cfg.ChainIndexer.EnableIndexer, Override(InitChainIndexerKey, modules.InitChainIndexer), ), diff --git a/node/impl/full.go b/node/impl/full.go index 6ed0bf3eb11..24240e3df2c 100644 --- a/node/impl/full.go +++ b/node/impl/full.go @@ -36,6 +36,7 @@ type FullNodeAPI struct { full.EthAPI full.ActorEventsAPI full.F3API + full.ChainIndexAPI DS dtypes.MetadataDS NetworkName dtypes.NetworkName diff --git a/node/impl/full/chain_index.go b/node/impl/full/chain_index.go new file mode 100644 index 00000000000..09c7a1ce3d3 --- /dev/null +++ b/node/impl/full/chain_index.go @@ -0,0 +1,46 @@ +package full + +import ( + "context" + "errors" + + "go.uber.org/fx" + + "github.com/filecoin-project/go-state-types/abi" + + "github.com/filecoin-project/lotus/api" + "github.com/filecoin-project/lotus/chain/index" + "github.com/filecoin-project/lotus/chain/types" +) + +type ChainIndexerAPI interface { + ChainValidateIndex(ctx context.Context, epoch abi.ChainEpoch, backfill bool) (*types.IndexValidation, error) +} + +var ( + _ ChainIndexerAPI = *new(api.FullNode) +) + +type ChainIndexAPI struct { + fx.In + ChainIndexerAPI +} + +type ChainIndexHandler struct { + indexer index.Indexer +} + +func (ch *ChainIndexHandler) ChainValidateIndex(ctx context.Context, epoch abi.ChainEpoch, backfill bool) (*types.IndexValidation, error) { + if ch.indexer == nil { + return nil, errors.New("chain indexer is disabled") + } + return ch.indexer.ChainValidateIndex(ctx, epoch, backfill) +} + +var _ ChainIndexerAPI = (*ChainIndexHandler)(nil) + +func NewChainIndexHandler(indexer index.Indexer) *ChainIndexHandler { + return &ChainIndexHandler{ + indexer: indexer, + } +} diff --git a/node/modules/chainindex.go b/node/modules/chainindex.go index f93a488e9ed..624c668c8e8 100644 --- a/node/modules/chainindex.go +++ b/node/modules/chainindex.go @@ -10,6 +10,7 @@ import ( "github.com/filecoin-project/go-address" "github.com/filecoin-project/go-state-types/abi" + "github.com/filecoin-project/lotus/api" "github.com/filecoin-project/lotus/chain/events" "github.com/filecoin-project/lotus/chain/index" "github.com/filecoin-project/lotus/chain/messagepool" @@ -17,6 +18,7 @@ import ( "github.com/filecoin-project/lotus/chain/store" "github.com/filecoin-project/lotus/chain/types" "github.com/filecoin-project/lotus/node/config" + "github.com/filecoin-project/lotus/node/impl/full" "github.com/filecoin-project/lotus/node/modules/helpers" "github.com/filecoin-project/lotus/node/repo" ) @@ -28,14 +30,13 @@ func ChainIndexer(cfg config.ChainIndexerConfig) func(lc fx.Lifecycle, mctx help return nil, nil } - sqlitePath, err := r.SqlitePath() + chainIndexPath, err := r.ChainIndexPath() if err != nil { return nil, err } - // TODO Implement config driven auto-backfilling - chainIndexer, err := index.NewSqliteIndexer(filepath.Join(sqlitePath, index.DefaultDbFilename), - cs, cfg.GCRetentionEpochs, cfg.ReconcileEmptyIndex, cfg.MaxReconcileTipsets) + dbPath := filepath.Join(chainIndexPath, index.DefaultDbFilename) + chainIndexer, err := index.NewSqliteIndexer(dbPath, cs, cfg.GCRetentionEpochs, cfg.ReconcileEmptyIndex, cfg.MaxReconcileTipsets) if err != nil { return nil, err } @@ -70,16 +71,18 @@ func InitChainIndexer(lc fx.Lifecycle, mctx helpers.MetricsCtx, indexer index.In return *actor.DelegatedAddress, true }) - indexer.SetRecomputeTipSetStateFunc(func(ctx context.Context, ts *types.TipSet) error { + executedMessagesLoaderFunc := index.MakeLoadExecutedMessages(func(ctx context.Context, ts *types.TipSet) error { _, _, err := sm.RecomputeTipSetState(ctx, ts) return err }) + indexer.SetExecutedMessagesLoaderFunc(executedMessagesLoaderFunc) + ch, err := mp.Updates(ctx) if err != nil { return err } - go index.WaitForMpoolUpdates(ctx, ch, indexer) + go WaitForMpoolUpdates(ctx, ch, indexer) ev, err := events.NewEvents(ctx, &evapi) if err != nil { @@ -97,14 +100,37 @@ func InitChainIndexer(lc fx.Lifecycle, mctx helpers.MetricsCtx, indexer index.In unlockObserver() return xerrors.Errorf("error while reconciling chain index with chain state: %w", err) } - log.Infof("chain indexer reconciled with chain state; observer will start upates from height: %d", head.Height()) unlockObserver() - if err := indexer.Start(); err != nil { - return err - } + indexer.Start() return nil }, }) } + +func ChainIndexHandler(cfg config.ChainIndexerConfig) func(helpers.MetricsCtx, repo.LockedRepo, fx.Lifecycle, index.Indexer) (*full.ChainIndexHandler, error) { + return func(mctx helpers.MetricsCtx, r repo.LockedRepo, lc fx.Lifecycle, indexer index.Indexer) (*full.ChainIndexHandler, error) { + return full.NewChainIndexHandler(indexer), nil + } +} + +func WaitForMpoolUpdates(ctx context.Context, ch <-chan api.MpoolUpdate, indexer index.Indexer) { + for ctx.Err() == nil { + select { + case <-ctx.Done(): + return + case u := <-ch: + if u.Type != api.MpoolAdd { + continue + } + if u.Message == nil { + continue + } + err := indexer.IndexSignedMessage(ctx, u.Message) + if err != nil { + log.Errorw("failed to index signed Mpool message", "error", err) + } + } + } +} diff --git a/node/repo/fsrepo.go b/node/repo/fsrepo.go index 26cbbd6b135..1c2e9f738d4 100644 --- a/node/repo/fsrepo.go +++ b/node/repo/fsrepo.go @@ -37,7 +37,7 @@ const ( fsDatastore = "datastore" fsLock = "repo.lock" fsKeystore = "keystore" - fsSqlite = "sqlite" + fsChainIndex = "chainindex" ) func NewRepoTypeFromString(t string) RepoType { @@ -376,9 +376,9 @@ type fsLockedRepo struct { ssErr error ssOnce sync.Once - sqlPath string - sqlErr error - sqlOnce sync.Once + chainIndexPath string + chainIndexErr error + chainIndexOnce sync.Once storageLk sync.Mutex configLk sync.Mutex @@ -473,19 +473,19 @@ func (fsr *fsLockedRepo) SplitstorePath() (string, error) { return fsr.ssPath, fsr.ssErr } -func (fsr *fsLockedRepo) SqlitePath() (string, error) { - fsr.sqlOnce.Do(func() { - path := fsr.join(fsSqlite) +func (fsr *fsLockedRepo) ChainIndexPath() (string, error) { + fsr.chainIndexOnce.Do(func() { + path := fsr.join(fsChainIndex) if err := os.MkdirAll(path, 0755); err != nil { - fsr.sqlErr = err + fsr.chainIndexErr = err return } - fsr.sqlPath = path + fsr.chainIndexPath = path }) - return fsr.sqlPath, fsr.sqlErr + return fsr.chainIndexPath, fsr.chainIndexErr } // join joins path elements with fsr.path diff --git a/node/repo/interface.go b/node/repo/interface.go index 11c965bf55c..100d0dc58d5 100644 --- a/node/repo/interface.go +++ b/node/repo/interface.go @@ -69,8 +69,8 @@ type LockedRepo interface { // SplitstorePath returns the path for the SplitStore SplitstorePath() (string, error) - // SqlitePath returns the path for the Sqlite database - SqlitePath() (string, error) + // ChainIndexPath returns the path for the chain index database + ChainIndexPath() (string, error) // Returns config in this repo Config() (interface{}, error) diff --git a/node/repo/memrepo.go b/node/repo/memrepo.go index d1e9b214b4a..cda00f985f2 100644 --- a/node/repo/memrepo.go +++ b/node/repo/memrepo.go @@ -268,12 +268,12 @@ func (lmem *lockedMemRepo) SplitstorePath() (string, error) { return splitstorePath, nil } -func (lmem *lockedMemRepo) SqlitePath() (string, error) { - sqlitePath := filepath.Join(lmem.Path(), "sqlite") - if err := os.MkdirAll(sqlitePath, 0755); err != nil { +func (lmem *lockedMemRepo) ChainIndexPath() (string, error) { + chainIndexPath := filepath.Join(lmem.Path(), "chainindex") + if err := os.MkdirAll(chainIndexPath, 0755); err != nil { return "", err } - return sqlitePath, nil + return chainIndexPath, nil } func (lmem *lockedMemRepo) ListDatastores(ns string) ([]int64, error) {