Skip to content

Commit

Permalink
Merge pull request #872 from ava-labs/erictaylor/p-chain-dynamic-fees
Browse files Browse the repository at this point in the history
feat: p-chain dynamic fees
  • Loading branch information
erictaylor authored Sep 27, 2024
2 parents ecde0b1 + 9e57a3b commit 5ae4440
Show file tree
Hide file tree
Showing 75 changed files with 6,110 additions and 82 deletions.
3 changes: 3 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"recommendations": ["streetsidesoftware.code-spell-checker"]
}
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.tsdk": "node_modules/typescript/lib"
}
}
23 changes: 23 additions & 0 deletions cspell.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
"version": "0.2",
"dictionaries": [
"companies",
"css",
"en_us",
"en-gb",
"fullstack",
"html",
"lorem-ipsum",
"node",
"npm",
"softwareTerms",
"sql",
"typescript"
],
"ignorePaths": ["node_modules", "__generated__", "build", "dist", "out"],
"ignoreRegExpList": ["/.*[0-9].*/"],
"language": "en",
"minWordLength": 5,
"words": ["amounter", "avalabs", "locktime", "stakeable", "unstakeable", "utxo", "utxos"]
}
42 changes: 42 additions & 0 deletions examples/p-chain/etna/base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { TransferableOutput, addTxSignatures, pvm, utils } from '../../../src';
import { getEnvVars } from '../../utils/getEnvVars';
import { getEtnaContextFromURI } from './utils/etna-context';

/**
* The amount of AVAX to send to self.
*/
const SEND_AVAX_AMOUNT: number = 0.001;

const main = async () => {
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();

const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);

const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL);

const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });

const tx = pvm.e.newBaseTx(
{
fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
outputs: [
TransferableOutput.fromNative(
context.avaxAssetID,
BigInt(SEND_AVAX_AMOUNT * 1e9),
[utils.bech32ToBytes(P_CHAIN_ADDRESS)],
),
],
utxos,
},
context,
);

await addTxSignatures({
unsignedTx: tx,
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
});

return pvmApi.issueSignedTx(tx.getSignedTx());
};

main().then(console.log);
50 changes: 50 additions & 0 deletions examples/p-chain/etna/delegate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { addTxSignatures, networkIDs, pvm, utils } from '../../../src';
import { getEnvVars } from '../../utils/getEnvVars';
import { getEtnaContextFromURI } from './utils/etna-context';

const AMOUNT_TO_DELEGATE_AVAX: number = 1;
const DAYS_TO_DELEGATE: number = 14;

const main = async () => {
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();

const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);

const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL);

const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });

const startTime = await pvmApi.getTimestamp();
const startDate = new Date(startTime.timestamp);
const start: bigint = BigInt(startDate.getTime() / 1_000);

const endTime = new Date(startTime.timestamp);
endTime.setDate(endTime.getDate() + DAYS_TO_DELEGATE);
const end: bigint = BigInt(endTime.getTime() / 1_000);

// TODO: Get this from an argument.
const nodeId = 'NodeID-MqgFXT8JhorbEW2LpTDGePBBhv55SSp3M';

const tx = pvm.e.newAddPermissionlessDelegatorTx(
{
end,
fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
nodeId,
rewardAddresses: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
start,
subnetId: networkIDs.PrimaryNetworkID.toString(),
utxos,
weight: BigInt(AMOUNT_TO_DELEGATE_AVAX * 1e9),
},
context,
);

await addTxSignatures({
unsignedTx: tx,
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
});

return pvmApi.issueSignedTx(tx.getSignedTx());
};

main().then(console.log);
43 changes: 43 additions & 0 deletions examples/p-chain/etna/export.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { TransferableOutput, addTxSignatures, pvm, utils } from '../../../src';
import { getEnvVars } from '../../utils/getEnvVars';
import { getEtnaContextFromURI } from './utils/etna-context';

const AMOUNT_TO_EXPORT_AVAX: number = 0.001;

const main = async () => {
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } =
getEnvVars();

const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL);

const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);

const { utxos } = await pvmApi.getUTXOs({
addresses: [P_CHAIN_ADDRESS],
});

const exportTx = pvm.e.newExportTx(
{
destinationChainId: context.xBlockchainID,
fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
outputs: [
TransferableOutput.fromNative(
context.avaxAssetID,
BigInt(AMOUNT_TO_EXPORT_AVAX * 1e9),
[utils.bech32ToBytes(X_CHAIN_ADDRESS)],
),
],
utxos,
},
context,
);

await addTxSignatures({
unsignedTx: exportTx,
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
});

return pvmApi.issueSignedTx(exportTx.getSignedTx());
};

main().then(console.log);
36 changes: 36 additions & 0 deletions examples/p-chain/etna/import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { addTxSignatures, pvm, utils } from '../../../src';
import { getEnvVars } from '../../utils/getEnvVars';
import { getEtnaContextFromURI } from './utils/etna-context';

const main = async () => {
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY, X_CHAIN_ADDRESS } =
getEnvVars();

const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL);

const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);

const { utxos } = await pvmApi.getUTXOs({
sourceChain: 'X',
addresses: [P_CHAIN_ADDRESS],
});

const importTx = pvm.e.newImportTx(
{
fromAddressesBytes: [utils.bech32ToBytes(X_CHAIN_ADDRESS)],
sourceChainId: context.xBlockchainID,
toAddresses: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
utxos,
},
context,
);

await addTxSignatures({
unsignedTx: importTx,
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
});

return pvmApi.issueSignedTx(importTx.getSignedTx());
};

main().then(console.log);
16 changes: 16 additions & 0 deletions examples/p-chain/etna/utils/etna-context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Context } from '../../../../src';

/**
* Gets the context from URI and then modifies the context
* to be used for testing example Etna transactions until Etna is enabled.
*/
export const getEtnaContextFromURI = async (
uri: string,
): Promise<Context.Context> => {
const context = await Context.getContextFromURI(uri);

return {
...context,
gasPrice: 10_000n,
};
};
10 changes: 10 additions & 0 deletions examples/p-chain/etna/utils/random-node-id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { base58check } from '../../../../src/utils';

export const getRandomNodeId = (): string => {
const buffer = new Uint8Array(20);
const randomBuffer = crypto.getRandomValues(buffer);

const nodeId = `NodeID-${base58check.encode(randomBuffer)}`;

return nodeId;
};
64 changes: 64 additions & 0 deletions examples/p-chain/etna/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { addTxSignatures, networkIDs, pvm, utils } from '../../../src';
import { getEnvVars } from '../../utils/getEnvVars';
import { getEtnaContextFromURI } from './utils/etna-context';
import { getRandomNodeId } from './utils/random-node-id';

const AMOUNT_TO_VALIDATE_AVAX: number = 1;
const DAYS_TO_VALIDATE: number = 21;

const nodeId = getRandomNodeId();

const main = async () => {
const { AVAX_PUBLIC_URL, P_CHAIN_ADDRESS, PRIVATE_KEY } = getEnvVars();

const pvmApi = new pvm.PVMApi(AVAX_PUBLIC_URL);

const context = await getEtnaContextFromURI(AVAX_PUBLIC_URL);

const { utxos } = await pvmApi.getUTXOs({ addresses: [P_CHAIN_ADDRESS] });

const startTime = await pvmApi.getTimestamp();
const startDate = new Date(startTime.timestamp);
const start: bigint = BigInt(startDate.getTime() / 1_000);

const endTime = new Date(startTime.timestamp);
endTime.setDate(endTime.getDate() + DAYS_TO_VALIDATE);
const end: bigint = BigInt(endTime.getTime() / 1_000);

const publicKey = utils.hexToBuffer(
'0x8f95423f7142d00a48e1014a3de8d28907d420dc33b3052a6dee03a3f2941a393c2351e354704ca66a3fc29870282e15',
);

const signature = utils.hexToBuffer(
'0x86a3ab4c45cfe31cae34c1d06f212434ac71b1be6cfe046c80c162e057614a94a5bc9f1ded1a7029deb0ba4ca7c9b71411e293438691be79c2dbf19d1ca7c3eadb9c756246fc5de5b7b89511c7d7302ae051d9e03d7991138299b5ed6a570a98',
);

const tx = pvm.e.newAddPermissionlessValidatorTx(
{
end,
delegatorRewardsOwner: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
fromAddressesBytes: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
nodeId,
publicKey,
rewardAddresses: [utils.bech32ToBytes(P_CHAIN_ADDRESS)],
shares: 20 * 1e4,
signature,
start,
subnetId: networkIDs.PrimaryNetworkID.toString(),
utxos,
weight: BigInt(AMOUNT_TO_VALIDATE_AVAX * 1e9),
},
context,
);

await addTxSignatures({
unsignedTx: tx,
privateKeys: [utils.hexToBuffer(PRIVATE_KEY)],
});

return pvmApi.issueSignedTx(tx.getSignedTx());
};

main()
.then(console.log)
.then(() => console.log('Validate node ID:', nodeId));
17 changes: 17 additions & 0 deletions examples/utils/getEnvVars.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const AVAX_PUBLIC_URL = process.env['AVAX_PUBLIC_URL'];
const P_CHAIN_ADDRESS = process.env['P_CHAIN_ADDRESS'];
const PRIVATE_KEY = process.env['PRIVATE_KEY'];
const X_CHAIN_ADDRESS = process.env['X_CHAIN_ADDRESS'];

export const getEnvVars = () => {
if (!(AVAX_PUBLIC_URL && P_CHAIN_ADDRESS && PRIVATE_KEY && X_CHAIN_ADDRESS)) {
throw new Error('Missing environment variable(s).');
}

return {
AVAX_PUBLIC_URL,
P_CHAIN_ADDRESS,
PRIVATE_KEY,
X_CHAIN_ADDRESS,
};
};
3 changes: 3 additions & 0 deletions jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,7 @@ module.exports = {
testEnvironment: 'node',
coverageProvider: 'v8',
extensionsToTreatAsEsm: ['.ts'],
// Experimental to fix issues with BigInt serialization
// See: https://jestjs.io/docs/configuration#workerthreads
workerThreads: true,
};
3 changes: 3 additions & 0 deletions src/crypto/secp256k1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import * as secp from '@noble/secp256k1';
import { Address } from 'micro-eth-signer';
import { concatBytes, hexToBuffer } from '../utils/buffer';

/** Number of bytes per signature */
export const SIGNATURE_LENGTH = 65;

export function randomPrivateKey() {
return secp.utils.randomPrivateKey();
}
Expand Down
5 changes: 5 additions & 0 deletions src/fixtures/context.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { createDimensions } from '../vms/common/fees/dimensions';
import type { Context } from '../vms/context';

export const testContext: Context = {
Expand All @@ -16,4 +17,8 @@ export const testContext: Context = {
addSubnetDelegatorFee: 1000000n,
networkID: 1,
hrp: 'avax',

// TODO: Adjust these based on what we want for the tests.
gasPrice: 1n,
complexityWeights: createDimensions(1, 1, 1, 1),
};
9 changes: 8 additions & 1 deletion src/fixtures/transactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import {
} from '../serializable/fxs/secp256k1';
import { BigIntPr, Int, Bytes } from '../serializable/primitives';
import { StakeableLockIn, StakeableLockOut } from '../serializable/pvm';
import { hexToBuffer } from '../utils';
import { hexToBuffer, unpackWithManager } from '../utils';
import { testContext } from './context';
import { stringToBytes } from '@scure/base';
import type { VM } from '../serializable';

export const cAddressForTest = '0xfd4DFC8f567caD8a275989982c5f8f1fC82B7563';
export const privateKeyForTest =
Expand Down Expand Up @@ -190,3 +191,9 @@ export const getOutputForTest = () =>
new BigIntPr(BigInt(0.1 * 1e9)),
Id.fromString(testContext.avaxAssetID),
);

export const txHexToTransaction = (vm: VM, txHex: string) => {
const txBytes = hexToBuffer(txHex);

return unpackWithManager(vm, txBytes);
};
2 changes: 1 addition & 1 deletion src/serializable/avax/avaxTx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { TransferableInput } from './transferableInput';
export abstract class AvaxTx extends Transaction {
abstract baseTx?: BaseTx;

getInputs(): TransferableInput[] {
getInputs(): readonly TransferableInput[] {
return this.baseTx?.inputs ?? [];
}
getBlockchainId() {
Expand Down
Loading

0 comments on commit 5ae4440

Please sign in to comment.