Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 13 additions & 3 deletions src-ts/proposalPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
BaseGovernorExecuteStage,
L2TimelockExecutionSingleStage,
SecurityCouncilManagerTimelockStage,
RedeemFailedError,
} from "./proposalStage";
import { Signer, BigNumber } from "ethers";
import { Provider, TransactionReceipt } from "@ethersproject/abstract-provider";
Expand Down Expand Up @@ -129,7 +130,7 @@ export class StageTracker extends EventEmitter {
this.stageFactory,
nStage,
this.pollingIntervalMs,
this.writeMode
this.writeMode,
);

// propagate events to the listener of this tracker - add some info about the previous stage
Expand Down Expand Up @@ -184,8 +185,18 @@ export class StageTracker extends EventEmitter {
} catch (err) {
if (err instanceof ProposalStageError) throw err;
if (err instanceof UnreachableCaseError) throw err;
if (err instanceof RedeemFailedError) {
this.emit(TrackerEventName.TRACKER_ERRORED, {
status: currentStatus!,
stage: this.stage.name,
identifier: this.stage.identifier,
error: err,
});

} else {
consecutiveErrors++;
}

consecutiveErrors++;
const error = err as Error;
if (consecutiveErrors > 5) {
// emit an error here
Expand All @@ -203,7 +214,6 @@ export class StageTracker extends EventEmitter {
error
);
}

await wait(this.pollingIntervalMs);
}
}
Expand Down
68 changes: 48 additions & 20 deletions src-ts/proposalStage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export interface ProposalStage {
}

/**
* Error with additional proposal information
* Fatal error with additional proposal information
*/
export class ProposalStageError extends Error {
constructor(message: string, identifier: string, stageName: string, inner?: Error);
Expand All @@ -105,6 +105,19 @@ export class UnreachableCaseError extends Error {
}
}

export class RedeemFailedError extends Error {
constructor(
public readonly retryableId: string,
public readonly since: number,
public readonly inner?: Error
) {
super(`Retryable redeem for ${retryableId} failing since: ${new Date(since).toISOString()}`);
if (inner) {
this.stack += "\nCaused By: " + inner.stack;
}
}
}

/**
* Taken from the IGovernorUpgradeable solidity
*/
Expand Down Expand Up @@ -869,7 +882,7 @@ export class SecurityCouncilManagerTimelockStage extends L2TimelockExecutionSing
receipt: TransactionReceipt,
arbOneSignerOrProvider: Provider | Signer
): Promise<L2TimelockExecutionSingleStage[]> {
const hasManagerEvent = receipt.logs.find(log => log.address === this.managerAddress);
const hasManagerEvent = receipt.logs.find((log) => log.address === this.managerAddress);

if (!hasManagerEvent) return [];

Expand All @@ -878,24 +891,36 @@ export class SecurityCouncilManagerTimelockStage extends L2TimelockExecutionSing
const upExecInterface = UpgradeExecutor__factory.createInterface();
const actionInterface = SecurityCouncilMemberSyncAction__factory.createInterface();

const logs = receipt.logs.filter(log => log.topics[0] === timelockInterface.getEventTopic("CallScheduled"));
const logs = receipt.logs.filter(
(log) => log.topics[0] === timelockInterface.getEventTopic("CallScheduled")
);

if (logs.length === 0) return [];

// we take the last log since it has the highest updateNonce
const lastLog = logs[logs.length - 1];

const callScheduledArgs = timelockInterface.parseLog(lastLog).args as CallScheduledEvent["args"];
const parsedSendTxToL1 = arbSysInterface.decodeFunctionData("sendTxToL1", callScheduledArgs.data);
const callScheduledArgs = timelockInterface.parseLog(lastLog)
.args as CallScheduledEvent["args"];
const parsedSendTxToL1 = arbSysInterface.decodeFunctionData(
"sendTxToL1",
callScheduledArgs.data
);
const parsedL1ScheduleBatch = L2TimelockExecutionStage.decodeScheduleBatch(parsedSendTxToL1[1]);

const parsedExecute = upExecInterface.decodeFunctionData("execute", parsedL1ScheduleBatch.callDatas[0]);
const parsedPerform = actionInterface.decodeFunctionData("perform", parsedExecute[1])
const parsedExecute = upExecInterface.decodeFunctionData(
"execute",
parsedL1ScheduleBatch.callDatas[0]
);
const parsedPerform = actionInterface.decodeFunctionData("perform", parsedExecute[1]);

const newMembers = parsedPerform[1]
const updateNonce = parsedPerform[2]
const newMembers = parsedPerform[1];
const updateNonce = parsedPerform[2];

const scheduleSalt = await SecurityCouncilManager__factory.connect(this.managerAddress, arbOneSignerOrProvider).generateSalt(newMembers, updateNonce)
const scheduleSalt = await SecurityCouncilManager__factory.connect(
this.managerAddress,
arbOneSignerOrProvider
).generateSalt(newMembers, updateNonce);

return [
new L2TimelockExecutionSingleStage(
Expand All @@ -906,8 +931,8 @@ export class SecurityCouncilManagerTimelockStage extends L2TimelockExecutionSing
scheduleSalt,
lastLog.address,
arbOneSignerOrProvider
)
]
),
];
}
}

Expand Down Expand Up @@ -1317,6 +1342,7 @@ export class L1TimelockExecutionBatchStage
export class RetryableExecutionStage implements ProposalStage {
public readonly identifier: string;
public name: string = "RetryableExecutionStage";
public failingSince: number = 0;

constructor(public readonly l1ToL2Message: L1ToL2MessageReader | L1ToL2MessageWriter) {
this.identifier = l1ToL2Message.retryableCreationId;
Expand Down Expand Up @@ -1384,15 +1410,17 @@ export class RetryableExecutionStage implements ProposalStage {
throw new Error("Message is not a writer");
}

while (true) {
try {
await (await this.l1ToL2Message.redeem()).wait();
break;
} catch {
const id = this.l1ToL2Message.retryableCreationId.toLowerCase();
console.error(`Failed to redeem retryable ${id}, retrying in 60s`);
await wait(60_000);
try {
await (await this.l1ToL2Message.redeem()).wait();
} catch (err) {
if (this.failingSince === 0) {
this.failingSince = Date.now();
}
throw new RedeemFailedError(
this.l1ToL2Message.retryableCreationId.toLowerCase(),
this.failingSince,
err as Error
);
}
}

Expand Down