diff --git a/CHANGELOG.md b/CHANGELOG.md index dadf1323e..a198341af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,13 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased](https://github.com/o1-labs/o1js/compare/08ba27329...HEAD) +### Breaking changes + +- `Mina.accountCreationFee()` is deprecated in favor of `Mina.getNetworkConstants().accountCreationFee`. https://github.com/o1-labs/o1js/pull/1367 + - `Mina.getNetworkConstants()` returns: + - [default](https://github.com/o1-labs/o1js/pull/1367/files#diff-ef2c3547d64a8eaa8253cd82b3623288f3271e14f1dc893a0a3ddc1ff4b9688fR7) network constants if used outside of the transaction scope. + - [actual](https://github.com/o1-labs/o1js/pull/1367/files#diff-437f2c15df7c90ad8154c5de1677ec0838d51859bcc0a0cefd8a0424b5736f31R1051) network constants if used within the transaction scope. + ### Added - **SHA256 hash function** exposed via `Hash.SHA2_256` or `Gadgets.SHA256`. https://github.com/o1-labs/o1js/pull/1285 diff --git a/run-ci-live-tests.sh b/run-ci-live-tests.sh index ba881bb5c..192041b54 100755 --- a/run-ci-live-tests.sh +++ b/run-ci-live-tests.sh @@ -17,6 +17,8 @@ echo "" HELLO_WORLD_PROC=$! ./run src/examples/zkapps/dex/run_live.ts --bundle | add_prefix "DEX" & DEX_PROC=$! +./run src/examples/fetch_live.ts --bundle | add_prefix "FETCH" & +FETCH_PROC=$! # Wait for each process and capture their exit statuses FAILURE=0 @@ -34,6 +36,13 @@ if [ $? -ne 0 ]; then echo "" FAILURE=1 fi +wait $FETCH_PROC +if [ $? -ne 0 ]; then + echo "" + echo "FETCH test failed." + echo "" + FAILURE=1 +fi # Exit with failure if any process failed if [ $FAILURE -ne 0 ]; then diff --git a/src/examples/fetch_live.ts b/src/examples/fetch_live.ts new file mode 100644 index 000000000..85ce4b8d4 --- /dev/null +++ b/src/examples/fetch_live.ts @@ -0,0 +1,92 @@ +import { expect } from 'expect'; +import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js'; + +const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true'; +Mina.setActiveInstance(configureMinaNetwork()); +const transactionFee = 100_000_000; +let defaultNetworkConstants: Mina.NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(1_000_000_000), +}; +const { sender } = await configureFeePayer(); + +await checkDefaultNetworkConstantsFetching(); +await checkActualNetworkConstantsFetching(); + +await tearDown(); + +async function checkDefaultNetworkConstantsFetching() { + console.log( + '\nCase #1: check that default network constants can be fetched outside of the transaction scope.' + ); + const networkConstants = Mina.getNetworkConstants(); + expect(defaultNetworkConstants).toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +async function checkActualNetworkConstantsFetching() { + console.log( + '\nCase #2: check that actual network constants can be fetched within the transaction scope.' + ); + let networkConstants: Mina.NetworkConstants | undefined; + await Mina.transaction({ sender, fee: transactionFee }, () => { + networkConstants = Mina.getNetworkConstants(); + }); + expect(networkConstants?.slotTime).not.toBeUndefined(); + expect(networkConstants?.genesisTimestamp).not.toBeUndefined(); + expect(defaultNetworkConstants).not.toEqual(networkConstants); + logNetworkConstants(networkConstants); +} + +function configureMinaNetwork() { + const minaGraphQlEndpoint = useCustomLocalNetwork + ? 'http://localhost:8080/graphql' + : 'https://berkeley.minascan.io/graphql'; + return Mina.Network({ + mina: minaGraphQlEndpoint, + archive: useCustomLocalNetwork + ? 'http://localhost:8282' + : 'https://api.minascan.io/archive/berkeley/v1/graphql', + lightnetAccountManager: 'http://localhost:8181', + }); +} + +async function configureFeePayer() { + const senderKey = useCustomLocalNetwork + ? (await Lightnet.acquireKeyPair()).privateKey + : PrivateKey.random(); + const sender = senderKey.toPublicKey(); + if (!useCustomLocalNetwork) { + console.log(`\nFunding the fee payer account.`); + await Mina.faucet(sender); + } + console.log(`\nFetching the fee payer account information.`); + const accountDetails = (await fetchAccount({ publicKey: sender })).account; + console.log( + `Using the fee payer account ${sender.toBase58()} with nonce: ${ + accountDetails?.nonce + } and balance: ${accountDetails?.balance}.` + ); + return { sender, senderKey }; +} + +async function tearDown() { + const keyPairReleaseMessage = await Lightnet.releaseKeyPair({ + publicKey: sender.toBase58(), + }); + if (keyPairReleaseMessage) console.info('\n' + keyPairReleaseMessage); +} + +function logNetworkConstants( + networkConstants: Mina.NetworkConstants | undefined +) { + console.log(`Account creation fee: ${networkConstants?.accountCreationFee}`); + console.log(`Slot time: ${networkConstants?.slotTime}`); + console.log(`Genesis timestamp: ${networkConstants?.genesisTimestamp}`); + console.log( + `Genesis date: ${new Date( + Number(networkConstants?.genesisTimestamp.toString() ?? '0') + )}` + ); +} diff --git a/src/examples/zkapps/dex/arbitrary_token_interaction.ts b/src/examples/zkapps/dex/arbitrary_token_interaction.ts index aaf7d8155..182beb95f 100644 --- a/src/examples/zkapps/dex/arbitrary_token_interaction.ts +++ b/src/examples/zkapps/dex/arbitrary_token_interaction.ts @@ -4,7 +4,6 @@ import { TokenContract, addresses, keys, tokenIds } from './dex.js'; let doProofs = true; let Local = Mina.LocalBlockchain({ proofsEnabled: doProofs }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: userKey, publicKey: userAddress }] = Local.testAccounts; let tx; @@ -26,7 +25,9 @@ console.log('deploy & init token contracts...'); tx = await Mina.transaction(userAddress, () => { // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(userAddress); - feePayerUpdate.balance.subInPlace(accountFee.mul(1)); + feePayerUpdate.balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(1) + ); tokenX.deploy(); }); await tx.prove(); @@ -36,7 +37,9 @@ await tx.send(); console.log('arbitrary token minting...'); tx = await Mina.transaction(userAddress, () => { // pay fees for creating user's token X account - AccountUpdate.createSigned(userAddress).balance.subInPlace(accountFee.mul(1)); + AccountUpdate.createSigned(userAddress).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(1) + ); // 😈😈😈 mint any number of tokens to our account 😈😈😈 let tokenContract = new TokenContract(addresses.tokenX); tokenContract.token.mint({ diff --git a/src/examples/zkapps/dex/dex.ts b/src/examples/zkapps/dex/dex.ts index 44ac6b517..e4f963bb4 100644 --- a/src/examples/zkapps/dex/dex.ts +++ b/src/examples/zkapps/dex/dex.ts @@ -394,7 +394,7 @@ class TokenContract extends SmartContract { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } /** @@ -410,7 +410,7 @@ class TokenContract extends SmartContract { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); } // this is a very standardized deploy method. instead, we could also take the account update from a callback diff --git a/src/examples/zkapps/dex/erc20.ts b/src/examples/zkapps/dex/erc20.ts index b18ba43de..9648640f8 100644 --- a/src/examples/zkapps/dex/erc20.ts +++ b/src/examples/zkapps/dex/erc20.ts @@ -85,7 +85,7 @@ class TrivialCoin extends SmartContract implements Erc20 { // assert that the receiving account is new, so this can be only done once receiver.account.isNew.requireEquals(Bool(true)); // pay fees for opened account - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); // since this is the only method of this zkApp that resets the entire state, provedState: true implies // that this function was run. Since it can be run only once, this implies it was run exactly once diff --git a/src/examples/zkapps/dex/happy-path-with-actions.ts b/src/examples/zkapps/dex/happy-path-with-actions.ts index 16ea8fddc..f7effd336 100644 --- a/src/examples/zkapps/dex/happy-path-with-actions.ts +++ b/src/examples/zkapps/dex/happy-path-with-actions.ts @@ -19,7 +19,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: true, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -44,6 +43,7 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -61,7 +61,7 @@ tic('deploy dex contracts'); tx = await Mina.transaction(feePayerAddress, () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); dex.deploy(); dexTokenHolderX.deploy(); diff --git a/src/examples/zkapps/dex/happy-path-with-proofs.ts b/src/examples/zkapps/dex/happy-path-with-proofs.ts index 472785101..3922f82c3 100644 --- a/src/examples/zkapps/dex/happy-path-with-proofs.ts +++ b/src/examples/zkapps/dex/happy-path-with-proofs.ts @@ -15,7 +15,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -46,6 +45,7 @@ let dexTokenHolderY = new DexTokenHolder(addresses.dex, tokenIds.Y); tic('deploy & init token contracts'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -63,7 +63,7 @@ tic('deploy dex contracts'); tx = await Mina.transaction(feePayerAddress, () => { // pay fees for creating 3 dex accounts AccountUpdate.createSigned(feePayerAddress).balance.subInPlace( - accountFee.mul(3) + Mina.getNetworkConstants().accountCreationFee.mul(3) ); dex.deploy(); dexTokenHolderX.deploy(); diff --git a/src/examples/zkapps/dex/run.ts b/src/examples/zkapps/dex/run.ts index e9f15fad2..bb50b2c11 100644 --- a/src/examples/zkapps/dex/run.ts +++ b/src/examples/zkapps/dex/run.ts @@ -9,7 +9,6 @@ let Local = Mina.LocalBlockchain({ enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); -let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -77,6 +76,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); @@ -110,7 +110,10 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { let feePayer = AccountUpdate.fundNewAccount(feePayerAddress, 4); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees @@ -133,7 +136,10 @@ async function main({ withVesting }: { withVesting: boolean }) { // supply the initial liquidity where the token ratio can be arbitrary console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee, + }, () => { AccountUpdate.fundNewAccount(feePayerAddress); dex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); @@ -437,7 +443,7 @@ async function main({ withVesting }: { withVesting: boolean }) { ); tx = await Mina.transaction(addresses.user2, () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); dex.redeemLiquidity(UInt64.from(USER_DL)); dex.redeemLiquidity(UInt64.from(USER_DL)); @@ -451,7 +457,7 @@ async function main({ withVesting }: { withVesting: boolean }) { console.log('user2 redeem liquidity'); tx = await Mina.transaction(addresses.user2, () => { AccountUpdate.createSigned(addresses.user2).balance.subInPlace( - accountFee.mul(2) + Mina.getNetworkConstants().accountCreationFee.mul(2) ); dex.redeemLiquidity(UInt64.from(USER_DL)); }); diff --git a/src/examples/zkapps/dex/run_live.ts b/src/examples/zkapps/dex/run_live.ts index db9336fcc..256ba5f87 100644 --- a/src/examples/zkapps/dex/run_live.ts +++ b/src/examples/zkapps/dex/run_live.ts @@ -34,7 +34,6 @@ const network = Mina.Network({ lightnetAccountManager: 'http://localhost:8181', }); Mina.setActiveInstance(network); -let accountFee = Mina.accountCreationFee(); let tx, pendingTx: Mina.TransactionId, balances, oldBalances; @@ -74,6 +73,7 @@ let userSpec = { sender: addresses.user, fee: 0.1e9 }; if (successfulTransactions <= 0) { tic('deploy & init token contracts'); tx = await Mina.transaction(senderSpec, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 1 account themselves let feePayerUpdate = AccountUpdate.createSigned(sender); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -97,7 +97,9 @@ if (successfulTransactions <= 1) { tic('deploy dex contracts'); tx = await Mina.transaction(senderSpec, () => { // pay fees for creating 3 dex accounts - AccountUpdate.createSigned(sender).balance.subInPlace(accountFee.mul(3)); + AccountUpdate.createSigned(sender).balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(3) + ); dex.deploy(); dexTokenHolderX.deploy(); tokenX.approveUpdate(dexTokenHolderX.self); diff --git a/src/examples/zkapps/dex/upgradability.ts b/src/examples/zkapps/dex/upgradability.ts index 273431c90..bf44ac97e 100644 --- a/src/examples/zkapps/dex/upgradability.ts +++ b/src/examples/zkapps/dex/upgradability.ts @@ -23,7 +23,6 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances; @@ -51,6 +50,7 @@ async function atomicActionsTest({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.fundNewAccount(feePayerAddress, 2); feePayerUpdate.send({ to: addresses.tokenX, amount: accountFee.mul(2) }); @@ -227,7 +227,6 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { enforceTransactionLimits: false, }); Mina.setActiveInstance(Local); - let accountFee = Mina.accountCreationFee(); let [{ privateKey: feePayerKey, publicKey: feePayerAddress }] = Local.testAccounts; let tx, balances, oldBalances; @@ -259,6 +258,7 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('deploy & init token contracts...'); tx = await Mina.transaction(feePayerAddress, () => { + const accountFee = Mina.getNetworkConstants().accountCreationFee; // pay fees for creating 2 token contract accounts, and fund them so each can create 2 accounts themselves let feePayerUpdate = AccountUpdate.createSigned(feePayerAddress); feePayerUpdate.balance.subInPlace(accountFee.mul(2)); @@ -309,10 +309,15 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('transfer tokens to user'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { let feePayer = AccountUpdate.createSigned(feePayerAddress); - feePayer.balance.subInPlace(Mina.accountCreationFee().mul(4)); + feePayer.balance.subInPlace( + Mina.getNetworkConstants().accountCreationFee.mul(4) + ); feePayer.send({ to: addresses.user, amount: 20e9 }); // give users MINA to pay fees feePayer.send({ to: addresses.user2, amount: 20e9 }); // transfer to fee payer so they can provide initial liquidity @@ -364,7 +369,10 @@ async function upgradeabilityTests({ withVesting }: { withVesting: boolean }) { console.log('supply liquidity -- base'); tx = await Mina.transaction( - { sender: feePayerAddress, fee: accountFee.mul(1) }, + { + sender: feePayerAddress, + fee: Mina.getNetworkConstants().accountCreationFee.mul(1), + }, () => { AccountUpdate.fundNewAccount(feePayerAddress); modifiedDex.supplyLiquidityBase(UInt64.from(10_000), UInt64.from(10_000)); diff --git a/src/lib/account_update.ts b/src/lib/account_update.ts index b24b32354..714fba42c 100644 --- a/src/lib/account_update.ts +++ b/src/lib/account_update.ts @@ -1273,7 +1273,7 @@ class AccountUpdate implements Types.AccountUpdate { ) { let accountUpdate = AccountUpdate.createSigned(feePayer as PrivateKey); accountUpdate.label = 'AccountUpdate.fundNewAccount()'; - let fee = Mina.accountCreationFee(); + let fee = Mina.getNetworkConstants().accountCreationFee; numberOfAccounts ??= 1; if (typeof numberOfAccounts === 'number') fee = fee.mul(numberOfAccounts); else fee = fee.add(UInt64.from(numberOfAccounts.initialBalance ?? 0)); diff --git a/src/lib/caller.unit-test.ts b/src/lib/caller.unit-test.ts index 1c6508dcd..47e242de6 100644 --- a/src/lib/caller.unit-test.ts +++ b/src/lib/caller.unit-test.ts @@ -17,7 +17,7 @@ let parentId = TokenId.derive(publicKey); let tx = await Mina.transaction(privateKey, () => { let parent = AccountUpdate.defaultAccountUpdate(publicKey); parent.body.mayUseToken = AccountUpdate.MayUseToken.InheritFromParent; - parent.balance.subInPlace(Mina.accountCreationFee()); + parent.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); let child = AccountUpdate.defaultAccountUpdate(publicKey, parentId); child.body.mayUseToken = AccountUpdate.MayUseToken.ParentsOwnToken; diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index 88b87c7e8..f1d39ef8b 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -19,6 +19,7 @@ import { export { fetchAccount, fetchLastBlock, + fetchGenesisConstants, checkZkappTransaction, parseFetchedAccount, markAccountToBeFetched, @@ -31,6 +32,7 @@ export { getCachedAccount, getCachedNetwork, getCachedActions, + getCachedGenesisConstants, addCachedAccount, networkConfig, setGraphqlEndpoint, @@ -44,7 +46,8 @@ export { removeJsonQuotes, fetchEvents, fetchActions, - Lightnet + Lightnet, + type GenesisConstants, }; type NetworkConfig = { @@ -216,6 +219,16 @@ type FetchError = { type ActionStatesStringified = { [K in keyof ActionStates]: string; }; +type GenesisConstants = { + genesisTimestamp: string; + coinbase: number; + accountCreationFee: number; + epochDuration: number; + k: number; + slotDuration: number; + slotsPerEpoch: number; +}; + // Specify 5min as the default timeout const defaultTimeout = 5 * 60 * 1000; @@ -257,6 +270,7 @@ let actionsToFetch = {} as Record< graphqlEndpoint: string; } >; +let genesisConstantsCache = {} as Record; function markAccountToBeFetched( publicKey: PublicKey, @@ -333,6 +347,7 @@ async function fetchMissingData( (async () => { try { await fetchLastBlock(graphqlEndpoint); + await fetchGenesisConstants(graphqlEndpoint); delete networksToFetch[network[0]]; } catch {} })() @@ -363,6 +378,12 @@ function getCachedActions( ?.actions; } +function getCachedGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): GenesisConstants { + return genesisConstantsCache[graphqlEndpoint]; +} + /** * Adds an account to the local cache, indexed by a GraphQL endpoint. */ @@ -819,6 +840,21 @@ const getActionsQuery = ( } }`; }; +const genesisConstantsQuery = `{ + genesisConstants { + genesisTimestamp + coinbase + accountCreationFee + } + daemonStatus { + consensusConfiguration { + epochDuration + k + slotDuration + slotsPerEpoch + } + } + }`; /** * Asynchronously fetches event data for an account from the Mina Archive Node GraphQL API. @@ -1009,6 +1045,37 @@ async function fetchActions( return actionsList; } +/** + * Fetches genesis constants. + */ +async function fetchGenesisConstants( + graphqlEndpoint = networkConfig.minaEndpoint +): Promise { + let [resp, error] = await makeGraphqlRequest( + genesisConstantsQuery, + graphqlEndpoint, + networkConfig.minaFallbackEndpoints + ); + if (error) throw Error(error.statusText); + const genesisConstants = resp?.data?.genesisConstants; + const consensusConfiguration = + resp?.data?.daemonStatus?.consensusConfiguration; + if (genesisConstants === undefined || consensusConfiguration === undefined) { + throw Error('Failed to fetch genesis constants.'); + } + const data = { + genesisTimestamp: genesisConstants.genesisTimestamp, + coinbase: Number(genesisConstants.coinbase), + accountCreationFee: Number(genesisConstants.accountCreationFee), + epochDuration: Number(consensusConfiguration.epochDuration), + k: Number(consensusConfiguration.k), + slotDuration: Number(consensusConfiguration.slotDuration), + slotsPerEpoch: Number(consensusConfiguration.slotsPerEpoch), + }; + genesisConstantsCache[graphqlEndpoint] = data; + return data as GenesisConstants; +} + namespace Lightnet { /** * Gets random key pair (public and private keys) from account manager diff --git a/src/lib/mina.ts b/src/lib/mina.ts index e3b5823a9..bcc257380 100644 --- a/src/lib/mina.ts +++ b/src/lib/mina.ts @@ -51,6 +51,7 @@ export { getAccount, hasAccount, getBalance, + getNetworkConstants, getNetworkState, accountCreationFee, sendTransaction, @@ -64,7 +65,9 @@ export { getProofsEnabled, // for internal testing only filterGroups, + type NetworkConstants }; + interface TransactionId { isSuccess: boolean; wait(options?: { maxAttempts?: number; interval?: number }): Promise; @@ -163,6 +166,15 @@ type ActionStates = { endActionState?: Field; }; +type NetworkConstants = { + genesisTimestamp: UInt64; + /** + * Duration of 1 slot in milliseconds + */ + slotTime: UInt64; + accountCreationFee: UInt64; +}; + function reportGetAccountError(publicKey: string, tokenId: string) { if (tokenId === TokenId.toBase58(TokenId.default)) { return `getAccount: Could not find account for public key ${publicKey}`; @@ -340,14 +352,10 @@ interface Mina { hasAccount(publicKey: PublicKey, tokenId?: Field): boolean; getAccount(publicKey: PublicKey, tokenId?: Field): Account; getNetworkState(): NetworkValue; - getNetworkConstants(): { - genesisTimestamp: UInt64; - /** - * Duration of 1 slot in millisecondw - */ - slotTime: UInt64; - accountCreationFee: UInt64; - }; + getNetworkConstants(): NetworkConstants; + /** + * @deprecated use {@link getNetworkConstants} + */ accountCreationFee(): UInt64; sendTransaction(transaction: Transaction): Promise; fetchEvents: ( @@ -369,21 +377,23 @@ interface Mina { } const defaultAccountCreationFee = 1_000_000_000; +const defaultNetworkConstants: NetworkConstants = { + genesisTimestamp: UInt64.from(0), + slotTime: UInt64.from(3 * 60 * 1000), + accountCreationFee: UInt64.from(defaultAccountCreationFee), +}; /** * A mock Mina blockchain running locally and useful for testing. */ function LocalBlockchain({ - accountCreationFee = defaultAccountCreationFee as string | number, proofsEnabled = true, enforceTransactionLimits = true, } = {}) { const slotTime = 3 * 60 * 1000; const startTime = Date.now(); const genesisTimestamp = UInt64.from(startTime); - const ledger = Ledger.create(); - let networkState = defaultNetworkState(); function addAccount(publicKey: PublicKey, balance: string) { @@ -412,12 +422,14 @@ function LocalBlockchain({ return { proofsEnabled, - accountCreationFee: () => UInt64.from(accountCreationFee), + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { return { + ...defaultNetworkConstants, genesisTimestamp, - accountCreationFee: UInt64.from(accountCreationFee), - slotTime: UInt64.from(slotTime), }; }, currentSlot() { @@ -490,7 +502,7 @@ function LocalBlockchain({ try { ledger.applyJsonTransaction( JSON.stringify(zkappCommandJson), - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); } catch (err: any) { @@ -499,7 +511,7 @@ function LocalBlockchain({ // TODO: label updates, and try to give precise explanations about what went wrong let errors = JSON.parse(err.message); err.message = invalidTransactionError(txn.transaction, errors, { - accountCreationFee, + accountCreationFee: defaultNetworkConstants.accountCreationFee.toString(), }); } finally { throw err; @@ -601,7 +613,7 @@ function LocalBlockchain({ applyJsonTransaction(json: string) { return ledger.applyJsonTransaction( json, - String(accountCreationFee), + defaultNetworkConstants.accountCreationFee.toString(), JSON.stringify(networkState) ); }, @@ -691,7 +703,6 @@ function Network( } | string ): Mina { - let accountCreationFee = UInt64.from(defaultAccountCreationFee); let minaGraphqlEndpoint: string; let archiveEndpoint: string; let lightnetAccountManagerEndpoint: string; @@ -737,22 +748,30 @@ function Network( ); } - // copied from mina/genesis_ledgers/berkeley.json - // TODO fetch from graphql instead of hardcoding - const genesisTimestampString = '2023-02-23T20:00:01Z'; - const genesisTimestamp = UInt64.from( - Date.parse(genesisTimestampString.slice(0, -1) + '+00:00') - ); - // TODO also fetch from graphql - const slotTime = UInt64.from(3 * 60 * 1000); return { - accountCreationFee: () => accountCreationFee, + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { - return { - genesisTimestamp, - slotTime, - accountCreationFee, - }; + if (currentTransaction()?.fetchMode === 'test') { + Fetch.markNetworkToBeFetched(minaGraphqlEndpoint); + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + return genesisConstants !== undefined + ? genesisToNetworkConstants(genesisConstants) + : defaultNetworkConstants; + } + if ( + !currentTransaction.has() || + currentTransaction.get().fetchMode === 'cached' + ) { + const genesisConstants = + Fetch.getCachedGenesisConstants(minaGraphqlEndpoint); + if (genesisConstants !== undefined) + return genesisToNetworkConstants(genesisConstants); + } + return defaultNetworkConstants; }, currentSlot() { throw Error( @@ -814,7 +833,7 @@ function Network( if (network !== undefined) return network; } throw Error( - `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint}` + `getNetworkState: Could not fetch network state from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.` ); }, async sendTransaction(txn: Transaction) { @@ -994,9 +1013,12 @@ function BerkeleyQANet(graphqlEndpoint: string) { } let activeInstance: Mina = { - accountCreationFee: () => UInt64.from(defaultAccountCreationFee), + /** + * @deprecated use {@link Mina.getNetworkConstants} + */ + accountCreationFee: () => defaultNetworkConstants.accountCreationFee, getNetworkConstants() { - throw new Error('must call Mina.setActiveInstance first'); + return defaultNetworkConstants; }, currentSlot: () => { throw new Error('must call Mina.setActiveInstance first'); @@ -1177,6 +1199,13 @@ function hasAccount(publicKey: PublicKey, tokenId?: Field): boolean { return activeInstance.hasAccount(publicKey, tokenId); } +/** + * @return Data associated with the current Mina network constants. + */ +function getNetworkConstants() { + return activeInstance.getNetworkConstants(); +} + /** * @return Data associated with the current state of the Mina network. */ @@ -1193,6 +1222,7 @@ function getBalance(publicKey: PublicKey, tokenId?: Field) { /** * Returns the default account creation fee. + * @deprecated use {@link Mina.getNetworkConstants} */ function accountCreationFee() { return activeInstance.accountCreationFee(); @@ -1592,3 +1622,15 @@ async function faucet(pub: PublicKey, network: string = 'berkeley-qanet') { } await waitForFunding(address); } + +function genesisToNetworkConstants( + genesisConstants: Fetch.GenesisConstants +): NetworkConstants { + return { + genesisTimestamp: UInt64.from( + Date.parse(genesisConstants.genesisTimestamp) + ), + slotTime: UInt64.from(genesisConstants.slotDuration), + accountCreationFee: UInt64.from(genesisConstants.accountCreationFee), + }; +} diff --git a/src/lib/precondition.ts b/src/lib/precondition.ts index 25b10b903..ac5fd8f8a 100644 --- a/src/lib/precondition.ts +++ b/src/lib/precondition.ts @@ -58,7 +58,7 @@ function Network(accountUpdate: AccountUpdate): Network { }, requireEquals(value: UInt64) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let slot = timestampToGlobalSlot( value, `Timestamp precondition unsatisfied: the timestamp can only equal numbers of the form ${genesisTimestamp} + k*${slotTime},\n` + @@ -319,12 +319,12 @@ function getVariable( function globalSlotToTimestamp(slot: UInt32) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); return UInt64.from(slot).mul(slotTime).add(genesisTimestamp); } function timestampToGlobalSlot(timestamp: UInt64, message: string) { let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let { quotient: slot, rest } = timestamp .sub(genesisTimestamp) .divMod(slotTime); @@ -340,7 +340,7 @@ function timestampToGlobalSlotRange( // so we have to make the range smaller -- round up `tsLower` and round down `tsUpper` // also, we should clamp to the UInt32 max range [0, 2**32-1] let { genesisTimestamp, slotTime } = - Mina.activeInstance.getNetworkConstants(); + Mina.getNetworkConstants(); let tsLowerInt = Int64.from(tsLower) .sub(genesisTimestamp) .add(slotTime) diff --git a/src/lib/token.test.ts b/src/lib/token.test.ts index 038686ee3..5f8502f90 100644 --- a/src/lib/token.test.ts +++ b/src/lib/token.test.ts @@ -44,7 +44,7 @@ class TokenContract extends SmartContract { amount: this.SUPPLY, }); receiver.account.isNew.assertEquals(Bool(true)); - this.balance.subInPlace(Mina.accountCreationFee()); + this.balance.subInPlace(Mina.getNetworkConstants().accountCreationFee); this.totalAmountInCirculation.set(this.SUPPLY.sub(100_000_000)); this.account.permissions.set({ ...Permissions.default(), @@ -172,7 +172,7 @@ async function setupLocal() { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer); feePayerUpdate.send({ to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + amount: Mina.getNetworkConstants().accountCreationFee, }); tokenZkapp.deploy(); }); @@ -189,7 +189,7 @@ async function setupLocalProofs() { let feePayerUpdate = AccountUpdate.fundNewAccount(feePayer, 3); feePayerUpdate.send({ to: tokenZkappAddress, - amount: Mina.accountCreationFee(), + amount: Mina.getNetworkConstants().accountCreationFee, }); tokenZkapp.deploy(); tokenZkapp.deployZkapp(zkAppBAddress, ZkAppB._verificationKey!);