|
| 1 | +package runner |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "errors" |
| 6 | + "fmt" |
| 7 | + "math/big" |
| 8 | + "math/rand" |
| 9 | + |
| 10 | + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super" |
| 11 | + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" |
| 12 | + "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" |
| 13 | + "github.com/ethereum-optimism/optimism/op-service/sources" |
| 14 | + "github.com/ethereum/go-ethereum/common" |
| 15 | + "github.com/ethereum/go-ethereum/crypto" |
| 16 | + "github.com/ethereum/go-ethereum/log" |
| 17 | +) |
| 18 | + |
| 19 | +func createGameInputs(ctx context.Context, log log.Logger, rollupClient *sources.RollupClient, supervisorClient *sources.SupervisorClient, typeName string, traceType types.TraceType) (utils.LocalGameInputs, error) { |
| 20 | + switch traceType { |
| 21 | + case types.TraceTypeSuperCannon, types.TraceTypeSuperPermissioned: |
| 22 | + if supervisorClient == nil { |
| 23 | + return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires supervisor rpc to be set", traceType) |
| 24 | + } |
| 25 | + return createGameInputsInterop(ctx, log, supervisorClient, typeName) |
| 26 | + default: |
| 27 | + if rollupClient == nil { |
| 28 | + return utils.LocalGameInputs{}, fmt.Errorf("trace type %s requires rollup rpc to be set", traceType) |
| 29 | + } |
| 30 | + return createGameInputsSingle(ctx, log, rollupClient, typeName) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +func createGameInputsSingle(ctx context.Context, log log.Logger, client *sources.RollupClient, typeName string) (utils.LocalGameInputs, error) { |
| 35 | + status, err := client.SyncStatus(ctx) |
| 36 | + if err != nil { |
| 37 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get rollup sync status: %w", err) |
| 38 | + } |
| 39 | + log.Info("Got sync status", "status", status, "type", typeName) |
| 40 | + |
| 41 | + if status.FinalizedL2.Number == 0 { |
| 42 | + return utils.LocalGameInputs{}, errors.New("safe head is 0") |
| 43 | + } |
| 44 | + l1Head := status.FinalizedL1 |
| 45 | + if status.FinalizedL1.Number > status.CurrentL1.Number { |
| 46 | + // Restrict the L1 head to a block that has actually been processed by op-node. |
| 47 | + // This only matters if op-node is behind and hasn't processed all finalized L1 blocks yet. |
| 48 | + l1Head = status.CurrentL1 |
| 49 | + log.Info("Node has not completed syncing finalized L1 block, using CurrentL1 instead", "type", typeName) |
| 50 | + } else if status.FinalizedL1.Number == 0 { |
| 51 | + // The node is resetting its pipeline and has set FinalizedL1 to 0, use the current L1 instead as it is the best |
| 52 | + // hope of getting a non-zero L1 block |
| 53 | + l1Head = status.CurrentL1 |
| 54 | + log.Warn("Node has zero finalized L1 block, using CurrentL1 instead", "type", typeName) |
| 55 | + } |
| 56 | + log.Info("Using L1 head", "head", l1Head, "type", typeName) |
| 57 | + if l1Head.Number == 0 { |
| 58 | + return utils.LocalGameInputs{}, errors.New("l1 head is 0") |
| 59 | + } |
| 60 | + blockNumber, err := findL2BlockNumberToDispute(ctx, log, client, l1Head.Number, status.FinalizedL2.Number) |
| 61 | + if err != nil { |
| 62 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to find l2 block number to dispute: %w", err) |
| 63 | + } |
| 64 | + claimOutput, err := client.OutputAtBlock(ctx, blockNumber) |
| 65 | + if err != nil { |
| 66 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) |
| 67 | + } |
| 68 | + parentOutput, err := client.OutputAtBlock(ctx, blockNumber-1) |
| 69 | + if err != nil { |
| 70 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim output: %w", err) |
| 71 | + } |
| 72 | + localInputs := utils.LocalGameInputs{ |
| 73 | + L1Head: l1Head.Hash, |
| 74 | + L2Head: parentOutput.BlockRef.Hash, |
| 75 | + L2OutputRoot: common.Hash(parentOutput.OutputRoot), |
| 76 | + L2Claim: common.Hash(claimOutput.OutputRoot), |
| 77 | + L2BlockNumber: new(big.Int).SetUint64(blockNumber), |
| 78 | + } |
| 79 | + return localInputs, nil |
| 80 | +} |
| 81 | + |
| 82 | +func createGameInputsInterop(ctx context.Context, log log.Logger, client *sources.SupervisorClient, typeName string) (utils.LocalGameInputs, error) { |
| 83 | + status, err := client.SyncStatus(ctx) |
| 84 | + if err != nil { |
| 85 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get rollup sync status: %w", err) |
| 86 | + } |
| 87 | + log.Info("Got sync status", "status", status, "type", typeName) |
| 88 | + |
| 89 | + claimTimestamp := status.FinalizedTimestamp |
| 90 | + agreedTimestamp := claimTimestamp - 1 |
| 91 | + if claimTimestamp == 0 { |
| 92 | + return utils.LocalGameInputs{}, errors.New("finalized timestamp is 0") |
| 93 | + } |
| 94 | + l1Head := status.MinSyncedL1 |
| 95 | + log.Info("Using L1 head", "head", l1Head, "type", typeName) |
| 96 | + if l1Head.Number == 0 { |
| 97 | + return utils.LocalGameInputs{}, errors.New("l1 head is 0") |
| 98 | + } |
| 99 | + |
| 100 | + prestateProvider := super.NewSuperRootPrestateProvider(client, agreedTimestamp) |
| 101 | + gameDepth := types.Depth(30) |
| 102 | + provider := super.NewSuperTraceProvider(log, nil, prestateProvider, client, l1Head.ID(), gameDepth, agreedTimestamp, claimTimestamp+10) |
| 103 | + var agreedPrestate []byte |
| 104 | + var claim common.Hash |
| 105 | + switch 2 { //rand.Intn(3) { |
| 106 | + case 0: // Derive block on first chain |
| 107 | + log.Info("Running first chain") |
| 108 | + prestate, err := prestateProvider.AbsolutePreState(ctx) |
| 109 | + if err != nil { |
| 110 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get pre-state commitment: %w", err) |
| 111 | + } |
| 112 | + agreedPrestate = prestate.Marshal() |
| 113 | + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(0))) |
| 114 | + if err != nil { |
| 115 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) |
| 116 | + } |
| 117 | + case 1: // Derive block on second chain |
| 118 | + log.Info("Deriving second chain") |
| 119 | + agreedPrestate, err = provider.GetPreimageBytes(ctx, types.NewPosition(gameDepth, big.NewInt(0))) |
| 120 | + if err != nil { |
| 121 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get agreed prestate at position 0: %w", err) |
| 122 | + } |
| 123 | + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(1))) |
| 124 | + if err != nil { |
| 125 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) |
| 126 | + } |
| 127 | + case 2: // Consolidate |
| 128 | + log.Info("Running consolidate step") |
| 129 | + step := int64(super.StepsPerTimestamp - 1) |
| 130 | + agreedPrestate, err = provider.GetPreimageBytes(ctx, types.NewPosition(gameDepth, big.NewInt(step-1))) |
| 131 | + if err != nil { |
| 132 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get agreed prestate at position 0: %w", err) |
| 133 | + } |
| 134 | + claim, err = provider.Get(ctx, types.NewPosition(gameDepth, big.NewInt(step))) |
| 135 | + if err != nil { |
| 136 | + return utils.LocalGameInputs{}, fmt.Errorf("failed to get claim: %w", err) |
| 137 | + } |
| 138 | + } |
| 139 | + localInputs := utils.LocalGameInputs{ |
| 140 | + L1Head: l1Head.Hash, |
| 141 | + AgreedPreState: agreedPrestate, |
| 142 | + L2OutputRoot: crypto.Keccak256Hash(agreedPrestate), |
| 143 | + L2Claim: claim, |
| 144 | + L2BlockNumber: new(big.Int).SetUint64(claimTimestamp + 10), // Anything beyond the claim |
| 145 | + } |
| 146 | + return localInputs, nil |
| 147 | +} |
| 148 | + |
| 149 | +func findL2BlockNumberToDispute(ctx context.Context, log log.Logger, client *sources.RollupClient, l1HeadNum uint64, l2BlockNum uint64) (uint64, error) { |
| 150 | + // Try to find a L1 block prior to the batch that make l2BlockNum safe |
| 151 | + // Limits how far back we search to 10 * 32 blocks |
| 152 | + const skipSize = uint64(32) |
| 153 | + for i := 0; i < 10; i++ { |
| 154 | + if l1HeadNum < skipSize { |
| 155 | + // Too close to genesis, give up and just use the original block |
| 156 | + log.Info("Failed to find prior batch.") |
| 157 | + return l2BlockNum, nil |
| 158 | + } |
| 159 | + l1HeadNum -= skipSize |
| 160 | + prevSafeHead, err := client.SafeHeadAtL1Block(ctx, l1HeadNum) |
| 161 | + if err != nil { |
| 162 | + return 0, fmt.Errorf("failed to get prior safe head at L1 block %v: %w", l1HeadNum, err) |
| 163 | + } |
| 164 | + if prevSafeHead.SafeHead.Number < l2BlockNum { |
| 165 | + switch rand.Intn(3) { |
| 166 | + case 0: // First block of span batch |
| 167 | + return prevSafeHead.SafeHead.Number + 1, nil |
| 168 | + case 1: // Last block of span batch |
| 169 | + return prevSafeHead.SafeHead.Number, nil |
| 170 | + case 2: // Random block, probably but not guaranteed to be in the middle of a span batch |
| 171 | + firstBlockInSpanBatch := prevSafeHead.SafeHead.Number + 1 |
| 172 | + if l2BlockNum <= firstBlockInSpanBatch { |
| 173 | + // There is only one block in the next batch so we just have to use it |
| 174 | + return l2BlockNum, nil |
| 175 | + } |
| 176 | + offset := rand.Intn(int(l2BlockNum - firstBlockInSpanBatch)) |
| 177 | + return firstBlockInSpanBatch + uint64(offset), nil |
| 178 | + } |
| 179 | + |
| 180 | + } |
| 181 | + if prevSafeHead.SafeHead.Number < l2BlockNum { |
| 182 | + // We walked back far enough to be before the batch that included l2BlockNum |
| 183 | + // So use the first block after the prior safe head as the disputed block. |
| 184 | + // It must be the first block in a batch. |
| 185 | + return prevSafeHead.SafeHead.Number + 1, nil |
| 186 | + } |
| 187 | + } |
| 188 | + log.Warn("Failed to find prior batch", "l2BlockNum", l2BlockNum, "earliestCheckL1Block", l1HeadNum) |
| 189 | + return l2BlockNum, nil |
| 190 | +} |
0 commit comments