Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

100 implement edit function #145

Merged
merged 11 commits into from
Oct 25, 2024
1 change: 0 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ jobs:
run: npm test

functional-test-cli:
if: false
runs-on: ubuntu-22.04

defaults:
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,6 @@ export const listFilesCommand = new Command('list')
console.log('Targeting website at address', sc.address)

const files = await listFiles(provider, sc)
console.log(`Total of ${files.length} files:`)
files.sort().forEach((f) => console.log(f))
})
4 changes: 4 additions & 0 deletions cli/src/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,14 @@ export const uploadCommand = new Command('upload')
batches: [],
chunks: [],
fileInits: [],
filesToDelete: [],
metadatas: [],
metadatasToDelete: [],
chunkSize: chunkSize,
websiteDirPath: websiteDirPath,
skipConfirm: options.yes,
currentTotalEstimation: 0n,
maxConcurrentOps: 4,
minimalFees: await provider.client.getMinimalFee(),
}

Expand Down
178 changes: 139 additions & 39 deletions cli/src/lib/website/filesInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import {
Args,
Mas,
MAX_GAS_CALL,
MIN_GAS_CALL,
minBigInt,
Operation,
SmartContract,
strToBytes,
U32,
} from '@massalabs/massa-web3'
import { storageCostForEntry } from '../utils/storage'
Expand All @@ -16,10 +18,70 @@ import {
globalMetadataKey,
} from './storageKeys'
import { FileDelete } from './models/FileDelete'
import { maxBigInt } from '../../tasks/utils'

const functionName = 'filesInit'
const batchSize = 20

/**
* Divide the files, filesToDelete, metadatas, and metadatasToDelete into multiple batches
* @param files - Array of FileInit instances
* @param filesToDelete - Array of FileDelete instances
* @param metadatas - Array of Metadata instances
* @param metadatasToDelete - Array of Metadata instances to delete
* @param batchSize - Maximum number of elements in each batch
* @returns - Array of Batch instances
*/
function createBatches(
files: FileInit[],
filesToDelete: FileDelete[],
metadatas: Metadata[],
metadatasToDelete: Metadata[]
): Batch[] {
const batches: Batch[] = []

let currentBatch = new Batch([], [], [], [])

const addBatch = () => {
if (Object.values(currentBatch).some((v) => v.length > 0)) {
batches.push(currentBatch)
currentBatch = new Batch([], [], [], [])
}
}

for (const file of files) {
if (currentBatch.fileInits.length >= batchSize) {
addBatch()
}
currentBatch.fileInits.push(file)
}

for (const fileDelete of filesToDelete) {
if (currentBatch.fileDeletes.length >= batchSize) {
addBatch()
}
currentBatch.fileDeletes.push(fileDelete)
}

for (const metadata of metadatas) {
if (currentBatch.metadatas.length >= batchSize) {
addBatch()
}
currentBatch.metadatas.push(metadata)
}

for (const metadataDelete of metadatasToDelete) {
if (currentBatch.metadataDeletes.length >= batchSize) {
addBatch()
}
currentBatch.metadataDeletes.push(metadataDelete)
}

addBatch()

return batches
}

/**
* Send the filesInits to the smart contract
* @param sc - SmartContract instance
Expand All @@ -33,44 +95,27 @@ export async function sendFilesInits(
files: FileInit[],
filesToDelete: FileDelete[],
metadatas: Metadata[],
metadatasToDelete: Metadata[]
metadatasToDelete: Metadata[],
minimalFees: bigint = Mas.fromString('0.01')
): Promise<Operation[]> {
const fileInitsBatches: FileInit[][] = []
const operations: Operation[] = []
const batches: Batch[] = createBatches(
files,
filesToDelete,
metadatas,
metadatasToDelete
)

for (let i = 0; i < files.length; i += batchSize) {
fileInitsBatches.push(files.slice(i, i + batchSize))
}
const operations: Operation[] = []

for (const batch of fileInitsBatches) {
const coins = await filesInitCost(
sc,
batch,
filesToDelete,
metadatas,
metadatasToDelete
)
const gas = await estimatePrepareGas(
sc,
batch,
filesToDelete,
metadatas,
metadatasToDelete
)
const args = new Args()
.addSerializableObjectArray(batch)
.addSerializableObjectArray(filesToDelete)
.addSerializableObjectArray(metadatas)
.addSerializableObjectArray(metadatasToDelete)
.serialize()
for (const batch of batches) {
const coins = await batch.batchCost(sc)
const gas = await batch.estimateGas(sc)
const args = batch.serialize()

const op = await sc.call(functionName, args, {
coins: coins,
coins: coins <= 0n ? 0n : coins,
thomas-senechal marked this conversation as resolved.
Show resolved Hide resolved
maxGas: gas,
fee:
BigInt(gas) > BigInt(Mas.fromString('0.01'))
? BigInt(gas)
: BigInt(Mas.fromString('0.01')),
fee: gas > minimalFees ? gas : minimalFees,
})

operations.push(op)
Expand Down Expand Up @@ -122,7 +167,7 @@ export async function filesInitCost(
return (
acc +
storageCostForEntry(
BigInt(globalMetadataKey(metadata.key).length),
BigInt(globalMetadataKey(strToBytes(metadata.key)).length),
BigInt(metadata.value.length + 4)
)
)
Expand All @@ -132,7 +177,7 @@ export async function filesInitCost(
return (
acc +
storageCostForEntry(
BigInt(globalMetadataKey(metadata.key).length),
BigInt(globalMetadataKey(strToBytes(metadata.key)).length),
BigInt(metadata.value.length + 4)
)
)
Expand All @@ -148,13 +193,17 @@ export async function filesInitCost(
}

/**
* Estimate the gas cost for the operation
* Estimate the gas cost for the prepare operation
* Required until https://github.com/massalabs/massa/issues/4742 is fixed
* @param sc - SmartContract instance
* @param files - Array of PreStore instances
* @param files - Array of FileInit instances
* @param filesToDelete - Array of FileDelete instances
* @param metadatas - Array of Metadata instances
* @param metadatasToDelete - Array of Metadata instances to delete
*
* @returns - Estimated gas cost for the operation
*/
export async function estimatePrepareGas(
async function estimatePrepareGas(
sc: SmartContract,
files: FileInit[],
filesToDelete: FileDelete[],
Expand All @@ -176,14 +225,65 @@ export async function estimatePrepareGas(
.serialize()

const result = await sc.read(functionName, args, {
coins: coins,
coins: coins <= 0n ? 0n : coins,
maxGas: MAX_GAS_CALL,
})
if (result.info.error) {
console.error(result.info)
throw new Error(result.info.error)
}

const gasCost = BigInt(result.info.gasCost)
const numberOfElements = BigInt(
files.length +
filesToDelete.length +
metadatas.length +
metadatasToDelete.length
)

return minBigInt(
maxBigInt(gasCost * numberOfElements, MIN_GAS_CALL),
MAX_GAS_CALL
)
}

/**
* Represents parameters for the filesInit function
*/
class Batch {
constructor(
public fileInits: FileInit[],
public fileDeletes: FileDelete[],
public metadatas: Metadata[],
public metadataDeletes: Metadata[]
) {}

serialize(): Uint8Array {
return new Args()
.addSerializableObjectArray(this.fileInits)
.addSerializableObjectArray(this.fileDeletes)
.addSerializableObjectArray(this.metadatas)
.addSerializableObjectArray(this.metadataDeletes)
.serialize()
}

return minBigInt(gasCost * BigInt(files.length), MAX_GAS_CALL)
batchCost(sc: SmartContract): Promise<bigint> {
return filesInitCost(
sc,
this.fileInits,
this.fileDeletes,
this.metadatas,
this.metadataDeletes
)
}

estimateGas(sc: SmartContract): Promise<bigint> {
return estimatePrepareGas(
sc,
this.fileInits,
this.fileDeletes,
this.metadatas,
this.metadataDeletes
)
}
}
8 changes: 7 additions & 1 deletion cli/src/lib/website/models/FileDelete.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'
import { sha256 } from 'js-sha256'

export class FileDelete implements Serializable<FileDelete> {
constructor(public hashLocation: Uint8Array = new Uint8Array(0)) {}
constructor(
public location: string,
public hashLocation: Uint8Array = new Uint8Array(
sha256.arrayBuffer(location)
)
) {}

serialize(): Uint8Array {
return new Args().addUint8Array(this.hashLocation).serialize()
Expand Down
13 changes: 5 additions & 8 deletions cli/src/lib/website/models/Metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,19 @@ import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'

export class Metadata implements Serializable<Metadata> {
constructor(
public key: Uint8Array = new Uint8Array(0),
public value: Uint8Array = new Uint8Array(0)
public key: string = '',
public value: string = ''
) {}

serialize(): Uint8Array {
return new Args()
.addUint8Array(this.key)
.addUint8Array(this.value)
.serialize()
return new Args().addString(this.key).addString(this.value).serialize()
}

deserialize(data: Uint8Array, offset: number): DeserializedResult<Metadata> {
const args = new Args(data, offset)

this.key = args.nextUint8Array()
this.value = args.nextUint8Array()
this.key = args.nextString()
this.value = args.nextString()

return { instance: this, offset: args.getOffset() }
}
Expand Down
1 change: 1 addition & 0 deletions cli/src/tasks/estimations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export function showEstimatedCost(): ListrTask {
}

ctx.currentTotalEstimation += opFees
ctx.currentTotalEstimation += totalEstimatedGas
},
rendererOptions: {
outputBar: Infinity,
Expand Down
Loading
Loading