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

Update cli new new sc #136

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cli/src/commands/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const uploadCommand = new Command('upload')
provider: provider,
batches: [],
chunks: [],
preStores: [],
fileInits: [],
chunkSize: chunkSize,
websiteDirPath: websiteDirPath,
skipConfirm: options.yes,
Expand Down
6 changes: 3 additions & 3 deletions cli/src/lib/batcher.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ChunkPost } from './website/chunkPost'
import { FileChunkPost } from './website/models/FileChunkPost'

/**
* Represents a batch of chunks to be uploaded.
*/
export interface Batch {
id: number
chunks: ChunkPost[]
chunks: FileChunkPost[]
}

/**
Expand All @@ -14,7 +14,7 @@ export interface Batch {
* @param chunks - the chunks to divide into batches
* @param chunkSize - the maximum size of each batch
*/
export function batcher(chunks: ChunkPost[], chunkSize: number): Batch[] {
export function batcher(chunks: FileChunkPost[], chunkSize: number): Batch[] {
chunks.sort((a, b) => b.data.length - a.data.length)

const batches: Batch[] = []
Expand Down
79 changes: 17 additions & 62 deletions cli/src/lib/website/chunk.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { U32 } from '@massalabs/massa-web3'
import { sha256 } from 'js-sha256'

import { storageCostForEntry } from '../utils/storage'

const FILE_TAG = 0
const CHUNK_TAG = 1
const CHUNK_NB_TAG = 2
import { fileChunkKey } from './storageKeys'
import { FileChunkPost } from './models/FileChunkPost'

/**
* Divide a data array into chunks of a given size.
Expand Down Expand Up @@ -51,71 +48,29 @@ export function computeChunkCost(
chunkID: bigint,
chunkSize: bigint
): bigint {
const filePathHash = sha256.arrayBuffer(filePath)
const filePathHashBytes = new Uint8Array(filePathHash)

// Storage of the chunk itself
let uploadCost = storageCostForEntry(
getChunkKeyLength(filePath, chunkID),
BigInt(fileChunkKey(filePathHashBytes, chunkID).length),
chunkSize
)

return uploadCost
}

/**
* Returns the storage key for a chunk from its ID and the file path.
* @param filePath - the path of the file the chunk belongs to
* @param chunkID - the ID of the chunk
* @returns the key full key of the chunk
* Convert an array of chunks into an array of FileChunkPost.
* @param filepath - the path of the file the chunks belong to
* @param chunks - the chunks to convert
* @returns an array of FileChunkPost
*/
export function getChunkKey(filePath: string, chunkID: bigint): Uint8Array {
const filePathHashBuffer = sha256.arrayBuffer(filePath)
const filePathHashBytes = new Uint8Array(filePathHashBuffer)

const chunkNumberBytes = U32.toBytes(chunkID)

const result = new Uint8Array(
1 + filePathHashBytes.length + 1 + chunkNumberBytes.length
)

let offset = 0

result[offset++] = FILE_TAG

result.set(filePathHashBytes, offset)
offset += filePathHashBytes.length

result[offset++] = CHUNK_TAG

result.set(chunkNumberBytes, offset)

return result
}

/**
* Returns the length of the storage key for a chunk from its ID and the file path.
* @param filePath - the path of the file the chunk belongs to
* @param chunkID - the ID of the chunk
* @returns the length of the chunk key
*/
export function getChunkKeyLength(filePath: string, chunkID: bigint): bigint {
return BigInt(getChunkKey(filePath, chunkID).length)
}

/**
* Returns the storage key for the total number of chunks for a file.
* @param filePath - the path of the file the chunk belongs to
* @returns the key of the total number of chunks for a file
*/
export function getTotalChunkKey(filePath: string): Uint8Array {
const filePathHashBuffer = sha256.arrayBuffer(filePath)
const filePathHashBytes = new Uint8Array(filePathHashBuffer)

const result = new Uint8Array(1 + filePathHashBytes.length)

let offset = 0

result[offset++] = CHUNK_NB_TAG

result.set(filePathHashBytes, offset)

return result
export function toChunkPosts(
filepath: string,
chunks: Uint8Array[]
): FileChunkPost[] {
return chunks.map((chunk, index) => {
return new FileChunkPost(filepath, BigInt(index), chunk)
})
}
35 changes: 0 additions & 35 deletions cli/src/lib/website/chunkPost.ts

This file was deleted.

7 changes: 5 additions & 2 deletions cli/src/lib/website/deploySC.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Provider, SmartContract, U32 } from '@massalabs/massa-web3'

Check failure on line 1 in cli/src/lib/website/deploySC.ts

View workflow job for this annotation

GitHub Actions / lint-cli

'U32' is defined but never used
import { readFileSync } from 'fs'
import { storageCostForEntry } from '../utils/storage'
import { DEWEB_VERSION_TAG } from './storageKeys'

const byteCode = readFileSync('src/lib/website/sc/main.wasm')
const ownerKey = 'OWNER'
Expand All @@ -16,7 +17,9 @@
BigInt(ownerKey.length),
BigInt(provider.address.length)
)
const fileListCost = storageCostForEntry(1n, BigInt(U32.SIZE_BYTE))

return ownerKeyCost + fileListCost
// u32 for string size, 1 byte for the version number, so 5 bytes
const versionCost = storageCostForEntry(BigInt(DEWEB_VERSION_TAG.length), BigInt(5))

return ownerKeyCost + versionCost
}
154 changes: 154 additions & 0 deletions cli/src/lib/website/filesInit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import {
Args,
Mas,
MAX_GAS_CALL,
minBigInt,
Operation,
SmartContract,
U32,
} from '@massalabs/massa-web3'
import { storageCostForEntry } from '../utils/storage'
import { FileInit } from './models/FileInit'
import { Metadata } from './models/Metadata'
import {
fileChunkCountKey,
fileLocationKey,
globalMetadataKey,
} from './storageKeys'

const functionName = 'filesInit'
const batchSize = 20

/**
* Send the filesInits to the smart contract
* @param sc - SmartContract instance
* @param files - Array of FileInit instances
* @param filesToDelete - Array of FileInit instances to delete
* @param metadatas - Array of Metadata instances
* @returns - Array of Operation instances
*/
export async function sendFilesInits(
sc: SmartContract,
files: FileInit[],
filesToDelete: FileInit[],
metadatas: Metadata[]
): Promise<Operation[]> {
const chunkBatches: FileInit[][] = []
const operations: Operation[] = []

for (let i = 0; i < files.length; i += batchSize) {
chunkBatches.push(files.slice(i, i + batchSize))
}

for (const batch of chunkBatches) {
const coins = await filesInitCost(sc, batch, filesToDelete, metadatas)
const gas = await estimatePrepareGas(sc, batch, filesToDelete, metadatas)
const op = await sc.call(
functionName,
new Args()
.addSerializableObjectArray(batch)
.addSerializableObjectArray(filesToDelete)
.addSerializableObjectArray(metadatas)
.serialize(),
{
coins: coins,
maxGas: gas,
fee:
BigInt(gas) > BigInt(Mas.fromString('0.01'))
? BigInt(gas)
: BigInt(Mas.fromString('0.01')),
}
)

operations.push(op)
}

return operations
}

// TODO: Improve estimation
// - If a file is already stored, we don't need to send coins for its hash storage
export async function filesInitCost(
_: SmartContract,
files: FileInit[],
filesToDelete: FileInit[],
metadatas: Metadata[]
): Promise<bigint> {
const filePathListCost = files.reduce((acc, chunk) => {
return (
acc +
storageCostForEntry(
BigInt(fileLocationKey(chunk.hashLocation).length),
BigInt(chunk.location.length + 4)
)
)
}, 0n)

const storageCost = files.reduce((acc, chunk) => {
return (
acc +
storageCostForEntry(
BigInt(fileChunkCountKey(chunk.hashLocation).length),
BigInt(U32.SIZE_BYTE)
)
)
}, 0n)

const filesToDeleteCost = filesToDelete.reduce((acc, chunk) => {
return (
acc +
storageCostForEntry(
1n + BigInt(chunk.hashLocation.length),
BigInt(U32.SIZE_BYTE)
)
)
}, 0n)

const metadatasCost = metadatas.reduce((acc, metadata) => {
return (
acc +
storageCostForEntry(
BigInt(globalMetadataKey(metadata.key).length),
BigInt(metadata.value.length + 4)
)
)
}, 0n)

return BigInt(
filePathListCost + storageCost + metadatasCost - filesToDeleteCost
)
}

/**
* Estimate the gas cost for the operation
* Required until https://github.com/massalabs/massa/issues/4742 is fixed
* @param sc - SmartContract instance
* @param files - Array of PreStore instances
* @returns - Estimated gas cost for the operation
*/
export async function estimatePrepareGas(
sc: SmartContract,
files: FileInit[],
filesToDelete: FileInit[],
metadatas: Metadata[]
): Promise<bigint> {
const coins = await filesInitCost(sc, files, filesToDelete, metadatas)

const result = await sc.read(
functionName,
new Args()
.addSerializableObjectArray(files)
.addSerializableObjectArray(filesToDelete)
.addSerializableObjectArray(metadatas)
.serialize(),
{
coins: coins,
maxGas: MAX_GAS_CALL,
}
)
if (result.info.error) {
throw new Error(result.info.error)
}
const gasCost = BigInt(result.info.gasCost)
return minBigInt(gasCost * BigInt(files.length), MAX_GAS_CALL)
}
29 changes: 29 additions & 0 deletions cli/src/lib/website/models/FileChunkPost.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Args, DeserializedResult, Serializable } from '@massalabs/massa-web3'

export class FileChunkPost implements Serializable<FileChunkPost> {
constructor(
public location: string = '',
public index: bigint = 0n,
public data: Uint8Array = new Uint8Array(0)
) {}

serialize(): Uint8Array {
return new Args()
.addString(this.location)
.addU32(this.index)
.addUint8Array(this.data)
.serialize()
}

deserialize(
data: Uint8Array,
offset: number
): DeserializedResult<FileChunkPost> {
const args = new Args(data, offset)
this.location = args.nextString()
this.index = args.nextU32()
this.data = args.nextUint8Array()

return { instance: this, offset: args.getOffset() }
}
}
Loading
Loading