diff --git a/src/fixtures/pvm.ts b/src/fixtures/pvm.ts index 0982a787f..7e998b1c8 100644 --- a/src/fixtures/pvm.ts +++ b/src/fixtures/pvm.ts @@ -29,8 +29,8 @@ import { transferableOutputBytes, } from './avax'; import { - address, - addressBytes, + addresses, + addressesBytes, id, idBytes, nodeId, @@ -65,6 +65,7 @@ import { makeList, makeListBytes } from './utils/makeList'; import type { FeeState } from '../vms/pvm'; import { ConvertSubnetTx } from '../serializable/pvm/convertSubnetTx'; import { ConvertSubnetValidator } from '../serializable/fxs/pvm/convertSubnetValidator'; +import { PChainOwner } from '../serializable/fxs/pvm/pChainOwner'; export const validator = () => new Validator(nodeId(), bigIntPr(), bigIntPr(), bigIntPr()); @@ -306,25 +307,36 @@ export const transformSubnetTxBytes = () => export const convertSubnetValidator = () => new ConvertSubnetValidator( - nodeId(), + // nodeId(), + bytes(), bigIntPr(), bigIntPr(), - signer(), - outputOwner(), - outputOwner(), + + // signer(), + // outputOwner(), + // outputOwner(), + proofOfPossession(), + pChainOwner(), + pChainOwner(), ); export const convertSubnetValidatorBytes = () => concatBytes( - nodeIdBytes(), + // nodeIdBytes(), + bytesBytes(), bigIntPrBytes(), bigIntPrBytes(), - bytesForInt(28), - signerBytes(), - bytesForInt(11), - outputOwnerBytes(), - bytesForInt(11), - outputOwnerBytes(), + + // bytesForInt(28), + // signerBytes(), + // bytesForInt(11), + // outputOwnerBytes(), + // bytesForInt(11), + // outputOwnerBytes(), + + proofOfPossessionBytes(), + pChainOwnerBytes(), + pChainOwnerBytes(), ); export const convertSubnetTx = () => @@ -332,7 +344,8 @@ export const convertSubnetTx = () => baseTx(), id(), id(), - address(), + // address(), + bytes(), makeList(convertSubnetValidator)(), input(), ); @@ -342,12 +355,21 @@ export const convertSubnetTxBytes = () => baseTxbytes(), idBytes(), idBytes(), - addressBytes(), + // addressBytes(), + bytesBytes(), makeListBytes(convertSubnetValidatorBytes)(), bytesForInt(10), inputBytes(), ); +export const pChainOwner = () => new PChainOwner(int(), addresses()()); + +export const pChainOwnerBytes = () => + concatBytes( + intBytes(), // threshold + addressesBytes(), + ); + export const feeState = (): FeeState => ({ capacity: 1n, excess: 1n, diff --git a/src/serializable/fxs/pvm/convertSubnetValidator.ts b/src/serializable/fxs/pvm/convertSubnetValidator.ts index 86c96edeb..5b3430a8a 100644 --- a/src/serializable/fxs/pvm/convertSubnetValidator.ts +++ b/src/serializable/fxs/pvm/convertSubnetValidator.ts @@ -1,15 +1,12 @@ import { pack, unpack } from '../../../utils/struct'; -import { Codec } from '../../codec/codec'; +import type { Codec } from '../../codec'; import type { Serializable } from '../../common/types'; import { serializable } from '../../common/types'; -import { BigIntPr } from '../../primitives'; +import { BigIntPr, Bytes } from '../../primitives'; import { TypeSymbols } from '../../constants'; -import { emptyNodeId } from '../../../constants/zeroValue'; +import { ProofOfPossession } from '../../pvm'; import { NodeId } from '../common'; -import { concatBytes } from '@noble/hashes/utils'; -import type { SignerEmpty } from '../../pvm'; -import type { Signer } from '../../pvm'; -import type { OutputOwners } from '../secp256k1'; +import { PChainOwner } from './pChainOwner'; /** * @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/txs/convert_subnet_tx.go#86 @@ -19,36 +16,36 @@ export class ConvertSubnetValidator { _type = TypeSymbols.ConvertSubnetValidator; constructor( - public readonly nodeId: NodeId, + public readonly nodeId: Bytes, public readonly weight: BigIntPr, public readonly balance: BigIntPr, - public readonly signer: Signer | SignerEmpty, + public readonly signer: ProofOfPossession, public readonly remainingBalanceOwner: Serializable, public readonly deactivationOwner: Serializable, ) {} getBalance() { - return this.balance.value(); + return this.balance; } getRemainingBalanceOwner() { - return this.remainingBalanceOwner as OutputOwners; + return this.remainingBalanceOwner as PChainOwner; } getDeactivationOwner() { - return this.deactivationOwner as OutputOwners; + return this.deactivationOwner as PChainOwner; } static fromNative( nodeId: string, weight: bigint, balance: bigint, - signer: Signer | SignerEmpty, - remainingBalanceOwner: OutputOwners, - deactivationOwner: OutputOwners, + signer: ProofOfPossession, + remainingBalanceOwner: PChainOwner, + deactivationOwner: PChainOwner, ) { return new ConvertSubnetValidator( - NodeId.fromString(nodeId), + new Bytes(NodeId.fromString(nodeId).toBytes()), new BigIntPr(weight), new BigIntPr(balance), signer, @@ -69,7 +66,11 @@ export class ConvertSubnetValidator { remainingBalanceOwner, deactivationOwner, rest, - ] = unpack(bytes, [NodeId, BigIntPr, BigIntPr, Codec, Codec, Codec], codec); + ] = unpack( + bytes, + [Bytes, BigIntPr, BigIntPr, ProofOfPossession, PChainOwner, PChainOwner], + codec, + ); return [ new ConvertSubnetValidator( @@ -85,11 +86,16 @@ export class ConvertSubnetValidator { } toBytes(codec: Codec) { - return concatBytes( - pack([this.nodeId, this.weight, this.balance], codec), - codec.PackPrefix(this.signer), - codec.PackPrefix(this.remainingBalanceOwner), - codec.PackPrefix(this.deactivationOwner), + return pack( + [ + this.nodeId, + this.weight, + this.balance, + this.signer, + this.remainingBalanceOwner, + this.deactivationOwner, + ], + codec, ); } @@ -98,9 +104,12 @@ export class ConvertSubnetValidator { throw new Error('Weight must be greater than 0'); } - if (this.nodeId === emptyNodeId) { - throw new Error('Node ID must be non-empty'); - } + // const nodeId = new NodeId(this.nodeId.toBytesWithoutLength()); + + // TODO: Properly add this logic back with new types. + // if (this.nodeId === emptyNodeId) { + // throw new Error('Node ID must be non-empty'); + // } return true; } } diff --git a/src/serializable/fxs/pvm/pChainOwner.test.ts b/src/serializable/fxs/pvm/pChainOwner.test.ts new file mode 100644 index 000000000..8567a7a88 --- /dev/null +++ b/src/serializable/fxs/pvm/pChainOwner.test.ts @@ -0,0 +1,5 @@ +import { pChainOwner, pChainOwnerBytes } from '../../../fixtures/pvm'; +import { testSerialization } from '../../../fixtures/utils/serializable'; +import { PChainOwner } from './pChainOwner'; + +testSerialization('PChainOwner', PChainOwner, pChainOwner, pChainOwnerBytes); diff --git a/src/serializable/fxs/pvm/pChainOwner.ts b/src/serializable/fxs/pvm/pChainOwner.ts new file mode 100644 index 000000000..e3530cd93 --- /dev/null +++ b/src/serializable/fxs/pvm/pChainOwner.ts @@ -0,0 +1,45 @@ +import { concatBytes } from '@noble/hashes/utils'; +import { toListStruct } from '../../../utils/serializeList'; +import { pack, unpack } from '../../../utils/struct'; +import { serializable } from '../../common/types'; +import { Int } from '../../primitives'; +import { Address } from '../common/address'; +import { TypeSymbols } from '../../constants'; +import type { Codec } from '../../codec'; + +/** + * @see https://github.com/ava-labs/avalanchego/blob/master/vms/platformvm/warp/message/register_subnet_validator.go + */ +@serializable() +export class PChainOwner { + _type = TypeSymbols.PChainOwner; + + constructor( + public readonly threshold: Int, + public readonly addresses: Address[], + ) {} + + getAddresses() { + return this.addresses; + } + + static fromBytes(bytes: Uint8Array, codec: Codec): [PChainOwner, Uint8Array] { + const [threshold, addresses, remaining] = unpack( + bytes, + [Int, toListStruct(Address)], + codec, + ); + return [new PChainOwner(threshold, addresses), remaining]; + } + + toBytes(codec: Codec) { + return concatBytes(pack([this.threshold, this.addresses], codec)); + } + + static fromNative(addresses: readonly Uint8Array[], threshold = 1) { + return new PChainOwner( + new Int(threshold), + addresses.map((addr) => new Address(addr)), + ); + } +} diff --git a/src/serializable/pvm/convertSubnetTx.ts b/src/serializable/pvm/convertSubnetTx.ts index 4f2bb4fe7..ff1e520fe 100644 --- a/src/serializable/pvm/convertSubnetTx.ts +++ b/src/serializable/pvm/convertSubnetTx.ts @@ -6,8 +6,9 @@ import { Codec } from '../codec/codec'; import type { Serializable } from '../common/types'; import { serializable } from '../common/types'; import { TypeSymbols } from '../constants'; -import { Address, Id } from '../fxs/common'; +import { Id } from '../fxs/common'; import { ConvertSubnetValidator } from '../fxs/pvm/convertSubnetValidator'; +import { Bytes } from '../primitives'; import { AbstractSubnetTx } from './abstractSubnetTx'; @serializable() @@ -18,7 +19,7 @@ export class ConvertSubnetTx extends AbstractSubnetTx { public readonly baseTx: BaseTx, public readonly subnetID: Id, public readonly chainID: Id, - public readonly address: Address, + public readonly address: Bytes, public readonly validators: ConvertSubnetValidator[], public readonly subnetAuth: Serializable, ) { @@ -36,7 +37,7 @@ export class ConvertSubnetTx extends AbstractSubnetTx { const [baseTx, subnetID, chainID, address, validators, subnetAuth, rest] = unpack( bytes, - [BaseTx, Id, Id, Address, toListStruct(ConvertSubnetValidator), Codec], + [BaseTx, Id, Id, Bytes, toListStruct(ConvertSubnetValidator), Codec], codec, ); return [ diff --git a/src/vms/pvm/etna-builder/builder.ts b/src/vms/pvm/etna-builder/builder.ts index 56e96ade0..ce04eaf66 100644 --- a/src/vms/pvm/etna-builder/builder.ts +++ b/src/vms/pvm/etna-builder/builder.ts @@ -11,7 +11,6 @@ import { } from '../../../constants/networkIDs'; import type { TransferOutput } from '../../../serializable'; import { - Address, Input, NodeId, OutputOwners, @@ -1387,7 +1386,7 @@ export const newConvertSubnetTx: TxBuilderFn = ( for (const validator of sortedValidators) { toBurn.set( context.avaxAssetID, - (toBurn.get(context.avaxAssetID) ?? 0n) + validator.getBalance(), + (toBurn.get(context.avaxAssetID) ?? 0n) + validator.getBalance().value(), ); } @@ -1434,7 +1433,8 @@ export const newConvertSubnetTx: TxBuilderFn = ( ), Id.fromString(subnetId), Id.fromString(chainId), - new Address(address), + // new Address(address), + new Bytes(address), sortedValidators, Input.fromNative(subnetAuth), ), diff --git a/src/vms/pvm/txs/fee/complexity.test.ts b/src/vms/pvm/txs/fee/complexity.test.ts index 1378b0d6b..a80cd9c56 100644 --- a/src/vms/pvm/txs/fee/complexity.test.ts +++ b/src/vms/pvm/txs/fee/complexity.test.ts @@ -19,6 +19,7 @@ import { TransferableOutput, } from '../../../../serializable'; import { ConvertSubnetValidator } from '../../../../serializable/fxs/pvm/convertSubnetValidator'; +import { PChainOwner } from '../../../../serializable/fxs/pvm/pChainOwner'; import { ProofOfPossession, SignerEmpty, @@ -281,7 +282,8 @@ describe('Complexity', () => { describe('getConvertSubnetValidatorComplexity', () => { test('any can spend', () => { - const pChainOwner = OutputOwners.fromNative([], 0n, 1); + // const pChainOwner = OutputOwners.fromNative([], 0n, 1); + const pChainOwner = PChainOwner.fromNative([], 1); const validator = ConvertSubnetValidator.fromNative( 'NodeID-MqgFXT8JhorbEW2LpTDGePBBhv55SSp3M', 1n, @@ -302,16 +304,25 @@ describe('Complexity', () => { ); }); test('single remaining balance owner', () => { - const remainingBalanceOwner = OutputOwners.fromNative( + // const remainingBalanceOwner = OutputOwners.fromNative( + // [ + // utils.bech32ToBytes( + // 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', + // ), + // ], + // 0n, + // 1, + // ); + const remainingBalanceOwner = PChainOwner.fromNative( [ utils.bech32ToBytes( 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', ), ], - 0n, 1, ); - const deactivationOwner = OutputOwners.fromNative([], 0n, 1); + // const deactivationOwner = OutputOwners.fromNative([], 0n, 1); + const deactivationOwner = PChainOwner.fromNative([], 1); const validator = ConvertSubnetValidator.fromNative( 'NodeID-MqgFXT8JhorbEW2LpTDGePBBhv55SSp3M', 1n, @@ -332,16 +343,25 @@ describe('Complexity', () => { ); }); test('single deactivation owner', () => { - const deactivationOwner = OutputOwners.fromNative( + // const deactivationOwner = OutputOwners.fromNative( + // [ + // utils.bech32ToBytes( + // 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', + // ), + // ], + // 0n, + // 1, + // ); + const deactivationOwner = PChainOwner.fromNative( [ utils.bech32ToBytes( 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', ), ], - 0n, 1, ); - const remainingBalanceOwner = OutputOwners.fromNative([], 0n, 1); + // const remainingBalanceOwner = OutputOwners.fromNative([], 0n, 1); + const remainingBalanceOwner = PChainOwner.fromNative([], 1); const validator = ConvertSubnetValidator.fromNative( 'NodeID-MqgFXT8JhorbEW2LpTDGePBBhv55SSp3M', 1n, @@ -362,13 +382,21 @@ describe('Complexity', () => { ); }); test('remaining balance owner and deactivation owner', () => { - const pChainOwner = OutputOwners.fromNative( + // const pChainOwner = OutputOwners.fromNative( + // [ + // utils.bech32ToBytes( + // 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', + // ), + // ], + // 0n, + // 1, + // ); + const pChainOwner = PChainOwner.fromNative( [ utils.bech32ToBytes( 'P-custom1p8ddr5wfmfq0zv3n2wnst0cm2pfccaudm3wsrs', ), ], - 0n, 1, ); const validator = ConvertSubnetValidator.fromNative( diff --git a/src/vms/pvm/txs/fee/complexity.ts b/src/vms/pvm/txs/fee/complexity.ts index 92329a93f..df91705b6 100644 --- a/src/vms/pvm/txs/fee/complexity.ts +++ b/src/vms/pvm/txs/fee/complexity.ts @@ -241,12 +241,12 @@ export const getConvertSubnetValidatorsComplexity = ( export const getConvertSubnetValidatorComplexity = ( validator: ConvertSubnetValidator, ): Dimensions => { - const nodeIdComplexity = getBytesComplexity(validator.nodeId.toBytes()); + const nodeIdComplexity = getBytesComplexity(validator.nodeId); const signerComplexity = getSignerComplexity(validator.signer); const addressComplexity = createDimensions({ bandwidth: - (validator.getRemainingBalanceOwner().addrs.length + - validator.getDeactivationOwner().addrs.length) * + (validator.getRemainingBalanceOwner().getAddresses().length + + validator.getDeactivationOwner().getAddresses().length) * SHORT_ID_LEN, dbRead: 0, dbWrite: 0, @@ -376,7 +376,7 @@ const transferSubnetOwnershipTx = ( const convertSubnetTx = (tx: ConvertSubnetTx): Dimensions => { return addDimensions( INTRINSIC_CONVERT_SUBNET_TX_COMPLEXITIES, - getBytesComplexity(tx.address.toBytes()), + getBytesComplexity(tx.address), getBaseTxComplexity(tx.baseTx), getAuthComplexity(tx.subnetAuth), getConvertSubnetValidatorsComplexity(tx.validators),