Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use actual network genesis constants (genesisTimestamp, slotTime, accountCreationFee). #1367

Merged
merged 5 commits into from
Jan 21, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions run-ci-live-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
73 changes: 73 additions & 0 deletions src/examples/fetch_live.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { expect } from 'expect';
shimkiv marked this conversation as resolved.
Show resolved Hide resolved
import { Lightnet, Mina, PrivateKey, UInt64, fetchAccount } from 'o1js';

const useCustomLocalNetwork = process.env.USE_CUSTOM_LOCAL_NETWORK === 'true';
const minaGraphQlEndpoint = useCustomLocalNetwork
? 'http://localhost:8080/graphql'
: 'https://berkeley.minascan.io/graphql';
const network = Mina.Network({
mina: minaGraphQlEndpoint,
archive: useCustomLocalNetwork
? 'http://localhost:8282'
: 'https://api.minascan.io/archive/berkeley/v1/graphql',
lightnetAccountManager: 'http://localhost:8181',
});
Mina.setActiveInstance(network);
const transactionFee = 100_000_000;

// Fee payer setup
console.log('');
shimkiv marked this conversation as resolved.
Show resolved Hide resolved
const senderKey = useCustomLocalNetwork
? (await Lightnet.acquireKeyPair()).privateKey
: PrivateKey.random();
const sender = senderKey.toPublicKey();
if (!useCustomLocalNetwork) {
console.log(`Funding the fee payer account.`);
await Mina.faucet(sender);
}
console.log(`Fetching 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}.`
);

console.log('');
console.log(
"Check that network constants CAN'T be fetched outside of a transaction."
);
const getNetworkConstants = () => {
Mina.activeInstance.getNetworkConstants();
};
expect(getNetworkConstants).toThrow(
`getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphQlEndpoint} outside of a transaction.`
);

console.log('');
console.log(
'Check that network constants CAN be fetched within the transaction.'
);
let slotTime: UInt64 | undefined;
let genesisTimestamp: UInt64 | undefined;
await Mina.transaction({ sender, fee: transactionFee }, () => {
const networkConstants = Mina.activeInstance.getNetworkConstants();
slotTime = networkConstants.slotTime;
genesisTimestamp = networkConstants.genesisTimestamp;
});

expect(slotTime).not.toBeUndefined();
expect(genesisTimestamp).not.toBeUndefined();

console.log(`Slot time: ${slotTime}`);
console.log(`Genesis timestamp: ${genesisTimestamp}`);
console.log(
`Genesis date: ${new Date(Number(genesisTimestamp?.toString() ?? '0'))}`
);

// Tear down
console.log('');
const keyPairReleaseMessage = await Lightnet.releaseKeyPair({
publicKey: sender.toBase58(),
});
if (keyPairReleaseMessage) console.info(keyPairReleaseMessage);
69 changes: 68 additions & 1 deletion src/lib/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
export {
fetchAccount,
fetchLastBlock,
fetchGenesisConstants,
checkZkappTransaction,
parseFetchedAccount,
markAccountToBeFetched,
Expand All @@ -31,6 +32,7 @@ export {
getCachedAccount,
getCachedNetwork,
getCachedActions,
getCachedGenesisConstants,
addCachedAccount,
networkConfig,
setGraphqlEndpoint,
Expand All @@ -44,7 +46,8 @@ export {
removeJsonQuotes,
fetchEvents,
fetchActions,
Lightnet
Lightnet,
type GenesisConstants,
};

type NetworkConfig = {
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -257,6 +270,7 @@ let actionsToFetch = {} as Record<
graphqlEndpoint: string;
}
>;
let genesisConstantsCache = {} as Record<string, GenesisConstants>;

function markAccountToBeFetched(
publicKey: PublicKey,
Expand Down Expand Up @@ -333,6 +347,7 @@ async function fetchMissingData(
(async () => {
try {
await fetchLastBlock(graphqlEndpoint);
await fetchGenesisConstants(graphqlEndpoint);
delete networksToFetch[network[0]];
} catch {}
})()
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -1009,6 +1045,37 @@ async function fetchActions(
return actionsList;
}

/**
* Fetches genesis constants.
*/
async function fetchGenesisConstants(
graphqlEndpoint = networkConfig.minaEndpoint
): Promise<GenesisConstants> {
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
Expand Down
71 changes: 49 additions & 22 deletions src/lib/mina.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ export {
// for internal testing only
filterGroups,
};

interface TransactionId {
isSuccess: boolean;
wait(options?: { maxAttempts?: number; interval?: number }): Promise<void>;
Expand Down Expand Up @@ -163,6 +164,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}`;
Expand Down Expand Up @@ -340,14 +350,7 @@ 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;
accountCreationFee(): UInt64;
sendTransaction(transaction: Transaction): Promise<TransactionId>;
fetchEvents: (
Expand All @@ -369,6 +372,11 @@ interface Mina {
}

const defaultAccountCreationFee = 1_000_000_000;
const defaultNetworkConstants = {
genesisTimestamp: UInt64.from(0),
slotTime: UInt64.from(3 * 60 * 1000),
accountCreationFee: UInt64.from(defaultAccountCreationFee),
shimkiv marked this conversation as resolved.
Show resolved Hide resolved
};

/**
* A mock Mina blockchain running locally and useful for testing.
Expand Down Expand Up @@ -737,22 +745,29 @@ 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,
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);
}
throw Error(
`getNetworkConstants: Could not fetch network constants from graphql endpoint ${minaGraphqlEndpoint} outside of a transaction.`
);
},
currentSlot() {
throw Error(
Expand Down Expand Up @@ -814,7 +829,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) {
Expand Down Expand Up @@ -1592,3 +1607,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),
};
}