Skip to content

Commit

Permalink
feat: add logic for fetching and storing MASP params
Browse files Browse the repository at this point in the history
  • Loading branch information
jurevans committed Sep 19, 2024
1 parent 994e526 commit f15fe7c
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 0 deletions.
3 changes: 3 additions & 0 deletions apps/namadillo/src/atoms/masp/atoms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// TODO

// Masp Params Atom
124 changes: 124 additions & 0 deletions apps/namadillo/src/atoms/masp/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { toBase64 } from "@cosmjs/encoding";
import { IndexedDBKVStore } from "@namada/storage";
import { sha256Hash } from "utils";
import { MASP_PARAMS_URL, MaspParam, MaspParamConfigs } from "./types";

export type MaspParamBytes = {
param: MaspParam;
bytes: Uint8Array;
};

export type MaspParamBytesProgress = {
param: MaspParam;
chunkSize: number;
expectedSize: number;
progress: number;
};

export type MaspParamBytesComplete = {
param: MaspParam;
progress: number;
};

export const fetchMaspParam = async (
param: MaspParam,
// Optional callback to track download progress
onRead?: (progress: MaspParamBytesProgress) => void,
// Optional callback for download complete
onComplete?: (complete: MaspParamBytesComplete) => void
): Promise<MaspParamBytes> => {
return fetch([MASP_PARAMS_URL, param].join("/"))
.then(async (response) => {
if (response.ok) {
const reader = response.body?.getReader();
if (!reader) {
throw new Error("No readable stream returned!");
}
return new ReadableStream({
start(controller) {
// Track download progress
const expectedSize = MaspParamConfigs[param].length;
let progress: number = 0;
// Start pumping the stream
return pump();
async function pump(): Promise<typeof pump | undefined> {
return reader?.read().then(({ done, value }) => {
// Invoke callback if provided
if (onRead && value) {
const chunkSize = value.length;
progress += chunkSize;
onRead({
param,
chunkSize,
expectedSize,
progress,
});
}
// When no more data needs to be consumed, close the stream
if (done) {
controller.close();
// Invoke callback with current param & progress if callback was provided
if (onComplete)
onComplete({
param,
progress,
});
return;
}
// Enqueue the next data chunk into our target stream
controller.enqueue(value);
return pump();
});
}
},
});
}
})
.then((stream) => new Response(stream))
.then((response) => response.blob())
.then(async (blob) => {
const arrayBuffer = await blob.arrayBuffer();
return {
param: param,
bytes: new Uint8Array(arrayBuffer),
};
});
};

export const storeMaspParam = async (
store: IndexedDBKVStore<string>,
{ param, bytes }: MaspParamBytes
): Promise<MaspParamBytes> => {
console.info(`Storing ${param}...`);
const base64Bytes = toBase64(bytes);
await store.set(param, base64Bytes);
return {
param,
bytes,
};
};

export const validateMaspParamBytes = async ({
param,
bytes,
}: MaspParamBytes): Promise<MaspParamBytes> => {
const { length, sha256sum } = MaspParamConfigs[param];

// Reject if invalid length (incomplete download or invalid)
if (length !== bytes.length) {
return Promise.reject(
`Invalid data length for ${param}! Expected ${length}, received ${bytes.length}!`
);
}

// Reject if invalid hash (otherwise invalid data)
const hash = await sha256Hash(bytes);

if (hash !== sha256sum) {
return Promise.reject(
`Invalid sha256 checksum for ${param}! Expected ${sha256sum}, received ${hash}!`
);
}

return { param, bytes };
};
2 changes: 2 additions & 0 deletions apps/namadillo/src/atoms/masp/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./functions";
export * from "./types";
36 changes: 36 additions & 0 deletions apps/namadillo/src/atoms/masp/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const { NAMADA_INTERFACE_PROXY: isProxy } = process.env;

const MASP_MPC_URL =
"https://github.com/anoma/masp-mpc/releases/download/namada-trusted-setup";

export const MASP_PARAMS_URL =
isProxy === "true" ? "http://localhost:8010/proxy" : MASP_MPC_URL;

export const STORAGE_PREFIX = "namadillo";

export enum MaspParam {
Output = "masp-output.params",
Convert = "masp-convert.params",
Spend = "masp-spend.params",
}

export const MaspParamConfigs: Record<
MaspParam,
{ length: number; sha256sum: string }
> = {
[MaspParam.Output]: {
length: 16398620,
sha256sum:
"ed8b5d354017d808cfaf7b31eca5c511936e65ef6d276770251f5234ec5328b8",
},
[MaspParam.Spend]: {
length: 49848572,
sha256sum:
"62b3c60ca54bd99eb390198e949660624612f7db7942db84595fa9f1b4a29fd8",
},
[MaspParam.Convert]: {
length: 22570940,
sha256sum:
"8e049c905e0e46f27662c7577a4e3480c0047ee1171f7f6d9c5b0de757bf71f1",
},
};
10 changes: 10 additions & 0 deletions apps/namadillo/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,13 @@ export const secondsToTimeRemainingString = (
.replace("hour", "Hr")
.replace("minute", "Min");
};

/**
* Given a Uint8Array, return the sha256 hash
*/
export const sha256Hash = async (msg: Uint8Array): Promise<string> => {
const hashBuffer = await crypto.subtle.digest("SHA-256", msg);
const hashArray = Array.from(new Uint8Array(hashBuffer));
// Return hash as hex
return hashArray.map((byte) => byte.toString(16).padStart(2, "0")).join("");
};

0 comments on commit f15fe7c

Please sign in to comment.