From 2bbedb6fe1657190a1430726cf5a014b36d76f5d Mon Sep 17 00:00:00 2001 From: wsdt Date: Sun, 8 Oct 2023 15:00:11 +0200 Subject: [PATCH] Faucet documentation --- .../hc-captcha-metafaucet/DEPLOYMENT.md | 116 ++++++++++++++++++ .../hc-captcha-metafaucet/README.md | 16 ++- .../hc-captcha-metafaucet/api/.env-template | 1 - .../api/hc_sendMetaTx.js | 4 +- .../test/mainnet-faucet.ts | 28 ++++- 5 files changed, 160 insertions(+), 5 deletions(-) create mode 100644 boba_community/hc-captcha-metafaucet/DEPLOYMENT.md diff --git a/boba_community/hc-captcha-metafaucet/DEPLOYMENT.md b/boba_community/hc-captcha-metafaucet/DEPLOYMENT.md new file mode 100644 index 0000000000..f6dd8a0c82 --- /dev/null +++ b/boba_community/hc-captcha-metafaucet/DEPLOYMENT.md @@ -0,0 +1,116 @@ +# What needs to be deployed? +Relevant files are located in `./api`. + +## Lambda functions +1. hc_getCAPTCHA.py +2. hc_sendMetaTx.js +3. hc_verifyCAPTCHA.py + +### Why do we have mixed languages? +Until now we always had Python lambda functions. When implementing the last function for the `metaTx` I discovered a limitation of the `web3py` library that doesn't allow you to `estimateGas` for signed transactions / account-connected contract objects. This unfortunately makes the Python library incompatible with HybridCompute - for that reason I had to move over that single endpoint to JavaScript. + +## Redis +All 3 lambda functions need to have access to this redis instance. The Redis endpoint is configured via environment variables for each lambda function. + +# Purpose of the faucet +The faucet itself has 2 main purposes: + +1. **Testnet faucet**: To provide testnet funds (native asset & token) for developers to test their applications. + +2. **Mainnet faucet**: To provide mainnet funds (only native asset, but support for token available) which helps to onboard new users onto BOBA. + +# Purpose of the endpoints +- `hc_getCAPTCHA.py`: Returns an *UUID* and *ImageBase64* string to show a captcha to the user on the frontend (e.g. Gateway). +- `hc_sendMetaTx.js`: Returns a nonce for the user to sign and triggers a smart contract function on behalf of the user when it receives a valid signature. +- `hc_verifyCAPTCHA.py`: Called from the smart contract directly to verify if the captcha values provided are valid. + +In short, `getCAPTCHA` and `hc_sendMetaTx` are called from the Gateway or other frontends in future. + +While `verifyCAPTCHA` is being called by the Faucet Smart Contract via HybridCompute. + +# Return values +All endpoints return errors in the following way: +```json +{ +"result": {"error": "arbitrary error"} +} +``` + +## hc_getCAPTCHA.py +```json +{ +"result": {"uuid": "your uuid", "imageBase64": "base64 string img"} +} +``` + +## hc_sendMetaTx.js +There is one exception for this endpoint returning a message instead of an error when the transaction failed on-chain. + +In all other cases the endpoint returns the error property as the other endpoints do. + +```json +{"result": {"txHash": "0x00..", "message": "Transaction has failed / Funds issued"}} +``` + +## hc_verifyCAPTCHA.py +Returns abi encoded payload for HybridCompute. Handled by smart contract (faucet). + +# Configuration +Configuration values are set via `.env`variables. +There is a `.env-template` file you can use. + +## hc_getCAPTCHA.py +- `REDIS_URL` +- `REDIS_PORT` +- `IS_LOCAL` (optional), in production: `False` + +## hc_sendMetaTx.js +- `REDIS_URL` +- `REDIS_PORT` +- `PK_KEY`, private key from where to send trigger meta transactions (does not hold faucet funds) +- `RPC_URL` +- `CONTRACT_FAUCET_ADDR` (optional locally, but mandatory in production -> *security*) +- `IS_LOCAL` (optional), in production: `False` + +## hc_verifyCAPTCHA.py +- `REDIS_URL` +- `REDIS_PORT` + +# Dependencies +## Python lambda +For the Python lambda functions we've a `requirements.txt` that is usually automatically installed by AWS upon deployment. + +## JavaScript lambda +For the JS lambda function the following 3 packages need to be available: +- `ethers` +- `redis` +- `dotenv` + +# Deployment +There is a `template.yaml` for the 2 python lambda functions. We may need extend it for the JS endpoint as well as well as security relevant best practices. + +## HTTPS/HTTP +To avoid mixed content issues for the Gateway we may need to have at least `hc_getCAPTCHA.py` and `hc_sendMetaTx.js` behind *https*. + +`hc_verifyCAPTCHA.py` on the other hand might need to stay on `http` so that it can be called via HybridCompute (never actually tried it out, but I can imagine this being an issue). + +## Networks/Chains +Endpoints can all be chain agnostic. This includes Mainnet<>Testnet networks as long as if we are fine with using the same Private-Key for sending transactions for both mainnets & testnets. + +Since this PrivateKey only needs funds to send transactions (all mainnet/testnet funds are held by the smart contract) this might be acceptable. + +Right now, these only need to be deployed once for all networks: +1. hc_getCAPTCHA.py +2. hc_verifyCAPTCHA.py + +This lambda function needs to be deployed for all networks separately, since we don't want users to choose an arbitrary contract: +1. hc_sendMetaTx.js + +Thus, please make sure to set the contract address in the `.env` file. + +# Other files +- `run-local-server-*`: only used for inter-process-communication (for integration tests), don't need to be uploaded to AWS. + +# CI/CD +We may want to consider a simple pipeline to get new versions deployed or at least having a quick writeup on how to apply fixes/updates. + diff --git a/boba_community/hc-captcha-metafaucet/README.md b/boba_community/hc-captcha-metafaucet/README.md index f0a3236332..a1277af2e7 100644 --- a/boba_community/hc-captcha-metafaucet/README.md +++ b/boba_community/hc-captcha-metafaucet/README.md @@ -1,12 +1,26 @@ # Boba Mainnet Faucet Using ImageCaptcha +## Successful User flow + +```mermaid +sequenceDiagram + User->>+getCAPTCHA: {to: 0x00..} + User->>+sendMetaTx: {to: 0x00..} + sendMetaTx->>+User: {nonce: abc} + User->>+sendMetaTx: {to: 0x00.., sig: 0x00.., uuid: 123, key: abc} + sendMetaTx->>+FaucetContract: trigger getFaucet() + FaucetContract->>+verifyCAPTCHA: Receives Captcha values + verifyCAPTCHA->>+FaucetContract: Return Captcha result (0|1) + FaucetContract->>+User: Issue funds or revert +``` + ### Foundry commands Use `--no-commit --no-git` for all commands. ### Deployment - Ensure `IS_LOCAL` is set to False, otherwise the `getCAPTCHA` endpoint will return the already solved captcha! -- Ensure `CONTRACT_FAUCET_ADDR` is set to the right mainnet faucet address, otherwise the user can provide their own address. #### MetaTxApi The MetaTxAPI has been rewritten to JavaScript since the Python-Web3 library doesn't have an implementation for estimating gas for signed transactions which makes it unusable to send HybridCompute transactions. + diff --git a/boba_community/hc-captcha-metafaucet/api/.env-template b/boba_community/hc-captcha-metafaucet/api/.env-template index e20727269f..7f94cd6fb9 100644 --- a/boba_community/hc-captcha-metafaucet/api/.env-template +++ b/boba_community/hc-captcha-metafaucet/api/.env-template @@ -2,6 +2,5 @@ REDIS_URL=localhost REDIS_PORT=6379 RPC_URL=https://goerli.boba.network PK_KEY=0x.. -LOCAL_PK_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 CONTRACT_FAUCET_ADDR= IS_LOCAL=False diff --git a/boba_community/hc-captcha-metafaucet/api/hc_sendMetaTx.js b/boba_community/hc-captcha-metafaucet/api/hc_sendMetaTx.js index e91670a57b..dba3286a24 100644 --- a/boba_community/hc-captcha-metafaucet/api/hc_sendMetaTx.js +++ b/boba_community/hc-captcha-metafaucet/api/hc_sendMetaTx.js @@ -51,7 +51,7 @@ exports.handler = async function hc_sendMetaTx(event, context) { } if (!CONTRACT_FAUCET_ADDR && !body.faucetAddr) { - return await return_payload(redisClient, { error: 'No mainnet faucet address provided' }); + return await return_payload(redisClient, { error: 'No faucet address provided' }); } else if (CONTRACT_FAUCET_ADDR && body.faucetAddr) { return await return_payload(redisClient, { error: 'Faucet address already configured' }); } @@ -70,7 +70,7 @@ exports.handler = async function hc_sendMetaTx(event, context) { return await return_payload(redisClient, {error: 'Please contact support (1)'}) } const wallet = new ethers.Wallet(PK_KEY, w3); - const faucetAddr = CONTRACT_FAUCET_ADDR || body.faucetAddr; + const faucetAddr = CONTRACT_FAUCET_ADDR ?? body.faucetAddr; const faucetContract = new ethers.Contract(faucetAddr, FAUCET_ABI, wallet); try { diff --git a/boba_community/hc-captcha-metafaucet/test/mainnet-faucet.ts b/boba_community/hc-captcha-metafaucet/test/mainnet-faucet.ts index ba1eec499f..55e95c80a9 100644 --- a/boba_community/hc-captcha-metafaucet/test/mainnet-faucet.ts +++ b/boba_community/hc-captcha-metafaucet/test/mainnet-faucet.ts @@ -441,6 +441,32 @@ describe("Get gas from mainnet faucet", function () { expect(preContractBalance).to.be.eq(postContractBalance, "Faucet should have all original funds") }); + describe('local environment tests', () => { + // these tests are only applicable to local/test environments + + it('should fail when faucet address not provided at all', async () => { + await (await fetch(EndpointConfig[LocalEndpoint.sendMetaTx].url, { + body: JSON.stringify({to: deployerWallet.address}), method: 'POST', headers: { + "content-type": "application/json" + } + })).json() + + const metaRequest = await (await fetch(EndpointConfig[LocalEndpoint.sendMetaTx].url, { + // faucetAddr only needs to be defined in tests + body: JSON.stringify({ + to: deployerWallet.address, + uuid: "123", + key: "987", + sig: "0x0293cc0d4eb416ca95349b7e63dc9d1c9a7aab4865b5cd6d6f2c36fb1dce12d34a05039aedf0bc64931a439def451bcf313abbcc72e9172f7fd51ecca30b41dd1b", + }), method: 'POST', headers: { + "content-type": "application/json" + } + })).json() + + expect(metaRequest?.result?.error).to.be.eq('No faucet address provided') + }) + }) + describe('meta tx', () => { it('should not get nonce if no address provided', async () => { const metaRequest = await (await fetch(EndpointConfig[LocalEndpoint.sendMetaTx].url, { @@ -490,7 +516,7 @@ describe("Get gas from mainnet faucet", function () { } })).json() - expect(metaRequest?.result?.error).to.be.eq('No mainnet faucet address provided') + expect(metaRequest?.result?.error).to.be.eq('No faucet address provided') }) it('should get nonce to sign', async () => {