-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* wip: adding faucet to cli * wip: got a signature back but ran into more issues post getting the signature * wip: got a sig and validated, but transaction is returning bad sig * wip: manual sending of tx * fix * documentation * wip: new min amount for 1000e10 bits * clean up code; removing commented code not needed anymore * updating verifying key prop in sign request * cleaned up faucet and added retry mechanism for failed faucets; added logs as well * more cleanup * updated error logging method to accept context * remvoing export from method only used within singular file * updated sdk and removed cli installs of polkadot utils * adding tests * minor change to trigger job * wip: faucet testing * wip: more faucet testing * fixed faucet test * faucet cleanup as per pr suggestions * removing public decorator * added some comments on how to deploy to dev/readme * Update dev/README.md * remove api as argument * Update src/flows/entropyFaucet/constants.ts Co-authored-by: Frankie <frankie.diamond@gmail.com> --------- Co-authored-by: Jesse Abramowitz <jesse@entropy.xyz> Co-authored-by: Frankie <frankie.diamond@gmail.com>
- Loading branch information
1 parent
e33cf72
commit 3a3130f
Showing
16 changed files
with
320 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
// Testnet address used to deploy program on chain | ||
// Used to derive various accounts registered to faucet program in order to be used for | ||
// issuing Faucet Funds | ||
export const FAUCET_PROGRAM_MOD_KEY = '5GWamxgW4XWcwGsrUynqnFq2oNZPqNXQhMDfgNH9xNsg2Yj7' | ||
// Faucet program pointer | ||
// To-DO: Look into deriving this program from owned programs of Faucet Program Mod Acct | ||
// this is differnt from tests because the fauce that is live now was lazily deployed without schemas | ||
// TO-DO: update this when faucet is deployed properly | ||
export const TESTNET_PROGRAM_HASH = '0x12af0bd1f2d91f12e34aeb07ea622c315dbc3c2bdc1e25ff98c23f1e61106c77' | ||
export const LOCAL_PROGRAM_HASH = '0x5fa0536818acaa380b0c349c8e887bf269d593a47e30c8e31de53a75d327f7b1' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// check verifying key has the balance and proper program hash | ||
|
||
import Entropy from "@entropyxyz/sdk"; | ||
import { blake2AsHex, encodeAddress } from "@polkadot/util-crypto"; | ||
import { getBalance } from "../balance/balance"; | ||
import { viewPrograms } from "../programs/view"; | ||
import FaucetSigner from "./signer"; | ||
import { FAUCET_PROGRAM_MOD_KEY, TESTNET_PROGRAM_HASH } from "./constants"; | ||
|
||
// only the faucet program should be on the key | ||
async function faucetSignAndSend (call: any, entropy: Entropy, amount: number, senderAddress: string, chosenVerifyingKey: any): Promise<any> { | ||
const api = entropy.substrate | ||
const faucetSigner = new FaucetSigner(api.registry, entropy, amount, chosenVerifyingKey) | ||
|
||
const sig = await call.signAsync(senderAddress, { | ||
signer: faucetSigner, | ||
}); | ||
return new Promise((resolve, reject) => { | ||
sig.send(({ status, dispatchError }: any) => { | ||
// status would still be set, but in the case of error we can shortcut | ||
// to just check it (so an error would indicate InBlock or Finalized) | ||
if (dispatchError) { | ||
let msg: string | ||
if (dispatchError.isModule) { | ||
// for module errors, we have the section indexed, lookup | ||
const decoded = api.registry.findMetaError(dispatchError.asModule); | ||
// @ts-ignore | ||
const { documentation, method, section } = decoded; | ||
|
||
msg = `${section}.${method}: ${documentation.join(' ')}` | ||
} else { | ||
// Other, CannotLookup, BadOrigin, no extra info | ||
msg = dispatchError.toString() | ||
} | ||
return reject(Error(msg)) | ||
} | ||
if (status.isFinalized) resolve(status) | ||
}) | ||
}) | ||
} | ||
|
||
export async function getRandomFaucet (entropy: Entropy, previousVerifyingKeys: string[] = [], programModKey = FAUCET_PROGRAM_MOD_KEY) { | ||
const modifiableKeys = await entropy.substrate.query.registry.modifiableKeys(programModKey) | ||
const verifyingKeys = JSON.parse(JSON.stringify(modifiableKeys.toJSON())) | ||
|
||
// Choosing one of the 5 verifiying keys at random to be used as the faucet sender | ||
if (verifyingKeys.length === previousVerifyingKeys.length) { | ||
throw new Error('FaucetError: There are no more faucets to choose from') | ||
} | ||
let chosenVerifyingKey = verifyingKeys[Math.floor(Math.random() * verifyingKeys.length)] | ||
if (previousVerifyingKeys.length && previousVerifyingKeys.includes(chosenVerifyingKey)) { | ||
const filteredVerifyingKeys = verifyingKeys.filter((key: string) => !previousVerifyingKeys.includes(key)) | ||
chosenVerifyingKey = filteredVerifyingKeys[Math.floor(Math.random() * filteredVerifyingKeys.length)] | ||
} | ||
const hashedKey = blake2AsHex(chosenVerifyingKey) | ||
const faucetAddress = encodeAddress(hashedKey, 42).toString() | ||
|
||
return { chosenVerifyingKey, faucetAddress, verifyingKeys } | ||
} | ||
|
||
export async function sendMoney ( | ||
entropy: Entropy, | ||
{ | ||
amount, | ||
addressToSendTo, | ||
faucetAddress, | ||
chosenVerifyingKey, | ||
faucetProgramPointer = TESTNET_PROGRAM_HASH | ||
}: { | ||
amount: string, | ||
addressToSendTo: string, | ||
faucetAddress: string, | ||
chosenVerifyingKey: string, | ||
faucetProgramPointer: string | ||
} | ||
): Promise<any> { | ||
// check balance of faucet address | ||
const balance = await getBalance(entropy, faucetAddress) | ||
if (balance <= 0) throw new Error('FundsError: Faucet Account does not have funds') | ||
// check verifying key for only one program matching the program hash | ||
const programs = await viewPrograms(entropy, { verifyingKey: chosenVerifyingKey }) | ||
if (programs.length) { | ||
if (programs.length > 1) throw new Error('ProgramsError: Faucet Account has too many programs attached, expected less') | ||
if (programs.length === 1 && programs[0].program_pointer !== faucetProgramPointer) { | ||
throw new Error('ProgramsError: Faucet Account does not possess Faucet program') | ||
} | ||
} else { | ||
throw new Error('ProgramsError: Faucet Account has no programs attached') | ||
} | ||
|
||
const transfer = entropy.substrate.tx.balances.transferAllowDeath(addressToSendTo, BigInt(amount)); | ||
const transferStatus = await faucetSignAndSend(transfer, entropy, parseInt(amount), faucetAddress, chosenVerifyingKey ) | ||
if (transferStatus.isFinalized) return transferStatus | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,48 +1,45 @@ | ||
import inquirer from "inquirer" | ||
import { print, accountChoices } from "../../common/utils" | ||
import Entropy from "@entropyxyz/sdk" | ||
import { getSelectedAccount, print } from "../../common/utils" | ||
import { initializeEntropy } from "../../common/initializeEntropy" | ||
|
||
export async function entropyFaucet ({ accounts }, options) { | ||
import { EntropyLogger } from '../../common/logger' | ||
import { getRandomFaucet, sendMoney } from "./faucet" | ||
import { TESTNET_PROGRAM_HASH } from "./constants" | ||
|
||
let chosenVerifyingKeys = [] | ||
export async function entropyFaucet ({ accounts, selectedAccount: selectedAccountAddress }, options, logger: EntropyLogger) { | ||
const FLOW_CONTEXT = 'ENTROPY_FAUCET' | ||
let faucetAddress | ||
let chosenVerifyingKey | ||
let entropy: Entropy | ||
let verifyingKeys: string[] = [] | ||
const amount = "10000000000" | ||
const { endpoint } = options | ||
const selectedAccount = getSelectedAccount(accounts, selectedAccountAddress) | ||
logger.log(`selectedAccount::`, FLOW_CONTEXT) | ||
logger.log(selectedAccount, FLOW_CONTEXT) | ||
try { | ||
// @ts-ignore (see TODO on aliceAccount) | ||
entropy = await initializeEntropy({ keyMaterial: selectedAccount.data, endpoint }) | ||
|
||
if (!entropy.registrationManager.signer.pair) { | ||
throw new Error("Keys are undefined") | ||
} | ||
|
||
const accountQuestion = { | ||
type: "list", | ||
name: "selectedAccount", | ||
message: "Choose account:", | ||
choices: accountChoices(accounts), | ||
} | ||
|
||
const answers = await inquirer.prompt([accountQuestion]) | ||
const selectedAccount = answers.selectedAccount | ||
|
||
const recipientAddress = selectedAccount.address | ||
const aliceAccount = { | ||
data: { | ||
// type: "seed", | ||
seed: "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a", | ||
// admin TODO: missing this field | ||
}, | ||
} | ||
|
||
// @ts-ignore (see TODO on aliceAccount) | ||
const entropy = await initializeEntropy({ keyMaterial: aliceAccount.data, endpoint }) | ||
|
||
if (!entropy.registrationManager.signer.pair) { | ||
throw new Error("Keys are undefined") | ||
} | ||
|
||
const amount = "10000000000000000" | ||
const tx = entropy.substrate.tx.balances.transferAllowDeath( | ||
recipientAddress, | ||
amount | ||
) | ||
|
||
await tx.signAndSend( | ||
entropy.registrationManager.signer.pair, | ||
async ({ status }) => { | ||
if (status.isInBlock || status.isFinalized) { | ||
print(recipientAddress, "funded") | ||
} | ||
({ chosenVerifyingKey, faucetAddress, verifyingKeys } = await getRandomFaucet(entropy, chosenVerifyingKeys)) | ||
|
||
await sendMoney(entropy, { amount, addressToSendTo: selectedAccountAddress, faucetAddress, chosenVerifyingKey, faucetProgramPointer: TESTNET_PROGRAM_HASH }) | ||
// reset chosen keys after successful transfer | ||
chosenVerifyingKeys = [] | ||
print(`Account: ${selectedAccountAddress} has been successfully funded with ${parseInt(amount).toLocaleString('en-US')} BITS`) | ||
} catch (error) { | ||
logger.error('Error issuing funds through faucet', error, FLOW_CONTEXT) | ||
chosenVerifyingKeys.push(chosenVerifyingKey) | ||
if (error.message.includes('FaucetError') || chosenVerifyingKeys.length === verifyingKeys.length) { | ||
console.error('ERR::', error.message) | ||
return | ||
} else { | ||
// Check for non faucet errors (FaucetError) and retry faucet | ||
await entropyFaucet({ accounts, selectedAccount: selectedAccountAddress }, options, logger) | ||
} | ||
) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import Entropy from "@entropyxyz/sdk"; | ||
import type { Signer, SignerResult } from "@polkadot/api/types"; | ||
import { Registry, SignerPayloadJSON } from "@polkadot/types/types"; | ||
import { u8aToHex } from "@polkadot/util"; | ||
import { stripHexPrefix } from "../../common/utils"; | ||
import { blake2AsHex, decodeAddress, encodeAddress, signatureVerify } from "@polkadot/util-crypto"; | ||
|
||
let id = 0 | ||
export default class FaucetSigner implements Signer { | ||
readonly #registry: Registry | ||
readonly #entropy: Entropy | ||
readonly amount: number | ||
readonly chosenVerifyingKey: any | ||
readonly globalTest: any | ||
|
||
constructor ( | ||
registry: Registry, | ||
entropy: Entropy, | ||
amount: number, | ||
chosenVerifyingKey: any, | ||
) { | ||
this.#registry = registry | ||
this.#entropy = entropy | ||
this.amount = amount | ||
this.chosenVerifyingKey = chosenVerifyingKey | ||
} | ||
|
||
async signPayload (payload: SignerPayloadJSON): Promise<SignerResult> { | ||
// toU8a(true) is important as it strips the scale encoding length prefix from the payload | ||
// without it transactions will fail | ||
// ref: https://github.com/polkadot-js/api/issues/4446#issuecomment-1013213962 | ||
const raw = this.#registry.createType('ExtrinsicPayload', payload, { | ||
version: payload.version, | ||
}).toU8a(true); | ||
|
||
const auxData = { | ||
spec_version: 100, | ||
transaction_version: 6, | ||
string_account_id: this.#entropy.keyring.accounts.registration.address, | ||
amount: this.amount | ||
} | ||
|
||
const signature = await this.#entropy.sign({ | ||
sigRequestHash: u8aToHex(raw), | ||
// @ts-ignore | ||
hash: {custom: 0}, | ||
auxiliaryData: [auxData], | ||
signatureVerifyingKey: this.chosenVerifyingKey | ||
}) | ||
|
||
let sigHex = u8aToHex(signature); | ||
// the 02 prefix is needed for signature type edcsa (00 = ed25519, 01 = sr25519, 02 = ecdsa) | ||
// ref: https://github.com/polkadot-js/tools/issues/175#issuecomment-767496439 | ||
sigHex = `0x02${stripHexPrefix(sigHex)}` | ||
|
||
const hashedKey = blake2AsHex(this.chosenVerifyingKey) | ||
const faucetAddress = encodeAddress(hashedKey) | ||
const publicKey = decodeAddress(faucetAddress); | ||
|
||
const hexPublicKey = u8aToHex(publicKey); | ||
|
||
const signatureValidation = signatureVerify(u8aToHex(raw), sigHex, hexPublicKey) | ||
|
||
if (signatureValidation.isValid) { | ||
return { id: id++, signature: sigHex } | ||
} else { | ||
throw new Error('FaucetSignerError: Signature is not valid') | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.