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

feat: p-chain dynamic fees #872

Merged
merged 40 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
d587389
chore: vscode auto import path setting relative
erictaylor Aug 14, 2024
bba2054
feat: base pvm complexity fns
erictaylor Aug 14, 2024
b64d15a
feat: add p-chain auth complexity
erictaylor Aug 15, 2024
9b99b1e
refactor: renaming for better readability
erictaylor Aug 15, 2024
eb04ec9
feat: add txComplexity
erictaylor Aug 15, 2024
0e90227
fix: complexity calculations and associated tests
erictaylor Aug 16, 2024
3977b68
refactor: move complexity constants to new file
erictaylor Aug 16, 2024
2651bf5
feat: add fee calculator
erictaylor Aug 19, 2024
ff1366c
chore: add cspell config and vscode extension recommendation
erictaylor Aug 20, 2024
03bb77a
feat: add pvm builder complexity logic
erictaylor Aug 20, 2024
d485942
feat: add new etnaBuilder for p-chain
erictaylor Aug 21, 2024
f2fdad9
refactor: cleanup
erictaylor Aug 21, 2024
12dcdef
feat: add etna builder spend logic
erictaylor Aug 28, 2024
fd46c2d
test: etna builder spend unwrapOutput
erictaylor Aug 29, 2024
62cabd2
feat: etna-builder fixes and more tests
erictaylor Sep 4, 2024
5e6ce59
feat: etna builder wrap up bug fixes and tests
erictaylor Sep 5, 2024
d4c5c0d
feat: expose new etna builder via experimental exports
erictaylor Sep 5, 2024
d85c261
refactor: cleanup code
erictaylor Sep 5, 2024
c56dd6b
refactor: review comment adjustments and cleanup
erictaylor Sep 6, 2024
35c2109
refactor: review comments and simplify builder in spots
erictaylor Sep 9, 2024
c18f9ba
test: coverage of spendHelper
erictaylor Sep 10, 2024
2bfa74f
refactor: replace unwrapOutput with getUtxoInfo in spend
erictaylor Sep 10, 2024
b539061
refactor: cleanup
erictaylor Sep 11, 2024
afa2f2d
refactor: use stackableLocktime
erictaylor Sep 11, 2024
36dd950
feat: add p-chain etna examples
erictaylor Sep 13, 2024
8007fa1
feat: add new etna experimental spend
erictaylor Sep 19, 2024
7183cfe
feat: opt-in consolidate outputs
erictaylor Sep 19, 2024
4a29afc
feat: spend reducers and tests
erictaylor Sep 20, 2024
0fe9515
feat: spend reducers and tests
erictaylor Sep 22, 2024
61946ff
feat: breakout spend reducers filters
erictaylor Sep 23, 2024
7968d58
test: add useUnlockedUTXOs reducer tests
erictaylor Sep 23, 2024
7e66550
test: add useSpendableLockedUTXOs test and examples updates
erictaylor Sep 23, 2024
a0448be
Merge branch 'master' into erictaylor/p-chain-dynamic-fees
erictaylor Sep 23, 2024
7c0d056
refactor: cleanup based on review feedback
erictaylor Sep 24, 2024
b3c6bfd
refactor: etna builder importtx with test
erictaylor Sep 26, 2024
141294d
fix: only allow AVAX assets on importtx
erictaylor Sep 26, 2024
94e1361
fix: address review feedback and fix adding stakeouts
erictaylor Sep 26, 2024
6f2979f
fix: usable unlocked utxos
erictaylor Sep 27, 2024
ddefe8c
refactor: rename some spend helper methods
erictaylor Sep 27, 2024
9e57a3b
refactor: cleanup TODO and magic numbers in tests
erictaylor Sep 27, 2024
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
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"]
erictaylor marked this conversation as resolved.
Show resolved Hide resolved
}
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;
erictaylor marked this conversation as resolved.
Show resolved Hide resolved

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
Loading