Skip to content

Commit 8eab305

Browse files
authored
feat: altvm file submitter (#7182)
1 parent 3472d11 commit 8eab305

File tree

20 files changed

+264
-55
lines changed

20 files changed

+264
-55
lines changed

.changeset/bright-glasses-drop.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"@hyperlane-xyz/cosmos-sdk": minor
3+
"@hyperlane-xyz/radix-sdk": minor
4+
"@hyperlane-xyz/utils": minor
5+
"@hyperlane-xyz/sdk": minor
6+
---
7+
8+
chore: add transactionToPrintableJson to altvm interface

.changeset/red-mayflies-do.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@hyperlane-xyz/cli": major
3+
---
4+
5+
feat: add altvm file submitter

typescript/cli/src/commands/core.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
chainCommandOption,
3434
inputFileCommandOption,
3535
outputFileCommandOption,
36+
strategyCommandOption,
3637
} from './options.js';
3738

3839
/**
@@ -56,6 +57,7 @@ export const coreCommand: CommandModule = {
5657
export const apply: CommandModuleWithWriteContext<{
5758
chain: string;
5859
config: string;
60+
strategy?: string;
5961
}> = {
6062
command: 'apply',
6163
describe:
@@ -70,8 +72,14 @@ export const apply: CommandModuleWithWriteContext<{
7072
true,
7173
'The path to output a Core Config JSON or YAML file.',
7274
),
75+
strategy: { ...strategyCommandOption, demandOption: false },
7376
},
74-
handler: async ({ context, chain, config: configFilePath }) => {
77+
handler: async ({
78+
context,
79+
chain,
80+
config: configFilePath,
81+
strategy: strategyUrl,
82+
}) => {
7583
logCommandHeader(`Hyperlane Core Apply`);
7684

7785
const addresses = (await context.registry.getChainAddresses(
@@ -86,6 +94,7 @@ export const apply: CommandModuleWithWriteContext<{
8694
chain,
8795
config,
8896
deployedCoreAddresses: addresses,
97+
strategyUrl,
8998
});
9099
process.exit(0);
91100
},

typescript/cli/src/context/altvm.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
SubmitterFactory,
1717
SubmitterMetadata,
1818
TxSubmitterType,
19-
isJsonRpcSubmitterConfig,
2019
} from '@hyperlane-xyz/sdk';
2120
import {
2221
AltVM,
@@ -25,7 +24,11 @@ import {
2524
assert,
2625
} from '@hyperlane-xyz/utils';
2726

28-
import { ExtendedChainSubmissionStrategy } from '../submitters/types.js';
27+
import { AltVMFileSubmitter } from '../submitters/AltVMFileSubmitter.js';
28+
import {
29+
CustomTxSubmitterType,
30+
ExtendedChainSubmissionStrategy,
31+
} from '../submitters/types.js';
2932

3033
import { SignerKeyProtocolMap } from './types.js';
3134

@@ -164,19 +167,16 @@ export class AltVMSignerFactory
164167
// was provided for our chain where we can read our private key
165168
if (strategyConfig[chain]) {
166169
const rawConfig = strategyConfig[chain]!.submitter;
167-
if (!isJsonRpcSubmitterConfig(rawConfig)) {
168-
throw new Error(
169-
`found unknown submitter in strategy config for chain ${chain}`,
170-
);
171-
}
172170

173-
if (!rawConfig.privateKey) {
174-
throw new Error(
175-
`missing private key in strategy config for chain ${chain}`,
176-
);
177-
}
171+
if (rawConfig.type === TxSubmitterType.JSON_RPC) {
172+
if (!rawConfig.privateKey) {
173+
throw new Error(
174+
`missing private key in strategy config for chain ${chain}`,
175+
);
176+
}
178177

179-
return rawConfig.privateKey;
178+
return rawConfig.privateKey;
179+
}
180180
}
181181

182182
// 3. Finally, if no key flag or strategy was provided we prompt the user
@@ -237,21 +237,40 @@ export class AltVMSignerFactory
237237
return new AltVMSignerFactory(metadataManager, signers);
238238
}
239239

240-
public submitterFactories(): ProtocolMap<Record<string, SubmitterFactory>> {
240+
public submitterFactories(
241+
chain: string,
242+
): ProtocolMap<Record<string, SubmitterFactory>> {
243+
const protocol = this.metadataManager.getProtocol(chain);
244+
241245
const factories: ProtocolMap<Record<string, SubmitterFactory>> = {};
242246

247+
if (
248+
protocol === ProtocolType.Ethereum ||
249+
protocol === ProtocolType.Sealevel
250+
) {
251+
return factories;
252+
}
253+
254+
const signer = this.get(chain);
255+
243256
for (const protocol of this.getSupportedProtocols()) {
244257
factories[protocol] = {
245258
[TxSubmitterType.JSON_RPC]: (
246-
multiProvider: MultiProvider,
259+
_multiProvider: MultiProvider,
247260
metadata: SubmitterMetadata,
248261
) => {
249262
// Used to type narrow metadata
250263
assert(
251264
metadata.type === TxSubmitterType.JSON_RPC,
252265
`Invalid metadata type: ${metadata.type}, expected ${TxSubmitterType.JSON_RPC}`,
253266
);
254-
return new AltVMJsonRpcTxSubmitter(multiProvider, this, metadata);
267+
return new AltVMJsonRpcTxSubmitter(signer, metadata);
268+
},
269+
[CustomTxSubmitterType.FILE]: (
270+
_multiProvider: MultiProvider,
271+
metadata: any,
272+
) => {
273+
return new AltVMFileSubmitter(signer, metadata);
255274
},
256275
};
257276
}

typescript/cli/src/deploy/core.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
runPreflightChecksForChains,
2626
validateCoreIsmCompatibility,
2727
} from './utils.js';
28+
import { getSubmitterByStrategy } from './warp.js';
2829

2930
interface DeployParams {
3031
context: WriteCommandContext;
@@ -35,6 +36,7 @@ interface DeployParams {
3536

3637
interface ApplyParams extends DeployParams {
3738
deployedCoreAddresses: DeployedCoreAddresses;
39+
strategyUrl?: string;
3840
}
3941

4042
/**
@@ -163,6 +165,12 @@ export async function runCoreApply(params: ApplyParams) {
163165
default: {
164166
const signer = context.altVmSigner.get(chain);
165167

168+
const { submitter } = await getSubmitterByStrategy({
169+
chain,
170+
context: params.context,
171+
strategyUrl: params.strategyUrl,
172+
});
173+
166174
const coreModule = new AltVMCoreModule(multiProvider, signer, {
167175
chain,
168176
config,
@@ -174,13 +182,7 @@ export async function runCoreApply(params: ApplyParams) {
174182
if (transactions.length) {
175183
logGray('Updating deployed core contracts');
176184

177-
if (signer.supportsTransactionBatching()) {
178-
await signer.sendAndConfirmBatchTransactions(transactions);
179-
} else {
180-
for (const tx of transactions) {
181-
await signer.sendAndConfirmTransaction(tx);
182-
}
183-
}
185+
await submitter.submit(...transactions);
184186

185187
logGreen(`Core config updated on ${chain}.`);
186188
} else {

typescript/cli/src/deploy/warp.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1014,7 +1014,7 @@ export async function getSubmitterByStrategy<T extends ProtocolType>({
10141014
return new EV5FileSubmitter(metadata);
10151015
},
10161016
},
1017-
...altVmSigner.submitterFactories(),
1017+
...altVmSigner.submitterFactories(chain),
10181018
},
10191019
}),
10201020
config: submissionStrategy,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { Logger } from 'pino';
2+
3+
import {
4+
ProtocolReceipt,
5+
ProtocolTransaction,
6+
ProtocolTypedTransaction,
7+
TxSubmitterInterface,
8+
TxSubmitterType,
9+
} from '@hyperlane-xyz/sdk';
10+
import {
11+
AltVM,
12+
Annotated,
13+
ProtocolType,
14+
assert,
15+
rootLogger,
16+
} from '@hyperlane-xyz/utils';
17+
18+
import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js';
19+
20+
import { CustomTxSubmitterType, FileTxSubmitterProps } from './types.js';
21+
22+
export class AltVMFileSubmitter<PT extends ProtocolType>
23+
implements TxSubmitterInterface<PT>
24+
{
25+
txSubmitterType: TxSubmitterType =
26+
CustomTxSubmitterType.FILE as TxSubmitterType;
27+
28+
protected readonly logger: Logger;
29+
30+
constructor(
31+
public readonly signer: AltVM.ISigner<
32+
ProtocolTransaction<PT>,
33+
ProtocolReceipt<PT>
34+
>,
35+
public readonly props: FileTxSubmitterProps,
36+
) {
37+
this.logger = rootLogger.child({
38+
module: AltVMFileSubmitter.name,
39+
});
40+
}
41+
42+
async submit(
43+
...txs: Annotated<ProtocolTypedTransaction<PT>['transaction']>[]
44+
): Promise<[]> {
45+
const filepath = this.props.filepath.trim();
46+
const allTxs = [];
47+
48+
// Convert raw transactions to printable ones which can later be signed
49+
for (const tx of txs) {
50+
allTxs.push(await this.signer.transactionToPrintableJson(tx));
51+
}
52+
53+
// Attempt to append transactions to existing filepath.
54+
try {
55+
const maybeExistingTxs = readYamlOrJson(filepath); // Can throw if file is empty
56+
assert(
57+
Array.isArray(maybeExistingTxs),
58+
`Target filepath ${filepath} has existing data, but is not an array. Overwriting.`,
59+
);
60+
allTxs.unshift(...maybeExistingTxs);
61+
} catch (e) {
62+
this.logger.debug(`Invalid transactions read from ${filepath}: ${e}`);
63+
}
64+
65+
writeYamlOrJson(filepath, allTxs);
66+
this.logger.debug(`Transactions written to ${filepath}`);
67+
return [];
68+
}
69+
}

typescript/cli/src/submitters/EV5FileSubmitter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414

1515
import { readYamlOrJson, writeYamlOrJson } from '../utils/files.js';
1616

17-
import { CustomTxSubmitterType, EV5FileTxSubmitterProps } from './types.js';
17+
import { CustomTxSubmitterType, FileTxSubmitterProps } from './types.js';
1818

1919
export class EV5FileSubmitter
2020
implements TxSubmitterInterface<ProtocolType.Ethereum>
@@ -24,7 +24,7 @@ export class EV5FileSubmitter
2424
protected readonly logger: Logger = rootLogger.child({
2525
module: 'file-submitter',
2626
});
27-
constructor(public readonly props: EV5FileTxSubmitterProps) {}
27+
constructor(public readonly props: FileTxSubmitterProps) {}
2828

2929
async submit(
3030
...txs: Annotated<

typescript/cli/src/submitters/types.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ export const CustomTxSubmitterType = {
1414
FILE: 'file',
1515
} as const;
1616

17-
export const EV5FileTxSubmitterPropsSchema = z.object({
17+
export const FileTxSubmitterPropsSchema = z.object({
1818
filepath: z.string(),
19+
chain: ZChainName,
1920
});
2021

2122
const FileSubmitterMetadataSchema = z.object({
2223
type: z.literal(CustomTxSubmitterType.FILE),
23-
...EV5FileTxSubmitterPropsSchema.shape,
24+
...FileTxSubmitterPropsSchema.shape,
2425
});
2526

2627
type FileSubmitterMetadata = z.infer<typeof FileSubmitterMetadataSchema>;
@@ -52,6 +53,4 @@ export type ExtendedChainSubmissionStrategy = z.infer<
5253
typeof ExtendedChainSubmissionStrategySchema
5354
>;
5455

55-
export type EV5FileTxSubmitterProps = z.infer<
56-
typeof EV5FileTxSubmitterPropsSchema
57-
>;
56+
export type FileTxSubmitterProps = z.infer<typeof FileTxSubmitterPropsSchema>;

typescript/cli/src/utils/balances.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,10 @@ export async function nativeBalancesAreSufficient(
7272
nativeToken.denom,
7373
`nativeToken denom is not defined on chain ${chain}`,
7474
);
75-
assert(gasPrice, `gasPrice is not defined on chain ${chain}`);
75+
76+
if (!gasPrice) {
77+
return;
78+
}
7679

7780
const ALT_VM_GAS = altVmSigner.getMinGas(protocol);
7881
requiredMinBalanceNativeDenom = BigNumber.from(

0 commit comments

Comments
 (0)