diff --git a/packages/cli/package/src/lib/chain/conversions.ts b/packages/cli/package/src/lib/chain/conversions.ts
index 8f043f130..46247b8c7 100644
--- a/packages/cli/package/src/lib/chain/conversions.ts
+++ b/packages/cli/package/src/lib/chain/conversions.ts
@@ -15,7 +15,9 @@
* along with this program. If not, see .
*/
-import { bufferToBase64 } from "../helpers/typesafeStringify.js";
+import type { ResourceType } from "../configs/project/provider/provider4.js";
+import { stringifyUnknown } from "../helpers/stringifyUnknown.js";
+import { bufferToBase64, numToStr } from "../helpers/typesafeStringify.js";
const PREFIX = new Uint8Array([0, 36, 8, 1, 18, 32]);
const BASE_58_PREFIX = "z";
@@ -77,3 +79,40 @@ export function hexStringToUTF8ToBase64String(hexString: string): string {
return bufferToBase64(Buffer.from(cleanHexString, "utf-8"));
}
+
+export async function resourceSupply(
+ resourceType: ResourceType,
+ supply: number,
+) {
+ const xbytes = (await import("xbytes")).default;
+
+ switch (resourceType) {
+ case "cpu":
+ return { supply, supplyString: numToStr(supply) };
+ case "ram":
+ return {
+ supply: Math.round(supply / (await getRamToBytesFromChain())),
+ supplyString: xbytes(supply),
+ };
+ case "storage":
+ return {
+ supply: Math.round(supply / STORAGE_TO_BYTES),
+ supplyString: xbytes(supply),
+ };
+ case "bandwidth":
+ return { supply, supplyString: numToStr(supply) };
+ case "ip":
+ return { supply, supplyString: numToStr(supply) };
+ default:
+ const unknownResourceType: never = resourceType;
+ throw new Error(
+ `Unknown resource type ${stringifyUnknown(unknownResourceType)}`,
+ );
+ }
+}
+
+const STORAGE_TO_BYTES = 1_000_000_000;
+
+async function getRamToBytesFromChain() {
+ return Promise.resolve(1_000_000_000);
+}
diff --git a/packages/cli/package/src/lib/chain/offer/offer.ts b/packages/cli/package/src/lib/chain/offer/offer.ts
index 099f3dd8a..78171d45e 100644
--- a/packages/cli/package/src/lib/chain/offer/offer.ts
+++ b/packages/cli/package/src/lib/chain/offer/offer.ts
@@ -158,7 +158,7 @@ export async function createOffers(flags: OffersArgs) {
return [
contracts.deployment.usdc,
resourcePricesArray,
- computePeersToRegister,
+ setCPUSupplyForCP(computePeersToRegister),
];
},
getTitle() {
@@ -265,7 +265,7 @@ export async function createOffers(flags: OffersArgs) {
let offersInfo: GetOffersInfoReturnType[1] = [];
const getOffersInfoRes = await setTryTimeout(
- "Getting offers info from indexer",
+ "get offers info from indexer",
async () => {
[offerInfoErrors, offersInfo] = await getOffersInfo(createdOffers);
@@ -299,6 +299,53 @@ ${await offersInfoToString([offerInfoErrors, offersInfo])}
`);
}
+function setCPUSupplyForCP(
+ computePeersToRegister: {
+ peerId: Uint8Array;
+ unitIds: Uint8Array[];
+ resourcesByType: {
+ readonly cpu: {
+ readonly resourceId: `0x${string}`;
+ readonly details: string;
+ readonly name: string;
+ readonly supply: number;
+ };
+ };
+ owner: string;
+ }[],
+) {
+ return computePeersToRegister.map(
+ ({
+ peerId,
+ unitIds,
+ resourcesByType: { cpu, ...restResources },
+ owner,
+ }) => {
+ return {
+ peerId,
+ unitIds,
+ resources: [
+ setCPUSupply(cpu, unitIds),
+ ...resourcesByTypeToArr(restResources),
+ ],
+ owner,
+ };
+ },
+ );
+}
+
+function setCPUSupply(
+ cpu: {
+ readonly resourceId: `0x${string}`;
+ readonly details: string;
+ readonly name: string;
+ readonly supply: number;
+ },
+ unitIds: Uint8Array[],
+): OnChainResource {
+ return { ...cpu, supply: unitIds.length * 2 };
+}
+
async function confirmOffer(offer: EnsureOfferConfig) {
return confirm({
message: `The following offer will be created: ${color.yellow(offer.offerName)}\n${yamlDiffPatch(
@@ -361,7 +408,7 @@ function formatOfferResource({
details,
}: {
resourceId: `0x${string}`;
- supply: number;
+ supply: number | string;
details: string;
}) {
return {
@@ -400,7 +447,7 @@ export async function addRemainingCPs({
sliceValuesToRegister: sliceCPsByNumberOfCUs(remainingCPs),
sliceIndex: CUsToRegisterCount,
getArgs(CPsToRegister) {
- return [offerId, CPsToRegister];
+ return [offerId, setCPUSupplyForCP(CPsToRegister)];
},
getTitle({ valuesToRegister: CPsToRegister }) {
return `Add compute peers:\n${CPsToRegister.map(
@@ -462,7 +509,11 @@ async function addRemainingCUs({
},
sliceIndex: CUs.length,
getArgs(CUsToRegister) {
- return [peerId, CUsToRegister, resourcesByType.cpu];
+ return [
+ peerId,
+ CUsToRegister,
+ setCPUSupply(resourcesByType.cpu, CUsToRegister),
+ ];
},
getTitle({ sliceCount: numberOfCUsForAddCU }) {
return `Add ${numToStr(numberOfCUsForAddCU)} compute units\nto compute peer ${cpName} (${peerIdBase58})\nfor offer ${offerName} (${offerId})`;
@@ -723,11 +774,6 @@ export async function resolveOffersFromProviderConfig(
return new Uint8Array(Buffer.from(id.slice(2), "hex"));
}),
owner: offerIndexerInfo.providerId,
- resources: Object.values(resourcesByTypeWithName).flatMap(
- (resource) => {
- return Array.isArray(resource) ? resource : [resource];
- },
- ),
resourcesByType: resourcesByTypeWithName,
};
},
@@ -882,6 +928,8 @@ async function ensureOfferConfigs() {
"Unreachable. Config is validated so it must return result",
);
+ const xbytes = (await import("xbytes")).default;
+
const resources = {
cpu: {
...cpu,
@@ -890,6 +938,7 @@ async function ensureOfferConfigs() {
},
ram: {
...ram,
+ supply: xbytes.parseSize(ram.supply),
resourceId: `0x${ramId}`,
details: JSON.stringify(ram.details),
},
@@ -897,6 +946,7 @@ async function ensureOfferConfigs() {
({ id: storageId, ...storage }) => {
return {
...storage,
+ supply: xbytes.parseSize(storage.supply),
resourceId: `0x${storageId}`,
details: JSON.stringify(storage.details),
};
@@ -925,9 +975,6 @@ async function ensureOfferConfigs() {
unitIds: times(resourcesWithIds.cpu.supply).map(() => {
return randomBytes(32);
}),
- resources: Object.values(resources).flatMap((resource) => {
- return Array.isArray(resource) ? resource : [resource];
- }),
resourcesByType: resources,
owner: walletAddress,
};
@@ -964,6 +1011,16 @@ async function ensureOfferConfigs() {
);
}
+function resourcesByTypeToArr(
+ resourcesByType: Partial<
+ Record
+ >,
+) {
+ return Object.values(resourcesByType).flatMap((resource) => {
+ return Array.isArray(resource) ? resource : [resource];
+ });
+}
+
type CPFromProviderConfig = Awaited<
ReturnType
>[number]["computePeersFromProviderConfig"][number];
diff --git a/packages/cli/package/src/lib/chain/offer/updateOffers.ts b/packages/cli/package/src/lib/chain/offer/updateOffers.ts
index 7a70d4366..054493981 100644
--- a/packages/cli/package/src/lib/chain/offer/updateOffers.ts
+++ b/packages/cli/package/src/lib/chain/offer/updateOffers.ts
@@ -19,6 +19,7 @@ import { color } from "@oclif/color";
import omit from "lodash-es/omit.js";
import { commandObj } from "../../commandObj.js";
+import type { ResourceType } from "../../configs/project/provider/provider4.js";
import { initNewProviderArtifactsConfig } from "../../configs/project/providerArtifacts/providerArtifacts.js";
import {
CLI_NAME,
@@ -29,7 +30,10 @@ import { numToStr } from "../../helpers/typesafeStringify.js";
import { splitErrorsAndResults } from "../../helpers/utils.js";
import { confirm } from "../../prompt.js";
import { ensureFluenceEnv } from "../../resolveFluenceEnv.js";
-import { peerIdHexStringToBase58String } from "../conversions.js";
+import {
+ peerIdHexStringToBase58String,
+ resourceSupply,
+} from "../conversions.js";
import { ptFormat, ptFormatWithSymbol } from "../currencies.js";
import { assertProviderIsRegistered } from "../providerInfo.js";
@@ -449,30 +453,40 @@ type ResourceInfo = {
};
type ResourceSupplyUpdate = {
- name: string;
+ resourceType: ResourceType;
onChainResource: ResourceInfo;
configuredResource: ResourceInfo;
};
async function createResourceSupplyUpdateTx(
peerId: string,
- { name, onChainResource, configuredResource }: ResourceSupplyUpdate,
+ { resourceType, onChainResource, configuredResource }: ResourceSupplyUpdate,
) {
- if (onChainResource.supply === configuredResource.supply) {
+ if (onChainResource.supply === configuredResource.supply * 2) {
return null;
}
const { contracts } = await getContracts();
+ const { supplyString: onChainSupplyString } = await resourceSupply(
+ resourceType,
+ configuredResource.supply,
+ );
+
+ const { supply, supplyString } = await resourceSupply(
+ resourceType,
+ configuredResource.supply,
+ );
+
return {
- description: `\nChanging ${name} supply from ${numToStr(
- onChainResource.supply,
- )} to ${numToStr(configuredResource.supply)}`,
+ description: `\nChanging ${resourceType} supply from ${
+ onChainSupplyString
+ } to ${supplyString}`,
tx: populateTx(
contracts.diamond.changeResourceMaxSupplyV2,
peerId,
onChainResource.resourceId,
- configuredResource.supply,
+ supply * 2,
),
};
}
@@ -496,12 +510,12 @@ async function populateChangeResourceSupplyTx({
const resourcePriceUpdates: ResourceSupplyUpdate[] = [
{
- name: "CPU",
+ resourceType: "cpu",
onChainResource: peer.resourcesByType.cpu,
configuredResource: configuredPeer.resourcesByType.cpu,
},
{
- name: "RAM",
+ resourceType: "ram",
onChainResource: peer.resourcesByType.ram,
configuredResource: configuredPeer.resourcesByType.ram,
},
@@ -516,19 +530,19 @@ async function populateChangeResourceSupplyTx({
}
return {
- name: "storage",
+ resourceType: "storage",
onChainResource: onChainStorage,
configuredResource: configuredStorage,
- };
+ } as const;
})
.filter(Boolean),
{
- name: "IP",
+ resourceType: "ip",
onChainResource: peer.resourcesByType.ip,
configuredResource: configuredPeer.resourcesByType.ip,
},
{
- name: "bandwidth",
+ resourceType: "bandwidth",
onChainResource: peer.resourcesByType.bandwidth,
configuredResource: configuredPeer.resourcesByType.bandwidth,
},
diff --git a/packages/cli/package/src/lib/configs/project/provider/provider4.ts b/packages/cli/package/src/lib/configs/project/provider/provider4.ts
index df13b1b3a..8b5d0899a 100644
--- a/packages/cli/package/src/lib/configs/project/provider/provider4.ts
+++ b/packages/cli/package/src/lib/configs/project/provider/provider4.ts
@@ -321,7 +321,8 @@ const peerCPUSchema = {
required: peerResourceSchema.required,
} as const satisfies JSONSchemaType;
-type PeerRAM = PeerResource & {
+type PeerRAM = Omit & {
+ supply: string;
details?: PeerRamDetails;
};
@@ -335,15 +336,15 @@ const peerRAMSchema = {
description: `${OVERRIDE_OR_EXTEND_DEDSCRIPTION}${peerRamDetailsSchema.description}`,
},
supply: {
- type: "integer",
- minimum: 1,
- description: "Amount of RAM in GB",
+ type: "string",
+ description: "Amount of RAM",
},
},
required: peerResourceSchema.required,
} as const satisfies JSONSchemaType;
-type PeerStorage = PeerResource & {
+type PeerStorage = Omit & {
+ supply: string;
details?: PeerStorageDetails;
};
@@ -357,8 +358,7 @@ const peerStorageSchema = {
description: `${OVERRIDE_OR_EXTEND_DEDSCRIPTION}${peerStorageDetailsSchema.description}`,
},
supply: {
- type: "integer",
- minimum: 1,
+ type: "string",
description: "Amount of storage in GB",
},
},
@@ -602,7 +602,7 @@ export default {
},
validate(config) {
return validateBatchAsync(
- // TODO: validate 4 GB RAM per 1 CPU core
+ validateEnoughRAMPerCPUCore(config),
validateCC(config),
validateNoDuplicatePeerNamesInOffers(config),
validateProtocolVersions(config),
@@ -765,13 +765,13 @@ export function defaultComputePeerConfig({
},
ram: {
name: RAM_RESOURCE_NAME,
- supply: 1,
+ supply: "11 GB",
details: DEFAULT_RAM_DETAILS,
},
storage: [
{
name: STORAGE_RESOURCE_NAME,
- supply: 1,
+ supply: "11 GB",
details: DEFAULT_STORAGE_DETAILS,
},
],
@@ -1088,6 +1088,35 @@ export const onChainResourceTypeToResourceType: Record<
[OnChainResourceType.PUBLIC_IP]: "ip",
};
+const BYTES_PER_CORE = 4_000_000_000;
+
+async function validateEnoughRAMPerCPUCore({
+ computePeers,
+}: {
+ computePeers: ComputePeers;
+}) {
+ const xbytes = (await import("xbytes")).default;
+
+ const errors = Object.entries(computePeers).reduce(
+ (acc, [peerName, { resources }]) => {
+ const cpu = resources.cpu.supply;
+ const ram = xbytes.parseSize(resources.ram.supply);
+ const expectedRam = cpu * BYTES_PER_CORE;
+
+ if (expectedRam > ram) {
+ acc.push(
+ `Compute peer ${color.yellow(peerName)} has not enough RAM per CPU core. Expected: ${xbytes(expectedRam)}. Got: ${xbytes(ram)}`,
+ );
+ }
+
+ return acc;
+ },
+ [],
+ );
+
+ return errors.length === 0 ? true : errors.join("\n");
+}
+
function validateComputePeerIPs({
computePeers,
}: {
@@ -1529,68 +1558,6 @@ type ChainResources = {
ip: Record;
};
-// const resourcesMock = [
-// {
-// ty: OnChainResourceType.VCPU,
-// metadata:
-// '{"manufacturer":"Intel","brand":"Xeon","architecture":"x86_64","generation":"Skylake"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC1",
-// },
-// {
-// ty: OnChainResourceType.VCPU,
-// metadata:
-// '{"manufacturer":"AMD","brand":"EPYC","architecture":"x86_64","generation":"Rome"}',
-// id: "0x211122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC1",
-// },
-// {
-// ty: OnChainResourceType.RAM,
-// metadata: '{"type":"DDR4","generation":"4"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC2",
-// },
-// {
-// ty: OnChainResourceType.RAM,
-// metadata: '{"type":"DDR4","generation":"5"}',
-// id: "0x211122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC2",
-// },
-// {
-// ty: OnChainResourceType.STORAGE,
-// metadata: '{"type":"SSD"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC3",
-// },
-// {
-// ty: OnChainResourceType.STORAGE,
-// metadata: '{"type":"HDD"}',
-// id: "0x211122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC3",
-// },
-// {
-// ty: OnChainResourceType.NETWORK_BANDWIDTH,
-// metadata: '{"type":"shared"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC4",
-// },
-// {
-// ty: OnChainResourceType.PUBLIC_IP,
-// metadata: '{"version":"IPv4"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC5",
-// },
-// {
-// ty: OnChainResourceType.GPU,
-// metadata: '{"manufacturer":"Nvidia","model":"RTX 3090"}',
-// id: "0x111122223333444455556666777788889999AAAABBBBCCCCDDDDEEEEFFFFCCC6",
-// },
-// ] as const satisfies {
-// ty: OnChainResourceType;
-// metadata: string;
-// id: string;
-// }[];
-
-// async function getResources() {
-// return new Promise((res) => {
-// setTimeout(() => {
-// res(resourcesMock);
-// }, 1000);
-// });
-// }
-
let resourcesPromise: undefined | Promise = undefined;
async function getResourcesFromChain(): Promise {
@@ -1602,10 +1569,8 @@ async function getResourcesFromChain(): Promise {
}
async function getResourcesFromChainImpl(): Promise {
- // TODO: get resources from chain
const { readonlyContracts } = await getReadonlyContracts();
const resources = await readonlyContracts.diamond.getResources();
- // const resources = await getResources();
const chainResources: ChainResources = {
cpu: {},
diff --git a/packages/cli/package/src/lib/dealClient.ts b/packages/cli/package/src/lib/dealClient.ts
index 3081186cd..30c28950a 100644
--- a/packages/cli/package/src/lib/dealClient.ts
+++ b/packages/cli/package/src/lib/dealClient.ts
@@ -706,6 +706,13 @@ export async function guessTxSizeAndSign<
valuesToRegister = sliceValuesToRegister(sliceIndex);
try {
+ dbg(
+ `\nESTIMATING GAS:\n\n${methodCallToString([
+ signArgs.method,
+ ...(await getArgs(valuesToRegister)),
+ ])}\n\nESTIMATING GAS END\n`,
+ );
+
const populatedTx = await populateTx(
signArgs.method,
...(await getArgs(valuesToRegister)),
diff --git a/packages/cli/package/yarn.lock b/packages/cli/package/yarn.lock
index d15bd4888..13fde0c2c 100644
--- a/packages/cli/package/yarn.lock
+++ b/packages/cli/package/yarn.lock
@@ -1964,7 +1964,7 @@ __metadata:
dependencies:
"@actions/core": "npm:1.11.1"
"@aws-sdk/lib-storage": "npm:^3.501.0"
- "@fluencelabs/deal-ts-clients": "npm:0.23.2-feat-marketplace-v2-resources-6fb9f61-7385-1.0"
+ "@fluencelabs/deal-ts-clients": "npm:0.23.2-feat-marketplace-v2-resources-106f89e-7374-1.0"
"@graphql-codegen/cli": "npm:^5.0.3"
"@graphql-codegen/typescript": "npm:^4.1.1"
"@graphql-codegen/typescript-graphql-request": "npm:^6.2.0"
@@ -2038,9 +2038,9 @@ __metadata:
languageName: unknown
linkType: soft
-"@fluencelabs/deal-ts-clients@npm:0.23.2-feat-marketplace-v2-resources-6fb9f61-7385-1.0":
- version: 0.23.2-feat-marketplace-v2-resources-6fb9f61-7385-1.0
- resolution: "@fluencelabs/deal-ts-clients@npm:0.23.2-feat-marketplace-v2-resources-6fb9f61-7385-1.0"
+"@fluencelabs/deal-ts-clients@npm:0.23.2-feat-marketplace-v2-resources-106f89e-7374-1.0":
+ version: 0.23.2-feat-marketplace-v2-resources-106f89e-7374-1.0
+ resolution: "@fluencelabs/deal-ts-clients@npm:0.23.2-feat-marketplace-v2-resources-106f89e-7374-1.0"
dependencies:
"@graphql-typed-document-node/core": "npm:^3.2.0"
debug: "npm:^4.3.4"
@@ -2052,7 +2052,7 @@ __metadata:
graphql-tag: "npm:^2.12.6"
ipfs-http-client: "npm:^60.0.1"
multiformats: "npm:^13.0.1"
- checksum: 10c0/779a403c6dde8833246cc55b42d6979a6f30e7fc46c3fcbe2e8e8558a1a766efd0d4211ce1b06db6db8c22374320752dc0338ea5a067373e3061e598f6e65ce4
+ checksum: 10c0/8adbb8f6521efc8a81cc8d18d9ac58cc4b6b39d9024e5b816cd79b95571a8a51f157f97db3401187f9925378f44031858ed359d2183e08803b1284671fbad2a5
languageName: node
linkType: hard