Merge pull request #14 from mCaptcha/js-bench
ci/woodpecker/push/woodpecker Pipeline failed Details

Collect JavaScript polyfill benchmark using mCaptcha/survey
This commit is contained in:
Aravinth Manivannan 2023-02-13 14:14:44 +05:30 committed by GitHub
commit 4f224d782a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1833 additions and 1322 deletions

View File

@ -12,6 +12,7 @@ module.exports = {
plugins: ["@typescript-eslint"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-var-requires": "off",
"@typescript-eslint/ban-types": "off",
indent: ["error", 2],
"linebreak-style": ["error", "unix"],

View File

@ -66,7 +66,7 @@ mime = "0.3.16"
#sailfish = "0.3.2"
tracing = { version = "0.1.37", features = ["log"] }
tera = "1.17.1"
tera = { version="1.17.1", features=["builtins"]}
#tokio = "1.11.0"

View File

@ -1,7 +1,7 @@
debug = true
allow_registration = true
source_code = "https://github.com/mcaptcha/survey"
default_campaign = "b6b261fa-3ef9-4d7f-8852-339b8f81bb01"
default_campaign = "4e951e01-71ee-4a18-9b97-782965495ae3"
support_email="support@example.org"
[server]

View File

@ -0,0 +1,2 @@
ALTER TABLE survey_responses
ADD COLUMN submitted_at TIMESTAMPTZ NOT NULL DEFAULT now();

View File

@ -0,0 +1,18 @@
CREATE TABLE IF NOT EXISTS survey_bench_type (
name VARCHAR(30) UNIQUE NOT NULL,
ID SERIAL PRIMARY KEY NOT NULL
);
INSERT INTO survey_bench_type (name) VALUES ('wasm');
INSERT INTO survey_bench_type (name) VALUES ('js');
CREATE OR REPLACE FUNCTION id_in_survey_bench_type(iname varchar)
RETURNS int LANGUAGE SQL AS $$
SELECT ID FROM survey_bench_type WHERE name = name;
$$;
ALTER TABLE survey_responses
ADD COLUMN submission_bench_type_id INTEGER references survey_bench_type(ID) NOT NULL
DEFAULT id_in_survey_bench_type('wasm');

45
package-lock.json generated
View File

@ -8,7 +8,8 @@
"name": "mcaptcha-survey",
"version": "1.0.0",
"dependencies": {
"@mcaptcha/pow-wasm": "0.1.0-alpha-1",
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1",
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-3"
},
"devDependencies": {
@ -18,7 +19,7 @@
"@types/sinon": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@wasm-tool/wasm-pack-plugin": "^1.4.0",
"@wasm-tool/wasm-pack-plugin": "^1.6.0",
"dart-sass": "^1.25.0",
"eslint": "^8.0.1",
"jest": "^27.2.5",
@ -1439,6 +1440,29 @@
}
]
},
"node_modules/@mcaptcha/pow_sha256-polyfill": {
"version": "0.1.0-alpha-1",
"resolved": "https://registry.npmjs.org/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-alpha-1.tgz",
"integrity": "sha512-lnQNBCOnVI9BunHP8FGCsGs310GguQWdxSspXlvWcrLwgl86aq0hlBzZfOV+szG/jeTRAMry0He3MrD/kbqB/Q==",
"funding": [
{
"type": "individual",
"url": "http://mcaptcha.org/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/mcaptcha"
},
{
"type": "individual",
"url": "http://batsense.net/donate"
},
{
"type": "liberapay",
"url": "https://liberapay.com/realaravinth"
}
]
},
"node_modules/@mcaptcha/pow-wasm": {
"version": "0.1.0-alpha-1",
"resolved": "https://registry.npmjs.org/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-alpha-1.tgz",
@ -2919,9 +2943,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001447",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001447.tgz",
"integrity": "sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==",
"version": "1.0.30001448",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
"integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==",
"dev": true,
"funding": [
{
@ -11164,6 +11188,11 @@
"resolved": "https://registry.npmjs.org/@mcaptcha/core-glue/-/core-glue-0.1.0-alpha-5.tgz",
"integrity": "sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA=="
},
"@mcaptcha/pow_sha256-polyfill": {
"version": "0.1.0-alpha-1",
"resolved": "https://registry.npmjs.org/@mcaptcha/pow_sha256-polyfill/-/pow_sha256-polyfill-0.1.0-alpha-1.tgz",
"integrity": "sha512-lnQNBCOnVI9BunHP8FGCsGs310GguQWdxSspXlvWcrLwgl86aq0hlBzZfOV+szG/jeTRAMry0He3MrD/kbqB/Q=="
},
"@mcaptcha/pow-wasm": {
"version": "0.1.0-alpha-1",
"resolved": "https://registry.npmjs.org/@mcaptcha/pow-wasm/-/pow-wasm-0.1.0-alpha-1.tgz",
@ -12363,9 +12392,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001447",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001447.tgz",
"integrity": "sha512-bdKU1BQDPeEXe9A39xJnGtY0uRq/z5osrnXUw0TcK+EYno45Y+U7QU9HhHEyzvMDffpYadFXi3idnSNkcwLkTw==",
"version": "1.0.30001448",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001448.tgz",
"integrity": "sha512-tq2YI+MJnooG96XpbTRYkBxLxklZPOdLmNIOdIhvf7SNJan6u5vCKum8iT7ZfCt70m1GPkuC7P3TtX6UuhupuA==",
"dev": true
},
"chalk": {

View File

@ -16,7 +16,7 @@
"@types/sinon": "^10.0.0",
"@typescript-eslint/eslint-plugin": "^5.0.0",
"@typescript-eslint/parser": "^5.0.0",
"@wasm-tool/wasm-pack-plugin": "^1.4.0",
"@wasm-tool/wasm-pack-plugin": "^1.6.0",
"dart-sass": "^1.25.0",
"eslint": "^8.0.1",
"jest": "^27.2.5",
@ -33,6 +33,7 @@
},
"dependencies": {
"@mcaptcha/vanilla-glue": "^0.1.0-alpha-3",
"@mcaptcha/pow-wasm": "0.1.0-alpha-1"
"@mcaptcha/pow_sha256-polyfill": "^0.1.0-alpha-1",
"@mcaptcha/pow-wasm": "^0.1.0-alpha-1"
}
}

View File

@ -24,12 +24,15 @@ use uuid::Uuid;
use super::{get_admin_check_login, get_uuid};
use crate::api::v1::bench::Bench;
use crate::api::v1::bench::SubmissionType;
use crate::errors::*;
use crate::AppData;
pub mod routes {
use serde::{Deserialize, Serialize};
use super::ResultsPage;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Campaign {
pub add: &'static str,
@ -62,13 +65,33 @@ pub mod routes {
self.delete.replace("{uuid}", campaign_id)
}
pub fn get_results_route(&self, campaign_id: &str) -> String {
self.results.replace("{uuid}", campaign_id)
pub fn get_results_route(
&self,
campaign_id: &str,
modifier: Option<ResultsPage>,
) -> String {
let mut res = self.results.replace("{uuid}", campaign_id);
if let Some(modifier) = modifier {
if let Some(page) = modifier.page {
res = format!("{res}?page={page}");
}
if let Some(bench_type) = modifier.bench_type {
if modifier.page.is_some() {
res = format!("{res}&bench_type={}", bench_type.to_string());
} else {
res = format!("{res}?bench_type={}", bench_type.to_string());
}
}
}
res
}
}
}
pub mod runners {
use std::str::FromStr;
use futures::try_join;
use crate::api::v1::bench::Bench;
@ -163,10 +186,12 @@ pub mod runners {
#[derive(Debug)]
struct InternalSurveyResp {
id: i32,
submitted_at: OffsetDateTime,
user_id: Uuid,
threads: Option<i32>,
device_user_provided: String,
device_software_recognised: String,
name: String,
}
#[derive(Debug)]
@ -190,34 +215,98 @@ pub mod runners {
data: &AppData,
page: usize,
limit: usize,
filter: Option<SubmissionType>,
) -> ServiceResult<Vec<SurveyResponse>> {
let mut db_responses = sqlx::query_as!(
InternalSurveyResp,
"SELECT
ID,
device_software_recognised,
threads,
user_id,
device_user_provided
let mut db_responses = if let Some(filter) = filter {
sqlx::query_as!(
InternalSurveyResp,
"SELECT
survey_responses.ID,
survey_responses.device_software_recognised,
survey_responses.threads,
survey_responses.user_id,
survey_responses.submitted_at,
survey_responses.device_user_provided,
survey_bench_type.name
FROM
survey_responses
INNER JOIN survey_bench_type ON
survey_responses.submission_bench_type_id = survey_bench_type.ID
WHERE
survey_bench_type.name = $3
AND
survey_responses.campaign_id = (
SELECT ID FROM survey_campaigns
WHERE
ID = $1
AND
user_id = (SELECT ID FROM survey_admins WHERE name = $2)
)
LIMIT $4 OFFSET $5",
uuid,
username,
filter.to_string(),
limit as i32,
page as i32,
)
.fetch_all(&data.db)
.await?
} else {
#[derive(Debug)]
struct I {
id: Option<i32>,
submitted_at: Option<OffsetDateTime>,
user_id: Option<Uuid>,
threads: Option<i32>,
device_user_provided: Option<String>,
device_software_recognised: Option<String>,
name: Option<String>,
}
let mut i = sqlx::query_as!(
I,
"SELECT
survey_responses.ID,
survey_responses.device_software_recognised,
survey_responses.threads,
survey_responses.user_id,
survey_responses.submitted_at,
survey_responses.device_user_provided,
survey_bench_type.name
FROM
survey_responses
INNER JOIN survey_bench_type ON
survey_responses.submission_bench_type_id = survey_bench_type.ID
WHERE
campaign_id = (
survey_responses.campaign_id = (
SELECT ID FROM survey_campaigns
WHERE
ID = $1
AND
user_id = (SELECT ID FROM survey_admins WHERE name = $2)
)
LIMIT $3 OFFSET $4
",
uuid,
username,
limit as i32,
page as i32,
)
.fetch_all(&data.db)
.await?;
LIMIT $3 OFFSET $4",
uuid,
username,
limit as i32,
page as i32,
)
.fetch_all(&data.db)
.await?;
let mut res = Vec::with_capacity(i.len());
i.drain(0..).for_each(|x| {
res.push(InternalSurveyResp {
id: x.id.unwrap(),
submitted_at: x.submitted_at.unwrap(),
user_id: x.user_id.unwrap(),
threads: x.threads,
device_user_provided: x.device_user_provided.unwrap(),
device_software_recognised: x.device_software_recognised.unwrap(),
name: x.name.unwrap(),
})
});
res
};
let mut responses = Vec::with_capacity(db_responses.len());
for r in db_responses.drain(0..) {
@ -256,7 +345,9 @@ pub mod runners {
user,
device_user_provided: r.device_user_provided,
device_software_recognised: r.device_software_recognised,
submitted_at: r.submitted_at.unix_timestamp(),
id: r.id as usize,
submission_type: SubmissionType::from_str(&r.name).unwrap(),
threads: r.threads.map(|t| t as usize),
})
}
@ -314,6 +405,8 @@ pub struct SurveyResponse {
pub device_software_recognised: String,
pub id: usize,
pub threads: Option<usize>,
pub submitted_at: i64,
pub submission_type: SubmissionType,
pub benches: Vec<Bench>,
}
@ -364,12 +457,17 @@ pub fn services(cfg: &mut web::ServiceConfig) {
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct ResultsPage {
page: Option<usize>,
pub bench_type: Option<SubmissionType>,
}
impl ResultsPage {
pub fn page(&self) -> usize {
self.page.unwrap_or(0)
}
pub fn new(page: Option<usize>, bench_type: Option<SubmissionType>) -> Self {
Self { page, bench_type }
}
}
#[actix_web_codegen_const_routes::get(
@ -383,9 +481,12 @@ pub async fn get_campaign_resutls(
data: AppData,
) -> ServiceResult<impl Responder> {
let username = id.identity().unwrap();
let query = query.into_inner();
let page = query.page();
let results = runners::get_results(&username, &path, &data, page, 50).await?;
let results =
runners::get_results(&username, &path, &data, page, 50, query.bench_type)
.await?;
Ok(HttpResponse::Ok().json(results))
}
@ -408,6 +509,7 @@ async fn add(
#[cfg(test)]
mod tests {
use crate::api::v1::bench::Submission;
use crate::api::v1::bench::SubmissionType;
use crate::errors::*;
use crate::tests::*;
use crate::*;
@ -479,6 +581,7 @@ mod tests {
device_software_recognised: DEVICE_SOFTWARE_RECOGNISED.into(),
threads: THREADS,
benches: BENCHES.clone(),
submission_type: SubmissionType::Wasm,
};
let _proof =
@ -493,6 +596,7 @@ mod tests {
&AppData::new(data.clone()),
0,
50,
None,
)
.await
.unwrap();
@ -503,6 +607,34 @@ mod tests {
let mut r = BENCHES.clone();
r.sort_by(|a, b| a.difficulty.cmp(&b.difficulty));
assert_eq!(
super::runners::get_results(
NAME,
&uuid::Uuid::parse_str(&campaign.campaign_id).unwrap(),
&AppData::new(data.clone()),
0,
50,
Some(SubmissionType::Wasm),
)
.await
.unwrap(),
responses
);
assert_eq!(
super::runners::get_results(
NAME,
&uuid::Uuid::parse_str(&campaign.campaign_id).unwrap(),
&AppData::new(data.clone()),
0,
50,
Some(SubmissionType::Js),
)
.await
.unwrap(),
Vec::default()
);
assert_eq!(l, r);
assert_eq!(
responses[0].device_software_recognised,
@ -515,7 +647,7 @@ mod tests {
&V1_API_ROUTES
.admin
.campaign
.get_results_route(&campaign.campaign_id),
.get_results_route(&campaign.campaign_id, None),
cookies.clone()
);
assert_eq!(results_resp.status(), StatusCode::OK);

View File

@ -169,6 +169,28 @@ pub struct Submission {
pub device_software_recognised: String,
pub threads: i32,
pub benches: Vec<Bench>,
pub submission_type: SubmissionType,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SubmissionType {
Wasm,
Js,
}
impl ToString for SubmissionType {
fn to_string(&self) -> String {
let s = serde_json::to_string(&self).unwrap();
(&s[1..(s.len() - 1)]).to_string()
}
}
impl FromStr for SubmissionType {
type Err = serde_json::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(&format!("\"{}\"", s))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@ -177,12 +199,11 @@ pub struct SubmissionProof {
pub proof: String,
}
fn is_session_authenticated(r: &HttpRequest, mut pl: &mut Payload) -> bool {
fn is_session_authenticated(r: &HttpRequest, pl: &mut Payload) -> bool {
use actix_web::FromRequest;
matches!(
Session::from_request(r, pl).into_inner().map(|x| {
let val = x.get::<String>(SURVEY_USER_ID);
println!("{:#?}", val);
val
}),
Ok(Ok(Some(_)))
@ -214,6 +235,7 @@ async fn submit(
let user_id = Uuid::from_str(&username).unwrap();
let payload = payload.into_inner();
let now = OffsetDateTime::now_utc();
struct ID {
id: i32,
@ -221,18 +243,25 @@ async fn submit(
let resp_id = sqlx::query_as!(
ID,
"INSERT INTO survey_responses (
user_id,
campaign_id,
device_user_provided,
device_software_recognised,
threads
) VALUES ($1, $2, $3, $4, $5)
user_id,
campaign_id,
device_user_provided,
device_software_recognised,
threads,
submitted_at,
submission_bench_type_id
) VALUES (
$1, $2, $3, $4, $5, $6,
(SELECT ID FROM survey_bench_type WHERE name = $7)
)
RETURNING ID;",
&user_id,
&campaign_id,
&payload.device_user_provided,
&payload.device_software_recognised,
&payload.threads
&payload.threads,
&now,
&payload.submission_type.to_string(),
)
.fetch_one(&data.db)
.await?;
@ -311,3 +340,14 @@ async fn fetch(data: AppData, path: web::Path<String>) -> ServiceResult<impl Res
.await?;
Ok(HttpResponse::Ok().json(config))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn survey_response_type_no_panic_test() {
assert_eq!(SubmissionType::Wasm.to_string(), "wasm".to_string());
assert_eq!(SubmissionType::Js.to_string(), "js".to_string());
}
}

View File

@ -21,9 +21,11 @@ use actix_web::{HttpResponse, Responder};
use serde::{Deserialize, Serialize};
use tera::Context;
use crate::api::v1::admin::campaigns::ResultsPage;
use crate::api::v1::admin::campaigns::{
runners::list_campaign_runner, ListCampaignResp,
};
use crate::api::v1::bench::SubmissionType;
use crate::pages::errors::*;
use crate::AppData;
use crate::Settings;
@ -55,6 +57,8 @@ pub fn register_templates(t: &mut tera::Tera) {
pub mod routes {
use serde::{Deserialize, Serialize};
use crate::api::v1::admin::campaigns::ResultsPage;
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct Campaigns {
pub home: &'static str,
@ -64,6 +68,7 @@ pub mod routes {
pub delete: &'static str,
pub results: &'static str,
}
impl Campaigns {
pub const fn new() -> Campaigns {
Campaigns {
@ -91,11 +96,22 @@ pub mod routes {
pub fn get_results_route(
&self,
campaign_id: &str,
page: Option<usize>,
modifier: Option<ResultsPage>,
) -> String {
let mut res = self.results.replace("{uuid}", campaign_id);
if let Some(page) = page {
res = format!("{res}?page={page}");
if let Some(modifier) = modifier {
let page = modifier.page();
if page != 0 {
res = format!("{res}?page={page}");
}
if let Some(bench_type) = modifier.bench_type {
if page != 0 {
res = format!("{res}&bench_type={}", bench_type.to_string());
} else {
res = format!("{res}?bench_type={}", bench_type.to_string());
}
}
}
res
}
@ -154,7 +170,6 @@ impl From<ListCampaignResp> for TemplateCampaign {
impl CtxError for Campaigns {
fn with_error(&self, e: &ReadableError) -> String {
self.ctx.borrow_mut().insert(ERROR_KEY, e);
self.render()
}
}

View File

@ -44,33 +44,88 @@ impl CtxError for CampaignResults {
}
}
const RESUTS_LIMIT: usize = 50;
const RESUTS_LIMIT: usize = 10;
#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub struct ResultsPagePayload {
next_page: Option<String>,
submissions: Vec<SurveyResponse>,
pub wasm_only: Option<String>,
pub js_only: Option<String>,
pub all_benches: Option<String>,
}
impl ResultsPagePayload {
pub fn new(
submissions: Vec<SurveyResponse>,
current_page: usize,
campaign_id: &Uuid,
modifier: ResultsPage,
) -> Self {
let next_page = if submissions.len() >= RESUTS_LIMIT {
let campaign_id_str = campaign_id.to_string();
let all_benches;
let wasm_only;
let js_only;
match modifier.bench_type {
Some(SubmissionType::Js) => {
all_benches = Some(
crate::PAGES
.panel
.campaigns
.get_results_route(&campaign_id_str, None),
);
wasm_only = Some(crate::PAGES.panel.campaigns.get_results_route(
&campaign_id_str,
Some(ResultsPage::new(None, Some(SubmissionType::Wasm))),
));
js_only = None;
}
Some(SubmissionType::Wasm) => {
js_only = Some(crate::PAGES.panel.campaigns.get_results_route(
&campaign_id_str,
Some(ResultsPage::new(None, Some(SubmissionType::Js))),
));
all_benches = Some(
crate::PAGES
.panel
.campaigns
.get_results_route(&campaign_id_str, None),
);
wasm_only = None;
}
None => {
all_benches = None;
js_only = Some(crate::PAGES.panel.campaigns.get_results_route(
&campaign_id_str,
Some(ResultsPage::new(None, Some(SubmissionType::Js))),
));
wasm_only = Some(crate::PAGES.panel.campaigns.get_results_route(
&campaign_id_str,
Some(ResultsPage::new(None, Some(SubmissionType::Wasm))),
));
}
}
let next_page = if submissions.len() == RESUTS_LIMIT {
let m = ResultsPage::new(Some(modifier.page() + 1), modifier.bench_type);
Some(
PAGES
.panel
.campaigns
.get_results_route(&campaign_id.to_string(), Some(current_page + 1)),
.get_results_route(&campaign_id_str, Some(m)),
)
} else {
None
};
Self {
next_page,
submissions,
js_only,
wasm_only,
all_benches,
}
}
}
@ -105,15 +160,22 @@ pub async fn results(
)),
Ok(uuid) => {
let username = id.identity().unwrap();
let query = query.into_inner();
let page = query.page();
let results =
runners::get_results(&username, &uuid, &data, page, RESUTS_LIMIT)
.await
.map_err(|e| {
PageError::new(CampaignResults::new(&data.settings, None), e)
})?;
let payload = ResultsPagePayload::new(results, page, &uuid);
let results = runners::get_results(
&username,
&uuid,
&data,
page,
RESUTS_LIMIT,
query.bench_type.clone(),
)
.await
.map_err(|e| {
PageError::new(CampaignResults::new(&data.settings, None), e)
})?;
let payload = ResultsPagePayload::new(results, &uuid, query);
let results_page =
CampaignResults::new(&data.settings, Some(payload)).render();

View File

@ -131,5 +131,7 @@
}
</style>
<!--
<script src="{{ assets.glue }}"></script>
-->
{% endblock body %}

View File

@ -19,6 +19,7 @@ import ROUTES from "../api/v1/routes";
import genJsonPaylod from "../utils/genJsonPayload";
import isBlankString from "../utils/isBlankString";
import createError from "../components/error/";
import { get_bench_type } from "./prove";
export const index = () => {
const ADV = <HTMLButtonElement>document.getElementById("advance");
@ -67,12 +68,13 @@ export const index = () => {
};
const submitBench = async () => {
const submission_type = await get_bench_type();
const payload: Submission = {
device_user_provided: deviceName,
threads: window.navigator.hardwareConcurrency,
device_software_recognised: window.navigator.userAgent,
benches: res,
submission_type,
};
const resp = await fetch(
@ -96,7 +98,6 @@ export const index = () => {
element.appendChild(proof);
element.appendChild(proofText);
document.getElementById("submission-proof").appendChild(element);
document.getElementById("winner-instructions").style.display = "block";
}
};

71
templates/bench/prove.ts Normal file
View File

@ -0,0 +1,71 @@
/*
* mCaptcha is a PoW based DoS protection software.
* This is the frontend web component of the mCaptcha system
* Copyright © 2021 Aravinth Manivnanan <realaravinth@batsense.net>.
*
* Use of this source code is governed by Apache 2.0 or MIT license.
* You shoud have received a copy of MIT and Apache 2.0 along with
* this program. If not, see <https://spdx.org/licenses/MIT.html> for
* MIT or <http://www.apache.org/licenses/LICENSE-2.0> for Apache.
*/
import * as p from "@mcaptcha/pow_sha256-polyfill";
import { PoWConfig, SubmissionType } from "./types";
export const get_bench_type = async (): Promise<SubmissionType> => {
console.log(`Wasm support says ${WasmSupported}`);
let submission_type: SubmissionType;
if (WasmSupported) {
submission_type = SubmissionType.wasm;
} else {
submission_type = SubmissionType.js;
}
return submission_type;
};
/**
* proove work
* @param {PoWConfig} config - the proof-of-work configuration using which
* work needs to be computed
* */
const prove = async (config: PoWConfig): Promise<number> => {
console.log(`Wasm support says ${WasmSupported}`);
let duration: number;
if (WasmSupported) {
const wasm = await require("@mcaptcha/pow-wasm");
const t0 = performance.now();
wasm.gen_pow(config.salt, config.string, config.difficulty_factor);
const t1 = performance.now();
duration = t1 - t0;
} else {
console.log("WASM unsupported, expect delay during proof generation");
const t0 = performance.now();
await p.generate_work(config.salt, config.string, config.difficulty_factor);
const t1 = performance.now();
duration = t1 - t0;
}
return duration;
};
// credits: @jf-bastien on Stack Overflow
// https://stackoverflow.com/questions/47879864/how-can-i-check-if-a-browser-supports-webassembly
const WasmSupported = (() => {
try {
if (
typeof WebAssembly === "object" &&
typeof WebAssembly.instantiate === "function"
) {
const module = new WebAssembly.Module(
Uint8Array.of(0x0, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00)
);
if (module instanceof WebAssembly.Module)
return new WebAssembly.Instance(module) instanceof WebAssembly.Instance;
}
} catch (e) {
console.error(e);
}
return false;
})();
export default prove;

View File

@ -15,35 +15,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { gen_pow } from "@mcaptcha/pow-wasm";
import { Bench } from "./types";
type PoWConfig = {
string: string;
difficulty_factor: number;
salt: string;
};
import { Bench, PoWConfig } from "./types";
import prove from "./prove";
const SALT = "674243647f1c355da8607a8cdda05120d79ca5d1af8b3b49359d056a0a82";
const PHRASE = "6e2a53dbc7d307970d7ba3c0000221722cb74f1c325137251ce8fa5c2240";
const config: PoWConfig = {
string: PHRASE,
difficulty_factor: 1,
salt: SALT,
};
console.debug("worker registered");
onmessage = function (event) {
onmessage = async (event) => {
console.debug("message received at worker");
const difficulty_factor = parseInt(event.data);
config.difficulty_factor = difficulty_factor;
const config: PoWConfig = {
string: PHRASE,
difficulty_factor,
salt: SALT,
};
const t0 = performance.now();
gen_pow(config.salt, config.string, config.difficulty_factor);
const t1 = performance.now();
const duration = t1 - t0;
const duration = await prove(config);
const msg: Bench = {
difficulty: difficulty_factor,

View File

@ -25,6 +25,7 @@ export type Submission = {
device_software_recognised: String;
threads: number;
benches: Array<Bench>;
submission_type: SubmissionType;
};
export type SubmissionProof = {
@ -35,3 +36,14 @@ export type SubmissionProof = {
export type BenchConfig = {
difficulties: Array<number>;
};
export type PoWConfig = {
string: string;
difficulty_factor: number;
salt: string;
};
export enum SubmissionType {
wasm = "wasm",
js = "js",
}

View File

@ -7,26 +7,44 @@
{% block body %}
<body class="panel__body">
<main class="panel__container">
<ul>
<h2>Filters</h2>
{% if payload.js_only %}
<ol><a href="{{ payload.js_only }}">JavaScript polyfil only</a></ol>
{% endif %}
{% if payload.wasm_only %}
<ol><a href="{{ payload.wasm_only }}">WASM only</a></ol>
{% endif %}
{% if payload.all_benches %}
<ol><a href="{{ payload.all_benches }}">All Benchmarks</a></ol>
{% endif %}
</ul>
<table>
<thead>
<tr>
<th>Submission ID</th>
<th>Time (UTC)</th>
<th>User ID</th>
<th>Device make (user provided)</th>
<th>Device make (detected)</th>
<th>Threads</th>
<th>Benchmark Type</th>
<th>Benches</th>
</tr>
</thead>
<tbody>
{% for sub in payload.submissions %}
<tr>
<th>{{ sub.id }}</th>
<th>{{ sub.user.id }}</th>
<th>{{ sub.device_user_provided }}</th>
<th>{{ sub.device_software_recognised }}</th>
<th>{{ sub.threads }}</th>
<th>
<td>{{ sub.id }}</td>
<td>{{ sub.submitted_at | date(format="%Y-%m-%d %H:%M", timezone="GMT") }}</td>
<td>{{ sub.user.id }}</td>
<td>{{ sub.device_user_provided }}</td>
<td>{{ sub.device_software_recognised }}</td>
<td>{{ sub.threads }}</td>
<td>{{ sub.submission_type }}</td>
<td>
<table>
<thead>
<th>Difficulty</th>
@ -41,13 +59,14 @@
{% endfor %}
</tbody>
</table>
</th>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<a href="{{payload.next_page}}">Next ></a>
{% if payload.next_page %}
<a href="{{payload.next_page}}">Next ></a>
{% endif %}
</main>
</body>
{% endblock body %}

View File

@ -1,30 +1,30 @@
'use strict';
const path = require('path');
"use strict";
const path = require("path");
//const WasmPackPlugin = require('@wasm-tool/wasm-pack-plugin');
module.exports = {
devtool: 'inline-source-map',
mode: 'development',
devtool: "inline-source-map",
mode: "production",
//mode: 'production',
entry: {
bundle: './templates/index.ts',
bench: './templates/bench/service-worker.ts',
glue: './templates/bench/vendor.ts',
bundle: "./templates/index.ts",
bench: "./templates/bench/service-worker.ts",
glue: "./templates/bench/vendor.ts",
},
output: {
filename: '[name].js',
path: path.resolve(__dirname, './static/cache/bundle'),
filename: "[name].js",
path: path.resolve(__dirname, "./static/cache/bundle"),
},
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
loader: "ts-loader",
},
],
},
resolve: {
extensions: ['.ts', '.tsx', '.js'],
extensions: [".ts", ".tsx", ".js"],
},
experiments: {

2561
yarn.lock

File diff suppressed because it is too large Load Diff