diff --git a/ecosystem/lava-sdk/bin/src/badge/fetchBadge.js b/ecosystem/lava-sdk/bin/src/badge/fetchBadge.js index 9c1eca4ea7..247ad8d599 100644 --- a/ecosystem/lava-sdk/bin/src/badge/fetchBadge.js +++ b/ecosystem/lava-sdk/bin/src/badge/fetchBadge.js @@ -60,7 +60,7 @@ class BadgeManager { if (code == grpc_web_1.grpc.Code.OK || msg == undefined) { return; } - reject(new Error(msg)); + reject(new Error("Failed fetching a badge from the badge server, message: " + msg)); }, }); }); diff --git a/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.d.ts b/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.d.ts index 08f336fb7d..89405f1b29 100644 --- a/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.d.ts +++ b/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.d.ts @@ -1,5 +1,14 @@ import { ConsumerSessionWithProvider, SessionManager } from "../types/types"; import Relayer from "../relayer/relayer"; +import { Badge } from "../grpc_web_services/pairing/relay_pb"; +import { QueryShowAllChainsResponse } from "../codec/spec/query"; +export interface LavaProvidersOptions { + accountAddress: string; + network: string; + relayer: Relayer | null; + geolocation: string; + debug?: boolean; +} export declare class LavaProviders { private providers; private network; @@ -7,19 +16,24 @@ export declare class LavaProviders { private accountAddress; private relayer; private geolocation; - constructor(accountAddress: string, network: string, relayer: Relayer | null, geolocation: string); + private debugMode; + constructor(options: LavaProvidersOptions); + updateLavaProvidersRelayersBadge(badge: Badge | undefined): void; init(pairingListConfig: string): Promise; + showAllChains(): Promise; initDefaultConfig(): Promise; initLocalConfig(path: string): Promise; GetLavaProviders(): ConsumerSessionWithProvider[]; GetNextLavaProvider(): ConsumerSessionWithProvider; - getSession(chainID: string, rpcInterface: string): Promise; + getSession(chainID: string, rpcInterface: string, badge?: Badge): Promise; + private debugPrint; pickRandomProviders(providers: Array): ConsumerSessionWithProvider[]; pickRandomProvider(providers: Array): ConsumerSessionWithProvider; private getPairingFromChain; private getMaxCuForUser; private getServiceApis; convertRestApiName(name: string): string; + SendRelayToAllProvidersAndRace(options: any, relayCu: number, rpcInterface: string): Promise; SendRelayWithRetry(options: any, lavaRPCEndpoint: ConsumerSessionWithProvider, relayCu: number, rpcInterface: string): Promise; private extractBlockNumberFromError; } diff --git a/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.js b/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.js index 20ea7286ea..d7f7c24354 100644 --- a/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.js +++ b/ecosystem/lava-sdk/bin/src/lavaOverLava/providers.js @@ -15,16 +15,27 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.LavaProviders = void 0; const default_1 = require("../config/default"); const types_1 = require("../types/types"); +const query_1 = require("../codec/pairing/query"); +const query_2 = require("../codec/spec/query"); const lavaPairing_1 = require("../util/lavaPairing"); const errors_1 = __importDefault(require("./errors")); +const common_1 = require("../util/common"); +const query_3 = require("../codec/spec/query"); +const BOOT_RETRY_ATTEMPTS = 2; class LavaProviders { - constructor(accountAddress, network, relayer, geolocation) { + constructor(options) { this.index = 0; this.providers = []; - this.network = network; - this.accountAddress = accountAddress; - this.relayer = relayer; - this.geolocation = geolocation; + this.network = options.network; + this.accountAddress = options.accountAddress; + this.relayer = options.relayer; + this.geolocation = options.geolocation; + this.debugMode = options.debug ? options.debug : false; + } + updateLavaProvidersRelayersBadge(badge) { + if (this.relayer) { + this.relayer.setBadge(badge); + } } init(pairingListConfig) { return __awaiter(this, void 0, void 0, function* () { @@ -56,6 +67,24 @@ class LavaProviders { this.providers = pairing; }); } + showAllChains() { + return __awaiter(this, void 0, void 0, function* () { + const sendRelayOptions = { + data: (0, common_1.generateRPCData)("abci_query", [ + "/lavanet.lava.spec.Query/ShowAllChains", + "", + "0", + false, + ]), + url: "", + connectionType: "", + }; + const info = yield this.SendRelayToAllProvidersAndRace(sendRelayOptions, 10, "tendermintrpc"); + const byteArrayResponse = (0, common_1.base64ToUint8Array)(info.result.response.value); + const response = query_3.QueryShowAllChainsResponse.decode(byteArrayResponse); + return response; + }); + } initDefaultConfig() { return __awaiter(this, void 0, void 0, function* () { // Fetch config from github repo @@ -116,7 +145,7 @@ class LavaProviders { return rpcAddress; } // getSession returns providers for current epoch - getSession(chainID, rpcInterface) { + getSession(chainID, rpcInterface, badge) { return __awaiter(this, void 0, void 0, function* () { let lastRelayResponse = null; if (this.providers == null) { @@ -124,15 +153,15 @@ class LavaProviders { } // Get lava providers list const lavaProviders = this.GetLavaProviders(); - // Iterate over each and try t oreturn pairing list + // Iterate over each and try to return pairing list for (let i = 0; i < lavaProviders.length; i++) { try { // Fetch lava provider which will be used for fetching pairing list const lavaRPCEndpoint = lavaProviders[i]; // Create request for fetching api methods for LAV1 - const lavaApis = yield this.getServiceApis(lavaRPCEndpoint, "LAV1", "rest", new Map([["/lavanet/lava/spec/spec/[^/s]+", 10]])); + const lavaApis = yield this.getServiceApis(lavaRPCEndpoint, { ChainID: "LAV1" }, "grpc", new Map([["lavanet.lava.spec.Query/Spec", 10]])); // Create request for getServiceApis method for chainID - const apis = yield this.getServiceApis(lavaRPCEndpoint, chainID, rpcInterface, lavaApis); + const apis = yield this.getServiceApis(lavaRPCEndpoint, { ChainID: chainID }, rpcInterface, lavaApis); // Create pairing request for getPairing method const pairingRequest = { chainID: chainID, @@ -143,7 +172,7 @@ class LavaProviders { // Set when will next epoch start const nextEpochStart = new Date(); nextEpochStart.setSeconds(nextEpochStart.getSeconds() + - parseInt(pairingResponse.time_left_to_next_pairing)); + parseInt(pairingResponse.timeLeftToNextPairing)); // Extract providers from pairing response const providers = pairingResponse.providers; // Initialize ConsumerSessionWithProvider array @@ -152,10 +181,16 @@ class LavaProviders { const userEntityRequest = { address: this.accountAddress, chainID: chainID, - block: pairingResponse.current_epoch, + block: pairingResponse.currentEpoch, }; // Fetch max compute units - const maxcu = yield this.getMaxCuForUser(lavaRPCEndpoint, userEntityRequest, lavaApis); + let maxcu; + if (badge) { + maxcu = badge.getCuAllocation(); + } + else { + maxcu = yield this.getMaxCuForUser(lavaRPCEndpoint, userEntityRequest, lavaApis); + } // Iterate over providers to populate pairing list for (const provider of providers) { // Skip providers with no endpoints @@ -180,7 +215,7 @@ class LavaProviders { const singleConsumerSession = new types_1.SingleConsumerSession(0, // cuSum 0, // latestRelayCuSum 1, // relayNumber - relevantEndpoints[0], parseInt(pairingResponse.current_epoch), provider.address); + relevantEndpoints[0], parseInt(pairingResponse.currentEpoch), provider.address); // Create a new pairing object const newPairing = new types_1.ConsumerSessionWithProvider(this.accountAddress, relevantEndpoints, singleConsumerSession, maxcu, 0, // used compute units false); @@ -193,14 +228,6 @@ class LavaProviders { } catch (err) { if (err instanceof Error) { - /* - console.log( - "Error during fetching pairing list " + - err.message + - "from provider " + - providers - ); - */ lastRelayResponse = err; } } @@ -208,8 +235,14 @@ class LavaProviders { throw lastRelayResponse; }); } + debugPrint(message, ...optionalParams) { + if (this.debugMode) { + console.log(message, ...optionalParams); + } + } pickRandomProviders(providers) { // Remove providers which does not match criteria + this.debugPrint("pickRandomProviders Providers list", providers); const validProviders = providers.filter((item) => item.MaxComputeUnits > item.UsedComputeUnits); if (validProviders.length === 0) { throw errors_1.default.errNoValidProvidersForCurrentEpoch; @@ -225,6 +258,7 @@ class LavaProviders { return validProviders; } pickRandomProvider(providers) { + this.debugPrint("pickRandomProvider Providers list", providers); // Remove providers which does not match criteria const validProviders = providers.filter((item) => item.MaxComputeUnits > item.UsedComputeUnits); if (validProviders.length === 0) { @@ -236,77 +270,98 @@ class LavaProviders { } getPairingFromChain(lavaRPCEndpoint, request, lavaApis) { return __awaiter(this, void 0, void 0, function* () { - const options = { - connectionType: "GET", - url: "/lavanet/lava/pairing/get_pairing/" + - request.chainID + - "/" + - request.client, - data: "", + const requestData = query_1.QueryGetPairingRequest.encode(request).finish(); + const hexData = Buffer.from(requestData).toString("hex"); + const sendRelayOptions = { + data: (0, common_1.generateRPCData)("abci_query", [ + "/lavanet.lava.pairing.Query/GetPairing", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - const relayCu = lavaApis.get("/lavanet/lava/pairing/user_entry/[^/s]+/[^/s]+"); + const relayCu = lavaApis.get("lavanet.lava.pairing.Query/GetPairing"); if (relayCu == undefined) { throw errors_1.default.errApiNotFound; } - const jsonResponse = yield this.SendRelayWithRetry(options, lavaRPCEndpoint, relayCu, "rest"); - if (jsonResponse.providers == undefined) { + const jsonResponse = yield this.SendRelayWithRetry(sendRelayOptions, lavaRPCEndpoint, relayCu, "tendermintrpc"); + const byteArrayResponse = (0, common_1.base64ToUint8Array)(jsonResponse.result.response.value); + const decodedResponse = query_1.QueryGetPairingResponse.decode(byteArrayResponse); + if (decodedResponse.providers == undefined) { throw errors_1.default.errProvidersNotFound; } - return jsonResponse; + return decodedResponse; }); } getMaxCuForUser(lavaRPCEndpoint, request, lavaApis) { return __awaiter(this, void 0, void 0, function* () { - const options = { - connectionType: "GET", - url: "/lavanet/lava/pairing/user_entry/" + - request.address + - "/" + - request.chainID, - data: "?block=" + request.block, + const requestData = query_1.QueryUserEntryRequest.encode(request).finish(); + const hexData = Buffer.from(requestData).toString("hex"); + const sendRelayOptions = { + data: (0, common_1.generateRPCData)("abci_query", [ + "/lavanet.lava.pairing.Query/UserEntry", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - const relayCu = lavaApis.get("/lavanet/lava/pairing/user_entry/[^/s]+/[^/s]+"); + const relayCu = lavaApis.get("lavanet.lava.pairing.Query/UserEntry"); if (relayCu == undefined) { throw errors_1.default.errApiNotFound; } - const jsonResponse = yield this.SendRelayWithRetry(options, lavaRPCEndpoint, relayCu, "rest"); - if (jsonResponse.maxCU == undefined) { + const jsonResponse = yield this.SendRelayWithRetry(sendRelayOptions, lavaRPCEndpoint, relayCu, "tendermintrpc"); + const byteArrayResponse = (0, common_1.base64ToUint8Array)(jsonResponse.result.response.value); + const response = query_1.QueryUserEntryResponse.decode(byteArrayResponse); + if (response.maxCU == undefined) { throw errors_1.default.errMaxCuNotFound; } // return maxCu from userEntry - return parseInt(jsonResponse.maxCU); + return response.maxCU.low; }); } - getServiceApis(lavaRPCEndpoint, chainID, rpcInterface, lavaApis) { + getServiceApis(lavaRPCEndpoint, request, rpcInterface, lavaApis) { return __awaiter(this, void 0, void 0, function* () { - const options = { - connectionType: "GET", - url: "/lavanet/lava/spec/spec/" + chainID, - data: "", + const requestData = query_2.QueryGetSpecRequest.encode(request).finish(); + const hexData = Buffer.from(requestData).toString("hex"); + const sendRelayOptions = { + data: (0, common_1.generateRPCData)("abci_query", [ + "/lavanet.lava.spec.Query/Spec", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - const relayCu = lavaApis.get("/lavanet/lava/spec/spec/[^/s]+"); + const relayCu = lavaApis.get("lavanet.lava.spec.Query/Spec"); if (relayCu == undefined) { throw errors_1.default.errApiNotFound; } - const jsonResponse = yield this.SendRelayWithRetry(options, lavaRPCEndpoint, relayCu, "rest"); - if (jsonResponse.Spec == undefined) { + const jsonResponse = yield this.SendRelayWithRetry(sendRelayOptions, lavaRPCEndpoint, relayCu, "tendermintrpc"); + const byteArrayResponse = (0, common_1.base64ToUint8Array)(jsonResponse.result.response.value); + const response = query_2.QueryGetSpecResponse.decode(byteArrayResponse); + if (response.Spec == undefined) { throw errors_1.default.errSpecNotFound; } const apis = new Map(); // Extract apis from response - for (const element of jsonResponse.Spec.apis) { - for (const apiInterface of element.api_interfaces) { + for (const element of response.Spec.apis) { + for (const apiInterface of element.apiInterfaces) { // Skip if interface which does not match if (apiInterface.interface != rpcInterface) continue; if (apiInterface.interface == "rest") { // handle REST apis const name = this.convertRestApiName(element.name); - apis.set(name, parseInt(element.compute_units)); + apis.set(name, element.computeUnits.low); } else { // Handle RPC apis - apis.set(element.name, parseInt(element.compute_units)); + apis.set(element.name, element.computeUnits.low); } } } @@ -317,13 +372,46 @@ class LavaProviders { const regex = /\{\s*[^}]+\s*\}/g; return name.replace(regex, "[^/s]+"); } + SendRelayToAllProvidersAndRace(options, relayCu, rpcInterface) { + return __awaiter(this, void 0, void 0, function* () { + for (let retryAttempt = 0; retryAttempt < BOOT_RETRY_ATTEMPTS; retryAttempt++) { + const allRelays = new Map(); + let response; + for (const provider of this.GetLavaProviders()) { + const uniqueKey = provider.Session.ProviderAddress + + String(Math.floor(Math.random() * 10000000)); + const providerRelayPromise = this.SendRelayWithRetry(options, provider, relayCu, rpcInterface) + .then((result) => { + this.debugPrint("response succeeded", result); + response = result; + allRelays.delete(uniqueKey); + }) + .catch((err) => { + this.debugPrint("one of the promises failed in SendRelayToAllProvidersAndRace reason:", err, uniqueKey); + allRelays.delete(uniqueKey); + }); + allRelays.set(uniqueKey, providerRelayPromise); + } + const promisesToWait = allRelays.size; + for (let i = 0; i < promisesToWait; i++) { + const returnedPromise = yield Promise.race(allRelays); + yield returnedPromise[1]; + if (response) { + return response; + } + } + this.debugPrint("Failed all promises SendRelayToAllProvidersAndRace, trying again", retryAttempt, "out of", BOOT_RETRY_ATTEMPTS); + } + throw new Error("Failed all promises SendRelayToAllProvidersAndRace"); + }); + } SendRelayWithRetry(options, lavaRPCEndpoint, relayCu, rpcInterface) { return __awaiter(this, void 0, void 0, function* () { let response; + if (this.relayer == null) { + throw errors_1.default.errNoRelayer; + } try { - if (this.relayer == null) { - throw errors_1.default.errNoRelayer; - } // For now we have hardcode relay cu response = yield this.relayer.sendRelay(options, lavaRPCEndpoint, relayCu, rpcInterface); } @@ -339,10 +427,6 @@ class LavaProviders { } // Save current block height lavaRPCEndpoint.Session.PairingEpoch = parseInt(currentBlockHeight); - // Validate that relayer exists - if (this.relayer == null) { - throw errors_1.default.errNoRelayer; - } // Retry same relay with added block height try { response = yield this.relayer.sendRelay(options, lavaRPCEndpoint, relayCu, rpcInterface); diff --git a/ecosystem/lava-sdk/bin/src/lavaOverLava/test.js b/ecosystem/lava-sdk/bin/src/lavaOverLava/test.js index 4ff7bbdeeb..fddecbb77f 100644 --- a/ecosystem/lava-sdk/bin/src/lavaOverLava/test.js +++ b/ecosystem/lava-sdk/bin/src/lavaOverLava/test.js @@ -26,7 +26,13 @@ it("Test convertRestApiName method", () => { output: "/lavanet/lava/pairing/verify_pairing/[^/s]+/[^/s]+/[^/s]+/[^/s]+", }, ]; - const lavaProviders = new providers_1.LavaProviders("", "", null, default_1.DEFAULT_GEOLOCATION); + const options = { + accountAddress: "", + network: "", + relayer: null, + geolocation: default_1.DEFAULT_GEOLOCATION, + }; + const lavaProviders = new providers_1.LavaProviders(options); testCasses.map((test) => { expect(lavaProviders.convertRestApiName(test.name)).toBe(test.output); }); @@ -49,7 +55,13 @@ it("Test pickRandomProvider method", () => { shouldFail: true, }, ]; - const lavaProviders = new providers_1.LavaProviders("", "", null, default_1.DEFAULT_GEOLOCATION); + const options = { + accountAddress: "", + network: "", + relayer: null, + geolocation: default_1.DEFAULT_GEOLOCATION, + }; + const lavaProviders = new providers_1.LavaProviders(options); testCasses.map((test) => { const consumerSessionWithProviderArr = [ // default consumer session with provider with only compute units set diff --git a/ecosystem/lava-sdk/bin/src/relayer/relayer.d.ts b/ecosystem/lava-sdk/bin/src/relayer/relayer.d.ts index 15fd9ed7d1..cece8a09da 100644 --- a/ecosystem/lava-sdk/bin/src/relayer/relayer.d.ts +++ b/ecosystem/lava-sdk/bin/src/relayer/relayer.d.ts @@ -8,6 +8,7 @@ declare class Relayer { private prefix; private badge?; constructor(chainID: string, privKey: string, lavaChainId: string, secure: boolean, badge?: Badge); + setBadge(badge: Badge | undefined): void; sendRelay(options: SendRelayOptions, consumerProviderSession: ConsumerSessionWithProvider, cuSum: number, apiInterface: string): Promise; extractErrorMessage(error: string): string; relayWithTimeout(timeLimit: number, task: any): Promise; diff --git a/ecosystem/lava-sdk/bin/src/relayer/relayer.js b/ecosystem/lava-sdk/bin/src/relayer/relayer.js index 7bde17faa1..57be73f55d 100644 --- a/ecosystem/lava-sdk/bin/src/relayer/relayer.js +++ b/ecosystem/lava-sdk/bin/src/relayer/relayer.js @@ -54,6 +54,14 @@ class Relayer { } this.badge = badge; } + // when an epoch changes we need to update the badge + setBadge(badge) { + if (this.badge && !badge) { + // we have a badge and trying to set it to undefined + throw new Error("Trying to set an undefined badge to an existing badge, bad flow"); + } + this.badge = badge; + } sendRelay(options, consumerProviderSession, cuSum, apiInterface) { return __awaiter(this, void 0, void 0, function* () { // Extract attributes from options @@ -129,7 +137,7 @@ class Relayer { }, }); }); - return this.relayWithTimeout(2000, requestPromise); + return this.relayWithTimeout(5000, requestPromise); }); } extractErrorMessage(error) { diff --git a/ecosystem/lava-sdk/bin/src/sdk/sdk.d.ts b/ecosystem/lava-sdk/bin/src/sdk/sdk.d.ts index 6ccc85ab85..43c4cae142 100644 --- a/ecosystem/lava-sdk/bin/src/sdk/sdk.d.ts +++ b/ecosystem/lava-sdk/bin/src/sdk/sdk.d.ts @@ -27,9 +27,11 @@ export interface LavaSDKOptions { geolocation?: string; lavaChainId?: string; secure?: boolean; + debug?: boolean; } export declare class LavaSDK { private privKey; + private walletAddress; private chainID; private rpcInterface; private network; @@ -37,10 +39,12 @@ export declare class LavaSDK { private geolocation; private lavaChainId; private badgeManager; + private currentEpochBadge; private lavaProviders; private account; private relayer; private secure; + private debugMode; private activeSessionManager; /** * Create Lava-SDK instance @@ -54,6 +58,9 @@ export declare class LavaSDK { */ constructor(options: LavaSDKOptions); static create(options: LavaSDKOptions): Promise; + private debugPrint; + private fetchNewBadge; + private initLavaProviders; private init; private handleRpcRelay; private handleRestRelay; @@ -68,11 +75,9 @@ export declare class LavaSDK { * */ sendRelay(options: SendRelayOptions | SendRestRelayOptions): Promise; - private generateRPCData; private decodeRelayResponse; private getCuSumForMethod; private getConsumerProviderSession; private newEpochStarted; private isRest; - private base64ToUint8Array; } diff --git a/ecosystem/lava-sdk/bin/src/sdk/sdk.js b/ecosystem/lava-sdk/bin/src/sdk/sdk.js index ee13e6246e..209f0f59ec 100644 --- a/ecosystem/lava-sdk/bin/src/sdk/sdk.js +++ b/ecosystem/lava-sdk/bin/src/sdk/sdk.js @@ -18,9 +18,9 @@ const errors_1 = __importDefault(require("./errors")); const relayer_1 = __importDefault(require("../relayer/relayer")); const fetchBadge_1 = require("../badge/fetchBadge"); const chains_1 = require("../util/chains"); +const common_1 = require("../util/common"); const providers_1 = require("../lavaOverLava/providers"); const default_1 = require("../config/default"); -const query_1 = require("../codec/spec/query"); class LavaSDK { /** * Create Lava-SDK instance @@ -33,10 +33,6 @@ class LavaSDK { * @returns A promise that resolves when the LavaSDK has been successfully initialized, returns LavaSDK object. */ constructor(options) { - this.base64ToUint8Array = (str) => { - const buffer = Buffer.from(str, "base64"); - return new Uint8Array(buffer); - }; // Extract attributes from options const { privateKey, badge, chainID, rpcInterface } = options; let { pairingListConfig, network, geolocation, lavaChainId } = options; @@ -59,6 +55,7 @@ class LavaSDK { this.chainID = chainID; this.rpcInterface = rpcInterface ? rpcInterface : ""; this.privKey = privateKey ? privateKey : ""; + this.walletAddress = ""; this.badgeManager = new fetchBadge_1.BadgeManager(badge); this.network = network; this.geolocation = geolocation; @@ -68,6 +65,7 @@ class LavaSDK { this.relayer = errors_1.default.errRelayerServiceNotInitialized; this.lavaProviders = errors_1.default.errLavaProvidersNotInitialized; this.activeSessionManager = errors_1.default.errSessionNotInitialized; + this.debugMode = options.debug ? options.debug : false; // enabling debug prints mainly used for development / debugging // Init sdk return (() => __awaiter(this, void 0, void 0, function* () { yield this.init(); @@ -83,64 +81,80 @@ class LavaSDK { return yield new LavaSDK(options); }); } - init() { + debugPrint(message, ...optionalParams) { + if (this.debugMode) { + console.log(message, ...optionalParams); + } + } + fetchNewBadge() { return __awaiter(this, void 0, void 0, function* () { - let wallet; - let badge; - if (this.badgeManager.isActive()) { - const { wallet, privKey } = yield (0, wallet_1.createDynamicWallet)(); - this.privKey = privKey; - const walletAddress = (yield wallet.getConsumerAccount()).address; - const badgeResponse = yield this.badgeManager.fetchBadge(walletAddress); - if (badgeResponse instanceof Error) { - throw fetchBadge_1.TimoutFailureFetchingBadgeError; - } - badge = badgeResponse.getBadge(); - const badgeSignerAddress = badgeResponse.getBadgeSignerAddress(); - this.account = { - algo: "secp256k1", - address: badgeSignerAddress, - pubkey: new Uint8Array([]), - }; + const badgeResponse = yield this.badgeManager.fetchBadge(this.walletAddress); + if (badgeResponse instanceof Error) { + throw fetchBadge_1.TimoutFailureFetchingBadgeError; } - else { - wallet = yield (0, wallet_1.createWallet)(this.privKey); - this.account = yield wallet.getConsumerAccount(); + return badgeResponse; + }); + } + initLavaProviders(start) { + return __awaiter(this, void 0, void 0, function* () { + if (this.account instanceof Error) { + throw new Error("initLavaProviders failed: " + String(this.account)); } - // Init relayer for lava providers - const lavaRelayer = new relayer_1.default(default_1.LAVA_CHAIN_ID, this.privKey, this.lavaChainId, this.secure, badge); // Create new instance of lava providers - const lavaProviders = yield new providers_1.LavaProviders(this.account.address, this.network, lavaRelayer, this.geolocation); + this.lavaProviders = yield new providers_1.LavaProviders({ + accountAddress: this.account.address, + network: this.network, + relayer: new relayer_1.default(default_1.LAVA_CHAIN_ID, this.privKey, this.lavaChainId, this.secure, this.currentEpochBadge), + geolocation: this.geolocation, + debug: this.debugMode, + }); + this.debugPrint("time took lava providers", performance.now() - start); // Init lava providers - yield lavaProviders.init(this.pairingListConfig); - const sendRelayOptions = { - data: this.generateRPCData("abci_query", [ - "/lavanet.lava.spec.Query/ShowAllChains", - "", - "0", - false, - ]), - url: "", - connectionType: "", - }; - const info = yield lavaProviders.SendRelayWithRetry(sendRelayOptions, lavaProviders.GetNextLavaProvider(), 10, "tendermintrpc"); - const byteArrayResponse = this.base64ToUint8Array(info.result.response.value); - const parsedChainList = query_1.QueryShowAllChainsResponse.decode(byteArrayResponse); + yield this.lavaProviders.init(this.pairingListConfig); + this.debugPrint("time took lava providers init", performance.now() - start); + const parsedChainList = yield this.lavaProviders.showAllChains(); // Validate chainID if (!(0, chains_1.isValidChainID)(this.chainID, parsedChainList)) { throw errors_1.default.errChainIDUnsupported; } + this.debugPrint("time took ShowAllChains", performance.now() - start); // If rpc is not defined use default for specified chainID this.rpcInterface = this.rpcInterface || (0, chains_1.fetchRpcInterface)(this.chainID, parsedChainList); + this.debugPrint("time took fetchRpcInterface", performance.now() - start); // Validate rpc interface with chain id (0, chains_1.validateRpcInterfaceWithChainID)(this.chainID, parsedChainList, this.rpcInterface); - // Save lava providers as local attribute - this.lavaProviders = lavaProviders; + this.debugPrint("time took validateRpcInterfaceWithChainID", performance.now() - start); // Get pairing list for current epoch - this.activeSessionManager = yield this.lavaProviders.getSession(this.chainID, this.rpcInterface); + this.activeSessionManager = yield this.lavaProviders.getSession(this.chainID, this.rpcInterface, this.currentEpochBadge); + this.debugPrint("time took getSession", performance.now() - start); + }); + } + init() { + return __awaiter(this, void 0, void 0, function* () { + const start = performance.now(); + if (this.badgeManager.isActive()) { + const { wallet, privKey } = yield (0, wallet_1.createDynamicWallet)(); + this.privKey = privKey; + this.walletAddress = (yield wallet.getConsumerAccount()).address; + const badgeResponse = yield this.fetchNewBadge(); + this.currentEpochBadge = badgeResponse.getBadge(); + const badgeSignerAddress = badgeResponse.getBadgeSignerAddress(); + this.account = { + algo: "secp256k1", + address: badgeSignerAddress, + pubkey: new Uint8Array([]), + }; + this.debugPrint("time took to get badge from badge server", performance.now() - start); + // this.debugPrint("badge", badge); + } + else { + const wallet = yield (0, wallet_1.createWallet)(this.privKey); + this.account = yield wallet.getConsumerAccount(); + } // Create relayer for querying network - this.relayer = new relayer_1.default(this.chainID, this.privKey, this.lavaChainId, this.secure, badge); + this.relayer = new relayer_1.default(this.chainID, this.privKey, this.lavaChainId, this.secure, this.currentEpochBadge); + yield this.initLavaProviders(start); }); } handleRpcRelay(options) { @@ -155,7 +169,7 @@ class LavaSDK { const pairingList = yield this.getConsumerProviderSession(); // Get cuSum for specified method const cuSum = this.getCuSumForMethod(method); - const data = this.generateRPCData(method, params); + const data = (0, common_1.generateRPCData)(method, params); // Check if relay was initialized if (this.relayer instanceof Error) { throw errors_1.default.errRelayerServiceNotInitialized; @@ -253,21 +267,6 @@ class LavaSDK { return yield this.handleRpcRelay(options); }); } - generateRPCData(method, params) { - const stringifyMethod = JSON.stringify(method); - const stringifyParam = JSON.stringify(params, (key, value) => { - if (typeof value === "bigint") { - return value.toString(); - } - return value; - }); - // TODO make id changable - return ('{"jsonrpc": "2.0", "id": 1, "method": ' + - stringifyMethod + - ', "params": ' + - stringifyParam + - "}"); - } decodeRelayResponse(relayResponse) { // Decode relay response const dec = new TextDecoder(); @@ -303,7 +302,16 @@ class LavaSDK { } // Check if new epoch has started if (this.newEpochStarted()) { - this.activeSessionManager = yield this.lavaProviders.getSession(this.chainID, this.rpcInterface); + // fetch a new badge: + if (this.badgeManager.isActive()) { + const badgeResponse = yield this.fetchNewBadge(); + this.currentEpochBadge = badgeResponse.getBadge(); + if (this.relayer instanceof relayer_1.default) { + this.relayer.setBadge(this.currentEpochBadge); + } + this.lavaProviders.updateLavaProvidersRelayersBadge(this.currentEpochBadge); + } + this.activeSessionManager = yield this.lavaProviders.getSession(this.chainID, this.rpcInterface, this.currentEpochBadge); } // Return randomized pairing list return this.lavaProviders.pickRandomProviders(this.activeSessionManager.PairingList); diff --git a/ecosystem/lava-sdk/bin/src/types/types.d.ts b/ecosystem/lava-sdk/bin/src/types/types.d.ts index 3f566724b4..420cc1e377 100644 --- a/ecosystem/lava-sdk/bin/src/types/types.d.ts +++ b/ecosystem/lava-sdk/bin/src/types/types.d.ts @@ -6,7 +6,7 @@ export declare class SessionManager { getCuSumFromApi(name: string, chainID: string): number | undefined; } export declare class ConsumerSessionWithProvider { - Acc: string; + ConsumerAddress: string; Endpoints: Array; Session: SingleConsumerSession; MaxComputeUnits: number; diff --git a/ecosystem/lava-sdk/bin/src/types/types.js b/ecosystem/lava-sdk/bin/src/types/types.js index 9253650734..8ea903d79a 100644 --- a/ecosystem/lava-sdk/bin/src/types/types.js +++ b/ecosystem/lava-sdk/bin/src/types/types.js @@ -21,7 +21,7 @@ class SessionManager { exports.SessionManager = SessionManager; class ConsumerSessionWithProvider { constructor(acc, endpoints, session, maxComputeUnits, usedComputeUnits, reliabilitySent) { - this.Acc = acc; + this.ConsumerAddress = acc; this.Endpoints = endpoints; this.Session = session; this.MaxComputeUnits = maxComputeUnits; diff --git a/ecosystem/lava-sdk/bin/src/util/common.d.ts b/ecosystem/lava-sdk/bin/src/util/common.d.ts new file mode 100644 index 0000000000..7037b87dad --- /dev/null +++ b/ecosystem/lava-sdk/bin/src/util/common.d.ts @@ -0,0 +1,2 @@ +export declare function base64ToUint8Array(str: string): Uint8Array; +export declare function generateRPCData(method: string, params: Array): string; diff --git a/ecosystem/lava-sdk/bin/src/util/common.js b/ecosystem/lava-sdk/bin/src/util/common.js new file mode 100644 index 0000000000..29a1e4157a --- /dev/null +++ b/ecosystem/lava-sdk/bin/src/util/common.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateRPCData = exports.base64ToUint8Array = void 0; +function base64ToUint8Array(str) { + const buffer = Buffer.from(str, "base64"); + return new Uint8Array(buffer); +} +exports.base64ToUint8Array = base64ToUint8Array; +function generateRPCData(method, params) { + const stringifyMethod = JSON.stringify(method); + const stringifyParam = JSON.stringify(params, (key, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }); + // TODO make id changable + return ('{"jsonrpc": "2.0", "id": 1, "method": ' + + stringifyMethod + + ', "params": ' + + stringifyParam + + "}"); +} +exports.generateRPCData = generateRPCData; diff --git a/ecosystem/lava-sdk/package.json b/ecosystem/lava-sdk/package.json index 983fa4000f..5668abe0f5 100644 --- a/ecosystem/lava-sdk/package.json +++ b/ecosystem/lava-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@lavanet/lava-sdk", - "version": "0.13.1", + "version": "0.16.1", "description": "An SDK for interacting with Lava provider", "main": "./bin/src/sdk/sdk.js", "author": "Lava Protocol Inc", @@ -16,9 +16,12 @@ "run_rest_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/restAPI.js", "run_jsonrpc_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/jsonRPC.js", "run_jsonrpc_badge_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/jsonRPC_badge_test.js", + "run_test_tendermint_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/tendermintRPC_test.js", + "node_test_tendermint_example": "node ./bin/examples/tendermintRPC_test.js", "run_test_rest_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/restAPI_test.js", "run_test_jsonrpc_example": "yarn tsc && webpack --config webpack.config.js && node ./bin/examples/jsonRPC_test.js", + "node_test_jsonrpc_example": "node ./bin/examples/jsonRPC_test.js", "jsonRPC": "node ./bin/examples/jsonRPC.js", "restAPI": "node ./bin/examples/restAPI.js", "prettier": "prettier --check .", diff --git a/ecosystem/lava-sdk/src/badge/fetchBadge.ts b/ecosystem/lava-sdk/src/badge/fetchBadge.ts index 37da498545..c0d5e1ec61 100644 --- a/ecosystem/lava-sdk/src/badge/fetchBadge.ts +++ b/ecosystem/lava-sdk/src/badge/fetchBadge.ts @@ -67,7 +67,11 @@ export class BadgeManager { if (code == grpc.Code.OK || msg == undefined) { return; } - reject(new Error(msg)); + reject( + new Error( + "Failed fetching a badge from the badge server, message: " + msg + ) + ); }, }); } diff --git a/ecosystem/lava-sdk/src/lavaOverLava/providers.ts b/ecosystem/lava-sdk/src/lavaOverLava/providers.ts index 6016339023..04bd41bc66 100644 --- a/ecosystem/lava-sdk/src/lavaOverLava/providers.ts +++ b/ecosystem/lava-sdk/src/lavaOverLava/providers.ts @@ -7,11 +7,26 @@ import { } from "../types/types"; import { QueryGetPairingRequest, + QueryGetPairingResponse, QueryUserEntryRequest, + QueryUserEntryResponse, } from "../codec/pairing/query"; +import { QueryGetSpecRequest, QueryGetSpecResponse } from "../codec/spec/query"; import { fetchLavaPairing } from "../util/lavaPairing"; import Relayer from "../relayer/relayer"; import ProvidersErrors from "./errors"; +import { base64ToUint8Array, generateRPCData } from "../util/common"; +import { Badge } from "../grpc_web_services/pairing/relay_pb"; +import { QueryShowAllChainsResponse } from "../codec/spec/query"; + +const BOOT_RETRY_ATTEMPTS = 2; +export interface LavaProvidersOptions { + accountAddress: string; + network: string; + relayer: Relayer | null; + geolocation: string; + debug?: boolean; +} export class LavaProviders { private providers: ConsumerSessionWithProvider[]; @@ -20,18 +35,21 @@ export class LavaProviders { private accountAddress: string; private relayer: Relayer | null; private geolocation: string; + private debugMode: boolean; - constructor( - accountAddress: string, - network: string, - relayer: Relayer | null, - geolocation: string - ) { + constructor(options: LavaProvidersOptions) { this.providers = []; - this.network = network; - this.accountAddress = accountAddress; - this.relayer = relayer; - this.geolocation = geolocation; + this.network = options.network; + this.accountAddress = options.accountAddress; + this.relayer = options.relayer; + this.geolocation = options.geolocation; + this.debugMode = options.debug ? options.debug : false; + } + + public updateLavaProvidersRelayersBadge(badge: Badge | undefined) { + if (this.relayer) { + this.relayer.setBadge(badge); + } } async init(pairingListConfig: string) { @@ -70,11 +88,32 @@ export class LavaProviders { // Add newly created pairing in the pairing list pairing.push(newPairing); } - // Save providers as local attribute this.providers = pairing; } + public async showAllChains(): Promise { + const sendRelayOptions = { + data: generateRPCData("abci_query", [ + "/lavanet.lava.spec.Query/ShowAllChains", + "", + "0", + false, + ]), + url: "", + connectionType: "", + }; + + const info = await this.SendRelayToAllProvidersAndRace( + sendRelayOptions, + 10, + "tendermintrpc" + ); + const byteArrayResponse = base64ToUint8Array(info.result.response.value); + const response = QueryShowAllChainsResponse.decode(byteArrayResponse); + return response; + } + async initDefaultConfig(): Promise { // Fetch config from github repo const response = await fetch(DEFAULT_LAVA_PAIRING_LIST); @@ -162,7 +201,8 @@ export class LavaProviders { // getSession returns providers for current epoch async getSession( chainID: string, - rpcInterface: string + rpcInterface: string, + badge?: Badge ): Promise { let lastRelayResponse = null; if (this.providers == null) { @@ -172,7 +212,7 @@ export class LavaProviders { // Get lava providers list const lavaProviders = this.GetLavaProviders(); - // Iterate over each and try t oreturn pairing list + // Iterate over each and try to return pairing list for (let i = 0; i < lavaProviders.length; i++) { try { // Fetch lava provider which will be used for fetching pairing list @@ -181,15 +221,15 @@ export class LavaProviders { // Create request for fetching api methods for LAV1 const lavaApis = await this.getServiceApis( lavaRPCEndpoint, - "LAV1", - "rest", - new Map([["/lavanet/lava/spec/spec/[^/s]+", 10]]) + { ChainID: "LAV1" }, + "grpc", + new Map([["lavanet.lava.spec.Query/Spec", 10]]) ); // Create request for getServiceApis method for chainID const apis = await this.getServiceApis( lavaRPCEndpoint, - chainID, + { ChainID: chainID }, rpcInterface, lavaApis ); @@ -211,7 +251,7 @@ export class LavaProviders { const nextEpochStart = new Date(); nextEpochStart.setSeconds( nextEpochStart.getSeconds() + - parseInt(pairingResponse.time_left_to_next_pairing) + parseInt(pairingResponse.timeLeftToNextPairing) ); // Extract providers from pairing response @@ -224,15 +264,20 @@ export class LavaProviders { const userEntityRequest = { address: this.accountAddress, chainID: chainID, - block: pairingResponse.current_epoch, + block: pairingResponse.currentEpoch, }; // Fetch max compute units - const maxcu = await this.getMaxCuForUser( - lavaRPCEndpoint, - userEntityRequest, - lavaApis - ); + let maxcu: number; + if (badge) { + maxcu = badge.getCuAllocation(); + } else { + maxcu = await this.getMaxCuForUser( + lavaRPCEndpoint, + userEntityRequest, + lavaApis + ); + } // Iterate over providers to populate pairing list for (const provider of providers) { @@ -266,7 +311,7 @@ export class LavaProviders { 0, // latestRelayCuSum 1, // relayNumber relevantEndpoints[0], - parseInt(pairingResponse.current_epoch), + parseInt(pairingResponse.currentEpoch), provider.address ); @@ -294,14 +339,6 @@ export class LavaProviders { return sessionManager; } catch (err) { if (err instanceof Error) { - /* - console.log( - "Error during fetching pairing list " + - err.message + - "from provider " + - providers - ); - */ lastRelayResponse = err; } } @@ -310,10 +347,17 @@ export class LavaProviders { throw lastRelayResponse; } + private debugPrint(message?: any, ...optionalParams: any[]) { + if (this.debugMode) { + console.log(message, ...optionalParams); + } + } + pickRandomProviders( providers: Array ): ConsumerSessionWithProvider[] { // Remove providers which does not match criteria + this.debugPrint("pickRandomProviders Providers list", providers); const validProviders = providers.filter( (item) => item.MaxComputeUnits > item.UsedComputeUnits ); @@ -337,6 +381,7 @@ export class LavaProviders { pickRandomProvider( providers: Array ): ConsumerSessionWithProvider { + this.debugPrint("pickRandomProvider Providers list", providers); // Remove providers which does not match criteria const validProviders = providers.filter( (item) => item.MaxComputeUnits > item.UsedComputeUnits @@ -357,36 +402,42 @@ export class LavaProviders { request: QueryGetPairingRequest, lavaApis: Map ): Promise { - const options = { - connectionType: "GET", - url: - "/lavanet/lava/pairing/get_pairing/" + - request.chainID + - "/" + - request.client, - data: "", + const requestData = QueryGetPairingRequest.encode(request).finish(); + + const hexData = Buffer.from(requestData).toString("hex"); + + const sendRelayOptions = { + data: generateRPCData("abci_query", [ + "/lavanet.lava.pairing.Query/GetPairing", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - - const relayCu = lavaApis.get( - "/lavanet/lava/pairing/user_entry/[^/s]+/[^/s]+" - ); + const relayCu = lavaApis.get("lavanet.lava.pairing.Query/GetPairing"); if (relayCu == undefined) { throw ProvidersErrors.errApiNotFound; } const jsonResponse = await this.SendRelayWithRetry( - options, + sendRelayOptions, lavaRPCEndpoint, relayCu, - "rest" + "tendermintrpc" + ); + const byteArrayResponse = base64ToUint8Array( + jsonResponse.result.response.value ); + const decodedResponse = QueryGetPairingResponse.decode(byteArrayResponse); - if (jsonResponse.providers == undefined) { + if (decodedResponse.providers == undefined) { throw ProvidersErrors.errProvidersNotFound; } - return jsonResponse; + return decodedResponse; } private async getMaxCuForUser( @@ -394,82 +445,101 @@ export class LavaProviders { request: QueryUserEntryRequest, lavaApis: Map ): Promise { - const options = { - connectionType: "GET", - url: - "/lavanet/lava/pairing/user_entry/" + - request.address + - "/" + - request.chainID, - - data: "?block=" + request.block, + const requestData = QueryUserEntryRequest.encode(request).finish(); + + const hexData = Buffer.from(requestData).toString("hex"); + + const sendRelayOptions = { + data: generateRPCData("abci_query", [ + "/lavanet.lava.pairing.Query/UserEntry", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - - const relayCu = lavaApis.get( - "/lavanet/lava/pairing/user_entry/[^/s]+/[^/s]+" - ); + const relayCu = lavaApis.get("lavanet.lava.pairing.Query/UserEntry"); if (relayCu == undefined) { throw ProvidersErrors.errApiNotFound; } const jsonResponse = await this.SendRelayWithRetry( - options, + sendRelayOptions, lavaRPCEndpoint, relayCu, - "rest" + "tendermintrpc" + ); + const byteArrayResponse = base64ToUint8Array( + jsonResponse.result.response.value ); + const response = QueryUserEntryResponse.decode(byteArrayResponse); - if (jsonResponse.maxCU == undefined) { + if (response.maxCU == undefined) { throw ProvidersErrors.errMaxCuNotFound; } // return maxCu from userEntry - return parseInt(jsonResponse.maxCU); + return response.maxCU.low; } private async getServiceApis( lavaRPCEndpoint: ConsumerSessionWithProvider, - chainID: string, + request: QueryGetSpecRequest, rpcInterface: string, lavaApis: Map ): Promise> { - const options = { - connectionType: "GET", - url: "/lavanet/lava/spec/spec/" + chainID, - data: "", + const requestData = QueryGetSpecRequest.encode(request).finish(); + + const hexData = Buffer.from(requestData).toString("hex"); + + const sendRelayOptions = { + data: generateRPCData("abci_query", [ + "/lavanet.lava.spec.Query/Spec", + hexData, + "0", + false, + ]), + url: "", + connectionType: "", }; - const relayCu = lavaApis.get("/lavanet/lava/spec/spec/[^/s]+"); + const relayCu = lavaApis.get("lavanet.lava.spec.Query/Spec"); + if (relayCu == undefined) { throw ProvidersErrors.errApiNotFound; } const jsonResponse = await this.SendRelayWithRetry( - options, + sendRelayOptions, lavaRPCEndpoint, relayCu, - "rest" + "tendermintrpc" + ); + const byteArrayResponse = base64ToUint8Array( + jsonResponse.result.response.value ); + const response = QueryGetSpecResponse.decode(byteArrayResponse); - if (jsonResponse.Spec == undefined) { + if (response.Spec == undefined) { throw ProvidersErrors.errSpecNotFound; } const apis = new Map(); // Extract apis from response - for (const element of jsonResponse.Spec.apis) { - for (const apiInterface of element.api_interfaces) { + for (const element of response.Spec.apis) { + for (const apiInterface of element.apiInterfaces) { // Skip if interface which does not match if (apiInterface.interface != rpcInterface) continue; if (apiInterface.interface == "rest") { // handle REST apis const name = this.convertRestApiName(element.name); - apis.set(name, parseInt(element.compute_units)); + apis.set(name, element.computeUnits.low); } else { // Handle RPC apis - apis.set(element.name, parseInt(element.compute_units)); + apis.set(element.name, element.computeUnits.low); } } } @@ -481,6 +551,61 @@ export class LavaProviders { return name.replace(regex, "[^/s]+"); } + async SendRelayToAllProvidersAndRace( + options: any, + relayCu: number, + rpcInterface: string + ): Promise { + for ( + let retryAttempt = 0; + retryAttempt < BOOT_RETRY_ATTEMPTS; + retryAttempt++ + ) { + const allRelays: Map> = new Map(); + let response; + for (const provider of this.GetLavaProviders()) { + const uniqueKey = + provider.Session.ProviderAddress + + String(Math.floor(Math.random() * 10000000)); + const providerRelayPromise = this.SendRelayWithRetry( + options, + provider, + relayCu, + rpcInterface + ) + .then((result) => { + this.debugPrint("response succeeded", result); + response = result; + allRelays.delete(uniqueKey); + }) + .catch((err: any) => { + this.debugPrint( + "one of the promises failed in SendRelayToAllProvidersAndRace reason:", + err, + uniqueKey + ); + allRelays.delete(uniqueKey); + }); + allRelays.set(uniqueKey, providerRelayPromise); + } + const promisesToWait = allRelays.size; + for (let i = 0; i < promisesToWait; i++) { + const returnedPromise = await Promise.race(allRelays); + await returnedPromise[1]; + if (response) { + return response; + } + } + this.debugPrint( + "Failed all promises SendRelayToAllProvidersAndRace, trying again", + retryAttempt, + "out of", + BOOT_RETRY_ATTEMPTS + ); + } + throw new Error("Failed all promises SendRelayToAllProvidersAndRace"); + } + async SendRelayWithRetry( options: any, lavaRPCEndpoint: ConsumerSessionWithProvider, @@ -488,11 +613,10 @@ export class LavaProviders { rpcInterface: string ): Promise { let response; + if (this.relayer == null) { + throw ProvidersErrors.errNoRelayer; + } try { - if (this.relayer == null) { - throw ProvidersErrors.errNoRelayer; - } - // For now we have hardcode relay cu response = await this.relayer.sendRelay( options, @@ -515,10 +639,6 @@ export class LavaProviders { // Save current block height lavaRPCEndpoint.Session.PairingEpoch = parseInt(currentBlockHeight); - // Validate that relayer exists - if (this.relayer == null) { - throw ProvidersErrors.errNoRelayer; - } // Retry same relay with added block height try { response = await this.relayer.sendRelay( diff --git a/ecosystem/lava-sdk/src/lavaOverLava/test.ts b/ecosystem/lava-sdk/src/lavaOverLava/test.ts index 6351f822fa..c2397f2417 100644 --- a/ecosystem/lava-sdk/src/lavaOverLava/test.ts +++ b/ecosystem/lava-sdk/src/lavaOverLava/test.ts @@ -32,7 +32,13 @@ it("Test convertRestApiName method", () => { }, ]; - const lavaProviders = new LavaProviders("", "", null, DEFAULT_GEOLOCATION); + const options = { + accountAddress: "", + network: "", + relayer: null, + geolocation: DEFAULT_GEOLOCATION, + }; + const lavaProviders = new LavaProviders(options); testCasses.map((test) => { expect(lavaProviders.convertRestApiName(test.name)).toBe(test.output); @@ -62,7 +68,13 @@ it("Test pickRandomProvider method", () => { }, ]; - const lavaProviders = new LavaProviders("", "", null, DEFAULT_GEOLOCATION); + const options = { + accountAddress: "", + network: "", + relayer: null, + geolocation: DEFAULT_GEOLOCATION, + }; + const lavaProviders = new LavaProviders(options); testCasses.map((test) => { const consumerSessionWithProviderArr = [ diff --git a/ecosystem/lava-sdk/src/relayer/relayer.ts b/ecosystem/lava-sdk/src/relayer/relayer.ts index 382ec74397..416098bafb 100644 --- a/ecosystem/lava-sdk/src/relayer/relayer.ts +++ b/ecosystem/lava-sdk/src/relayer/relayer.ts @@ -35,6 +35,17 @@ class Relayer { this.badge = badge; } + // when an epoch changes we need to update the badge + public setBadge(badge: Badge | undefined) { + if (this.badge && !badge) { + // we have a badge and trying to set it to undefined + throw new Error( + "Trying to set an undefined badge to an existing badge, bad flow" + ); + } + this.badge = badge; + } + async sendRelay( options: SendRelayOptions, consumerProviderSession: ConsumerSessionWithProvider, @@ -127,7 +138,7 @@ class Relayer { }); }); - return this.relayWithTimeout(2000, requestPromise); + return this.relayWithTimeout(5000, requestPromise); } extractErrorMessage(error: string) { diff --git a/ecosystem/lava-sdk/src/sdk/sdk.ts b/ecosystem/lava-sdk/src/sdk/sdk.ts index c7d8fd4d1b..e839ba0327 100644 --- a/ecosystem/lava-sdk/src/sdk/sdk.ts +++ b/ecosystem/lava-sdk/src/sdk/sdk.ts @@ -19,15 +19,16 @@ import { fetchRpcInterface, validateRpcInterfaceWithChainID, } from "../util/chains"; +import { base64ToUint8Array, generateRPCData } from "../util/common"; import { LavaProviders } from "../lavaOverLava/providers"; import { - LAVA_CHAIN_ID, + LAVA_CHAIN_ID as LAVA_SPEC_ID, DEFAULT_LAVA_PAIRING_NETWORK, DEFAULT_GEOLOCATION, DEFAULT_LAVA_CHAINID, } from "../config/default"; import { QueryShowAllChainsResponse } from "../codec/spec/query"; - +import { GenerateBadgeResponse } from "../grpc_web_services/pairing/badges_pb"; /** * Options for sending RPC relay. */ @@ -59,10 +60,12 @@ export interface LavaSDKOptions { geolocation?: string; // Optional: The geolocation to be used ["1" for North America, "2" for Europe ] lavaChainId?: string; // Optional: The Lava chain ID (default value for Lava Testnet) secure?: boolean; // Optional: communicates through https, this is a temporary flag that will be disabled once the chain will use https by default + debug?: boolean; // Optional for debugging the LavaSDK mostly prints to speed up development } export class LavaSDK { private privKey: string; + private walletAddress: string; private chainID: string; private rpcInterface: string; private network: string; @@ -70,11 +73,13 @@ export class LavaSDK { private geolocation: string; private lavaChainId: string; private badgeManager: BadgeManager; + private currentEpochBadge: Badge | undefined; // The current badge is the badge for the current epoch private lavaProviders: LavaProviders | Error; private account: AccountData | Error; private relayer: Relayer | Error; private secure: boolean; + private debugMode: boolean; private activeSessionManager: SessionManager | Error; @@ -116,6 +121,7 @@ export class LavaSDK { this.chainID = chainID; this.rpcInterface = rpcInterface ? rpcInterface : ""; this.privKey = privateKey ? privateKey : ""; + this.walletAddress = ""; this.badgeManager = new BadgeManager(badge); this.network = network; this.geolocation = geolocation; @@ -125,6 +131,7 @@ export class LavaSDK { this.relayer = SDKErrors.errRelayerServiceNotInitialized; this.lavaProviders = SDKErrors.errLavaProvidersNotInitialized; this.activeSessionManager = SDKErrors.errSessionNotInitialized; + this.debugMode = options.debug ? options.debug : false; // enabling debug prints mainly used for development / debugging // Init sdk return (async (): Promise => { @@ -142,82 +149,59 @@ export class LavaSDK { return await new LavaSDK(options); } - private async init() { - let wallet: LavaWallet; - let badge: Badge | undefined; - if (this.badgeManager.isActive()) { - const { wallet, privKey } = await createDynamicWallet(); - this.privKey = privKey; - const walletAddress = (await wallet.getConsumerAccount()).address; - const badgeResponse = await this.badgeManager.fetchBadge(walletAddress); - if (badgeResponse instanceof Error) { - throw TimeoutFailureFetchingBadgeError; - } - badge = badgeResponse.getBadge(); - const badgeSignerAddress = badgeResponse.getBadgeSignerAddress(); - this.account = { - algo: "secp256k1", - address: badgeSignerAddress, - pubkey: new Uint8Array([]), - }; - } else { - wallet = await createWallet(this.privKey); - this.account = await wallet.getConsumerAccount(); + private debugPrint(message?: any, ...optionalParams: any[]) { + if (this.debugMode) { + console.log(message, ...optionalParams); } + } - // Init relayer for lava providers - const lavaRelayer = new Relayer( - LAVA_CHAIN_ID, - this.privKey, - this.lavaChainId, - this.secure, - badge + private async fetchNewBadge(): Promise { + const badgeResponse = await this.badgeManager.fetchBadge( + this.walletAddress ); + if (badgeResponse instanceof Error) { + throw TimeoutFailureFetchingBadgeError; + } + return badgeResponse; + } + private async initLavaProviders(start: number) { + if (this.account instanceof Error) { + throw new Error("initLavaProviders failed: " + String(this.account)); + } // Create new instance of lava providers - const lavaProviders = await new LavaProviders( - this.account.address, - this.network, - lavaRelayer, - this.geolocation - ); + this.lavaProviders = await new LavaProviders({ + accountAddress: this.account.address, + network: this.network, + relayer: new Relayer( + LAVA_SPEC_ID, + this.privKey, + this.lavaChainId, + this.secure, + this.currentEpochBadge + ), + geolocation: this.geolocation, + debug: this.debugMode, + }); + this.debugPrint("time took lava providers", performance.now() - start); // Init lava providers - await lavaProviders.init(this.pairingListConfig); - - const sendRelayOptions = { - data: this.generateRPCData("abci_query", [ - "/lavanet.lava.spec.Query/ShowAllChains", - "", - "0", - false, - ]), - url: "", - connectionType: "", - }; - - const info = await lavaProviders.SendRelayWithRetry( - sendRelayOptions, - lavaProviders.GetNextLavaProvider(), - 10, - "tendermintrpc" - ); + await this.lavaProviders.init(this.pairingListConfig); + this.debugPrint("time took lava providers init", performance.now() - start); - const byteArrayResponse = this.base64ToUint8Array( - info.result.response.value - ); - - const parsedChainList = - QueryShowAllChainsResponse.decode(byteArrayResponse); + const parsedChainList: QueryShowAllChainsResponse = + await this.lavaProviders.showAllChains(); // Validate chainID if (!isValidChainID(this.chainID, parsedChainList)) { throw SDKErrors.errChainIDUnsupported; } + this.debugPrint("time took ShowAllChains", performance.now() - start); // If rpc is not defined use default for specified chainID this.rpcInterface = this.rpcInterface || fetchRpcInterface(this.chainID, parsedChainList); + this.debugPrint("time took fetchRpcInterface", performance.now() - start); // Validate rpc interface with chain id validateRpcInterfaceWithChainID( @@ -225,24 +209,55 @@ export class LavaSDK { parsedChainList, this.rpcInterface ); - - // Save lava providers as local attribute - this.lavaProviders = lavaProviders; + this.debugPrint( + "time took validateRpcInterfaceWithChainID", + performance.now() - start + ); // Get pairing list for current epoch this.activeSessionManager = await this.lavaProviders.getSession( this.chainID, - this.rpcInterface + this.rpcInterface, + this.currentEpochBadge ); + this.debugPrint("time took getSession", performance.now() - start); + } + + private async init() { + const start = performance.now(); + if (this.badgeManager.isActive()) { + const { wallet, privKey } = await createDynamicWallet(); + this.privKey = privKey; + this.walletAddress = (await wallet.getConsumerAccount()).address; + const badgeResponse = await this.fetchNewBadge(); + this.currentEpochBadge = badgeResponse.getBadge(); + const badgeSignerAddress = badgeResponse.getBadgeSignerAddress(); + this.account = { + algo: "secp256k1", + address: badgeSignerAddress, + pubkey: new Uint8Array([]), + }; + this.debugPrint( + "time took to get badge from badge server", + performance.now() - start + ); + // this.debugPrint("badge", badge); + } else { + const wallet = await createWallet(this.privKey); + this.account = await wallet.getConsumerAccount(); + } + // Create relayer for querying network this.relayer = new Relayer( this.chainID, this.privKey, this.lavaChainId, this.secure, - badge + this.currentEpochBadge ); + + await this.initLavaProviders(start); } private async handleRpcRelay(options: SendRelayOptions): Promise { @@ -259,7 +274,7 @@ export class LavaSDK { // Get cuSum for specified method const cuSum = this.getCuSumForMethod(method); - const data = this.generateRPCData(method, params); + const data = generateRPCData(method, params); // Check if relay was initialized if (this.relayer instanceof Error) { @@ -384,24 +399,6 @@ export class LavaSDK { return await this.handleRpcRelay(options); } - private generateRPCData(method: string, params: Array): string { - const stringifyMethod = JSON.stringify(method); - const stringifyParam = JSON.stringify(params, (key, value) => { - if (typeof value === "bigint") { - return value.toString(); - } - return value; - }); - // TODO make id changable - return ( - '{"jsonrpc": "2.0", "id": 1, "method": ' + - stringifyMethod + - ', "params": ' + - stringifyParam + - "}" - ); - } - private decodeRelayResponse(relayResponse: RelayReply): string { // Decode relay response const dec = new TextDecoder(); @@ -449,9 +446,21 @@ export class LavaSDK { // Check if new epoch has started if (this.newEpochStarted()) { + // fetch a new badge: + if (this.badgeManager.isActive()) { + const badgeResponse = await this.fetchNewBadge(); + this.currentEpochBadge = badgeResponse.getBadge(); + if (this.relayer instanceof Relayer) { + this.relayer.setBadge(this.currentEpochBadge); + } + this.lavaProviders.updateLavaProvidersRelayersBadge( + this.currentEpochBadge + ); + } this.activeSessionManager = await this.lavaProviders.getSession( this.chainID, - this.rpcInterface + this.rpcInterface, + this.currentEpochBadge ); } @@ -479,10 +488,4 @@ export class LavaSDK { ): options is SendRestRelayOptions { return (options as SendRestRelayOptions).url !== undefined; } - - private base64ToUint8Array = (str: string): Uint8Array => { - const buffer = Buffer.from(str, "base64"); - - return new Uint8Array(buffer); - }; } diff --git a/ecosystem/lava-sdk/src/types/types.ts b/ecosystem/lava-sdk/src/types/types.ts index e05d36ffa2..1939077c5d 100644 --- a/ecosystem/lava-sdk/src/types/types.ts +++ b/ecosystem/lava-sdk/src/types/types.ts @@ -26,7 +26,7 @@ export class SessionManager { } export class ConsumerSessionWithProvider { - Acc: string; + ConsumerAddress: string; Endpoints: Array; Session: SingleConsumerSession; MaxComputeUnits: number; @@ -41,7 +41,7 @@ export class ConsumerSessionWithProvider { usedComputeUnits: number, reliabilitySent: boolean ) { - this.Acc = acc; + this.ConsumerAddress = acc; this.Endpoints = endpoints; this.Session = session; this.MaxComputeUnits = maxComputeUnits; diff --git a/ecosystem/lava-sdk/src/util/common.ts b/ecosystem/lava-sdk/src/util/common.ts new file mode 100644 index 0000000000..6c1f6e25a2 --- /dev/null +++ b/ecosystem/lava-sdk/src/util/common.ts @@ -0,0 +1,23 @@ +export function base64ToUint8Array(str: string): Uint8Array { + const buffer = Buffer.from(str, "base64"); + + return new Uint8Array(buffer); +} + +export function generateRPCData(method: string, params: Array): string { + const stringifyMethod = JSON.stringify(method); + const stringifyParam = JSON.stringify(params, (key, value) => { + if (typeof value === "bigint") { + return value.toString(); + } + return value; + }); + // TODO make id changable + return ( + '{"jsonrpc": "2.0", "id": 1, "method": ' + + stringifyMethod + + ', "params": ' + + stringifyParam + + "}" + ); +}