From 32fc55f1945057f2c9c14141a18708653e4edef9 Mon Sep 17 00:00:00 2001 From: peak3d Date: Wed, 26 Apr 2023 00:51:54 +0200 Subject: [PATCH] [CaminoAddVal] Add nodeOwnerAuth --- e2e_tests/camino/pchain_nomock.test.ts | 30 ++-- ...x-P-Chain-Msig-from-separate-signatures.ts | 7 +- ...NodeTx-buildAddValidatorTx-P-Chain-Msig.ts | 7 +- src/apis/platformvm/api.ts | 129 +++++++++++++++++- src/apis/platformvm/builder.ts | 32 ++--- src/apis/platformvm/validationtx.ts | 57 +++++++- 6 files changed, 224 insertions(+), 38 deletions(-) diff --git a/e2e_tests/camino/pchain_nomock.test.ts b/e2e_tests/camino/pchain_nomock.test.ts index 1dd8759f4..f5cd51087 100644 --- a/e2e_tests/camino/pchain_nomock.test.ts +++ b/e2e_tests/camino/pchain_nomock.test.ts @@ -72,7 +72,7 @@ const delegationFee: number = 10 const threshold: number = 1 const locktime: BN = new BN(0) const memo: Buffer = Buffer.from( - "PlatformVM utility method buildAddValidatorTx to add a validator to the primary subnet" + "PlatformVM utility method buildCaminoAddValidatorTx to add a validator to the primary subnet" ) const interestRateDenominator = new BN(1_000_000 * (365 * 24 * 60 * 60)) const dummyContractBin = @@ -149,17 +149,20 @@ describe("Camino-PChain-Add-Validator", (): void => { () => (async function () { const stakeAmount: any = await pChain.getMinStake() - const unsignedTx: UnsignedTx = await pChain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pChain.buildCaminoAddValidatorTx( undefined, [P(adminAddress)], [P(adminAddress)], [P(adminAddress)], adminNodeId, + { + address: P(adminAddress), + auth: [[0, P(adminAddress)]] + }, startTime, endTime, stakeAmount.minValidatorStake, [P(adminAddress)], - delegationFee, locktime, threshold, memo @@ -177,17 +180,20 @@ describe("Camino-PChain-Add-Validator", (): void => { () => (async function () { const stakeAmount: any = await pChain.getMinStake() - const unsignedTx: UnsignedTx = await pChain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pChain.buildCaminoAddValidatorTx( undefined, [P(adminAddress)], [P(adminAddress)], [P(adminAddress)], node6Id, + { + address: P(adminAddress), + auth: [[0, P(adminAddress)]] + }, startTime, endTime, stakeAmount.minValidatorStake, [P(adminAddress)], - delegationFee, locktime, threshold, memo @@ -248,17 +254,20 @@ describe("Camino-PChain-Add-Validator", (): void => { () => (async function () { const stakeAmount: any = await pChain.getMinStake() - const unsignedTx: UnsignedTx = await pChain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pChain.buildCaminoAddValidatorTx( undefined, [P(addrB)], // "X-kopernikus1s93gzmzuvv7gz8q4l83xccrdchh8mtm3xm5s2g" [P(addrB)], [P(addrB)], node6Id, + { + address: P(addrB), + auth: [[0, P(addrB)]] + }, startTime, endTime, stakeAmount.minValidatorStake, [P(addrB)], - delegationFee, locktime, threshold, memo @@ -912,17 +921,20 @@ describe("Camino-PChain-Multisig", (): void => { ]) const utxoSet: UTXOSet = platformVMUTXOResponse.utxos - const unsignedTx: UnsignedTx = await pChain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pChain.buildCaminoAddValidatorTx( utxoSet, [P(multiSigAliasAddr)], [[P(multiSigAliasAddr)], [pAddressStrings[5]]], [P(multiSigAliasAddr)], node7Id, // the node where the alias is registered + { + address: P(multiSigAliasAddr), + auth: [[0, pAddressStrings[5]]] + }, startTime, endTime, new BN(2000000000000), [P(multiSigAliasAddr)], - 0, undefined, threshold, undefined, diff --git a/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig-from-separate-signatures.ts b/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig-from-separate-signatures.ts index 23eeed2e4..bf2f7b481 100644 --- a/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig-from-separate-signatures.ts +++ b/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig-from-separate-signatures.ts @@ -196,17 +196,20 @@ const sendAddValidatorTx = async (): Promise => { let startDate = new Date(Date.now() + 0.5 * 60 * 1000).getTime() / 1000 let endDate = startDate + 60 * 60 * 24 * 10 - const unsignedTx: UnsignedTx = await pchain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pchain.buildCaminoAddValidatorTx( utxoSet, [msigAlias], [[msigAlias], pAddressStrings], [msigAlias], nodeID, + { + address: msigAlias, + auth: [[0, msigAliasArray[0]]] + }, new BN(startDate), new BN(endDate), new BN(2000000000000), [msigAlias], - 0, // delegation fee undefined, threshold, undefined, diff --git a/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig.ts b/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig.ts index 0d446ba91..8cce3ea80 100644 --- a/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig.ts +++ b/examples/platformvm/buildRegisterNodeTx-buildAddValidatorTx-P-Chain-Msig.ts @@ -135,17 +135,20 @@ const sendAddValidatorTx = async (): Promise => { let startDate = new Date(Date.now() + 0.5 * 60 * 1000).getTime() / 1000 let endDate = startDate + 60 * 60 * 24 * 10 - const unsignedTx: UnsignedTx = await pchain.buildAddValidatorTx( + const unsignedTx: UnsignedTx = await pchain.buildCaminoAddValidatorTx( utxoSet, [msigAlias], [[msigAlias], pAddressStrings], [msigAlias], nodeID, + { + address: msigAlias, + auth: [[0, msigAliasArray[0]]] + }, new BN(startDate), new BN(endDate), new BN(2000000000000), [msigAlias], - 0, // delegation fee undefined, threshold, undefined, diff --git a/src/apis/platformvm/api.ts b/src/apis/platformvm/api.ts index 132e77e7f..62f7124ca 100644 --- a/src/apis/platformvm/api.ts +++ b/src/apis/platformvm/api.ts @@ -86,7 +86,7 @@ import { TransferableInput } from "./inputs" import { TransferableOutput } from "./outputs" import { Serialization, SerializedType } from "../../utils" import { GenesisData } from "../avm" -import { Auth, LockMode, Builder, FromSigner } from "./builder" +import { Auth, LockMode, Builder, FromSigner, NodeOwner } from "./builder" import { Network } from "../../utils/networks" import { Spender } from "./spender" @@ -100,6 +100,10 @@ const NanoBN = new BN(1000000000) const rewardPercentDenom = 1000000 type FromType = String[] | String[][] +type NodeOwnerType = { + address: string + auth: [number, string][] +} /** * Class for interacting with a node's PlatformVMAPI @@ -2007,7 +2011,7 @@ export class PlatformVMAPI extends JRPCAPI { const minStake: BN = (await this.getMinStake())["minValidatorStake"] if (stakeAmount.lt(minStake)) { throw new StakeError( - "PlatformVMAPI.buildAddValidatorTx -- stake amount must be at least " + + `PlatformVMAPI.${caller} -- stake amount must be at least ` + minStake.toString(10) ) } @@ -2018,7 +2022,7 @@ export class PlatformVMAPI extends JRPCAPI { delegationFee < 0 ) { throw new DelegationFeeError( - "PlatformVMAPI.buildAddValidatorTx -- delegationFee must be a number between 0 and 100" + `PlatformVMAPI.${caller} -- delegationFee must be a number between 0 and 100` ) } @@ -2027,7 +2031,7 @@ export class PlatformVMAPI extends JRPCAPI { const now: BN = UnixNow() if (startTime.lt(now) || endTime.lte(startTime)) { throw new TimeError( - "PlatformVMAPI.buildAddValidatorTx -- startTime must be in the future and endTime must come after startTime" + `PlatformVMAPI.${caller} -- startTime must be in the future and endTime must come after startTime` ) } @@ -2213,6 +2217,123 @@ export class PlatformVMAPI extends JRPCAPI { return builtUnsignedTx } + + /** + * Helper function which creates an unsigned [[CaminoAddValidatorTx]]. For more granular control, you may create your own + * [[UnsignedTx]] manually and import the [[CaminoAddValidatorTx]] class directly. + * + * @param utxoset A set of UTXOs that the transaction is built on + * @param toAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who received the staked tokens at the end of the staking period + * @param fromAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who own the staking UTXOs the fees in AVAX + * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment + * @param nodeID The node ID of the validator being added. + * @param nodeOwner The address and signature indices of the registered nodeId owner. + * @param startTime The Unix time when the validator starts validating the Primary Network. + * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). + * @param stakeAmount The amount being delegated as a {@link https://github.com/indutny/bn.js/|BN} + * @param rewardAddresses The addresses which will recieve the rewards from the delegated stake. + * @param rewardLocktime Optional. The locktime field created in the resulting reward outputs + * @param rewardThreshold Opional. The number of signatures required to spend the funds in the resultant reward UTXO. Default 1. + * @param memo Optional contains arbitrary bytes, up to 256 bytes + * @param asOf Optional. The timestamp to verify the transaction against as a {@link https://github.com/indutny/bn.js/|BN} + * @param toThreshold Optional. The number of signatures required to spend the funds in the resultant UTXO + * @param changeThreshold Optional. The number of signatures required to spend the funds in the resultant change UTXO + * + * @returns An unsigned transaction created from the passed in parameters. + */ + buildCaminoAddValidatorTx = async ( + utxoset: UTXOSet, + toAddresses: string[], + fromAddresses: FromType, + changeAddresses: string[], + nodeID: string, + nodeOwner: NodeOwnerType, + startTime: BN, + endTime: BN, + stakeAmount: BN, + rewardAddresses: string[], + rewardLocktime: BN = ZeroBN, + rewardThreshold: number = 1, + memo: PayloadBase | Buffer = undefined, + asOf: BN = ZeroBN, + toThreshold: number = 1, + changeThreshold: number = 1 + ): Promise => { + const caller = "buildCaminoAddValidatorTx" + + const to: Buffer[] = this._cleanAddressArrayBuffer(toAddresses, caller) + + const fromSigner = this._parseFromSigner(fromAddresses, caller) + + const change: Buffer[] = this._cleanAddressArrayBuffer( + changeAddresses, + caller + ) + const rewards: Buffer[] = this._cleanAddressArrayBuffer( + rewardAddresses, + caller + ) + + if (memo instanceof PayloadBase) { + memo = memo.getPayload() + } + + const minStake: BN = (await this.getMinStake())["minValidatorStake"] + if (stakeAmount.lt(minStake)) { + throw new StakeError( + `PlatformVMAPI.${caller} -- stake amount must be at least ` + + minStake.toString(10) + ) + } + + const avaxAssetID: Buffer = await this.getAVAXAssetID() + + const now: BN = UnixNow() + if (startTime.lt(now) || endTime.lte(startTime)) { + throw new TimeError( + `PlatformVMAPI.${caller} -- startTime must be in the future and endTime must come after startTime` + ) + } + + const auth: NodeOwner = { + address: this.parseAddress(nodeOwner.address), + auth: [] + } + nodeOwner.auth.forEach((o) => { + auth.auth.push([o[0], this.parseAddress(o[1])]) + }) + + const builtUnsignedTx: UnsignedTx = await this._getBuilder( + utxoset + ).buildCaminoAddValidatorTx( + this.core.getNetworkID(), + bintools.cb58Decode(this.blockchainID), + to, + fromSigner, + change, + NodeIDStringToBuffer(nodeID), + auth, + startTime, + endTime, + stakeAmount, + avaxAssetID, + rewards, + rewardLocktime, + rewardThreshold, + memo, + asOf, + toThreshold, + changeThreshold + ) + + if (!(await this.checkGooseEgg(builtUnsignedTx))) { + /* istanbul ignore next */ + throw new GooseEggCheckError("Failed Goose Egg Check") + } + + return builtUnsignedTx + } + /** * Build an unsigned [[AddressStateTx]]. * diff --git a/src/apis/platformvm/builder.ts b/src/apis/platformvm/builder.ts index bec2f12c9..bf5756ee8 100644 --- a/src/apis/platformvm/builder.ts +++ b/src/apis/platformvm/builder.ts @@ -65,6 +65,11 @@ export type FromSigner = { signer: Buffer[] } +export type NodeOwner = { + address: Buffer + auth: [number, Buffer][] +} + export type Auth = { addresses: Buffer[] threshold: number @@ -741,25 +746,7 @@ export class Builder { changeThreshold: number = 1 ): Promise => { if (this.caminoEnabled) { - return this.buildCaminoAddValidatorTx( - networkID, - blockchainID, - toAddresses, - fromSigner, - changeAddresses, - nodeID, - startTime, - endTime, - stakeAmount, - stakeAssetID, - rewardAddresses, - rewardLocktime, - rewardThreshold, - memo, - asOf, - toThreshold, - changeThreshold - ) + throw new Error("Use buildCaminoAddValidatorTx") } let ins: TransferableInput[] = [] @@ -1018,6 +1005,7 @@ export class Builder { * @param fromSigner The addresses being used to send and verify the funds from the UTXOs {@link https://github.com/feross/buffer|Buffer} * @param changeAddresses An array of addresses as {@link https://github.com/feross/buffer|Buffer} who gets the change leftover from the fee payment * @param nodeID The node ID of the validator being added. + * @param nodeOwner The address and signature indices of the registered nodeId owner. * @param startTime The Unix time when the validator starts validating the Primary Network. * @param endTime The Unix time when the validator stops validating the Primary Network (and staked AVAX is returned). * @param stakeAmount The amount being delegated as a {@link https://github.com/indutny/bn.js/|BN} @@ -1038,6 +1026,7 @@ export class Builder { fromSigner: FromSigner, change: Buffer[], nodeID: Buffer, + nodeOwner: NodeOwner, startTime: BN, endTime: BN, stakeAmount: BN, @@ -1105,6 +1094,11 @@ export class Builder { new ParseableOutput(rewardOutputOwners) ) + nodeOwner.auth.forEach((o) => { + baseTx.addSignatureIdx(o[0], o[1]) + }) + owners.push(new OutputOwners([nodeOwner.address], ZeroBN, 1)) + baseTx.setOutputOwners(owners) return new UnsignedTx(baseTx) } diff --git a/src/apis/platformvm/validationtx.ts b/src/apis/platformvm/validationtx.ts index 4e7473aeb..e32770fe8 100644 --- a/src/apis/platformvm/validationtx.ts +++ b/src/apis/platformvm/validationtx.ts @@ -6,8 +6,8 @@ import BN from "bn.js" import BinTools from "../../utils/bintools" import { BaseTx } from "./basetx" -import { TransferableOutput } from "../platformvm/outputs" -import { TransferableInput } from "../platformvm/inputs" +import { TransferableOutput } from "./outputs" +import { TransferableInput } from "./inputs" import { Buffer } from "buffer/" import { PlatformVMConstants } from "./constants" import { DefaultNetworkID } from "../../utils/constants" @@ -15,6 +15,10 @@ import { bufferToNodeIDString } from "../../utils/helperfunctions" import { AmountOutput, ParseableOutput } from "./outputs" import { Serialization, SerializedEncoding } from "../../utils/serialization" import { DelegationFeeError } from "../../utils/errors" +import { Credential, SigIdx, Signature } from "../../common" +import { SubnetAuth } from "./subnetauth" +import { KeyChain, KeyPair } from "./keychain" +import { SelectCredentialClass } from "./credentials" /** * @ignore @@ -548,6 +552,55 @@ export class CaminoAddValidatorTx extends AddValidatorTx { protected _typeName = "CaminoAddValidatorTx" protected _typeID = PlatformVMConstants.CAMINOADDVALIDATORTX + // signatures + protected nodeOwnerAuth: SubnetAuth = new SubnetAuth() + protected sigIdxs: SigIdx[] = [] // idxs of registered nodeID owner + + addSignatureIdx(addressIdx: number, address: Buffer): void { + const sigIdx = new SigIdx(addressIdx, address) + this.sigIdxs.push(sigIdx) + this.nodeOwnerAuth.addAddressIndex(sigIdx.getBytes()) + } + + getCredentialID(): number { + return PlatformVMConstants.SECPCREDENTIAL + } + + fromBuffer(bytes: Buffer, offset: number = 0): number { + offset = super.fromBuffer(bytes, offset) + + this.nodeOwnerAuth = new SubnetAuth() + offset += this.nodeOwnerAuth.fromBuffer(bytes, offset) + + return offset + } + + toBuffer(): Buffer { + let superBuff = super.toBuffer() + let bsize = superBuff.length + + const authBuf = this.nodeOwnerAuth.toBuffer() + bsize += authBuf.length + + return Buffer.concat([superBuff, authBuf], bsize) + } + + sign(msg: Buffer, kc: KeyChain): Credential[] { + const creds = super.sign(msg, kc) + + const cred = SelectCredentialClass(this.getCredentialID()) + for (const sigIdx of this.sigIdxs) { + const keypair: KeyPair = kc.getKey(sigIdx.getSource()) + const signval: Buffer = keypair.sign(msg) + const sig: Signature = new Signature() + sig.fromBuffer(signval) + cred.addSignature(sig) + } + creds.push(cred) + + return creds + } + /** * Class representing an unsigned CaminoAddValidatorTx transaction. *