diff --git a/kroma-chain-ops/genesis/layer_one.go b/kroma-chain-ops/genesis/layer_one.go index 4c729b0cf..9a505cec2 100644 --- a/kroma-chain-ops/genesis/layer_one.go +++ b/kroma-chain-ops/genesis/layer_one.go @@ -194,7 +194,7 @@ func PostProcessL1DeveloperGenesis(stateDB *state.MemoryStateDB, deployments *L1 stateDB.SetCode(predeploys.BeaconDepositContractAddr, predeploys.BeaconDepositContractCode) //setup governance token balances on L1 - log.Info("Set GovernanceContract balance on L1") + log.Info("Set GovernanceToken balance on L1") if !stateDB.Exist(deployments.L1GovernanceTokenProxy) { return fmt.Errorf("l1GovernanceToken proxy doesn't exist at %s", deployments.L1GovernanceTokenProxy) } @@ -211,7 +211,7 @@ func PostProcessL1DeveloperGenesis(stateDB *state.MemoryStateDB, deployments *L1 val = common.BigToHash(bigVal) for _, account := range DevAccounts { addrToBytes := append(make([]byte, 12), account.Bytes()...) - addrSlot := crypto.Keccak256Hash(append(addrToBytes, slot.Bytes()[:]...)) + addrSlot := crypto.Keccak256Hash(append(addrToBytes, slot.Bytes()...)) stateDB.SetState(deployments.L1GovernanceTokenProxy, addrSlot, val) log.Info("Post process update", "name", "GovernanceToken", "address", deployments.L1GovernanceTokenProxy, "slot", addrSlot.Hex(), "afterVal", val.Hex()) diff --git a/kroma-validator/challenger.go b/kroma-validator/challenger.go index 991074211..8f21a7dac 100644 --- a/kroma-validator/challenger.go +++ b/kroma-validator/challenger.go @@ -25,7 +25,6 @@ import ( "github.com/kroma-network/kroma/kroma-bindings/bindings" chal "github.com/kroma-network/kroma/kroma-validator/challenge" "github.com/kroma-network/kroma/kroma-validator/metrics" - val "github.com/kroma-network/kroma/kroma-validator/validator" ) var deletedOutputRoot = [32]byte{} @@ -56,9 +55,7 @@ type Challenger struct { l2BlockTime *big.Int checkpoint *big.Int requiredBondAmount *big.Int - - isValManagerEnabled bool - terminationIndex *big.Int + poolTerminationIndex *big.Int l2OutputSubmittedSub ethereum.Subscription challengeCreatedSub ethereum.Subscription @@ -160,38 +157,20 @@ func (c *Challenger) InitConfig(ctx context.Context) error { } c.requiredBondAmount = requiredBondAmount - cCtx, cCancel = context.WithTimeout(ctx, c.cfg.NetworkTimeout) - defer cCancel() - nextOutputIndex, err := c.l2ooContract.NextOutputIndex(optsutils.NewSimpleCallOpts(cCtx)) - if err != nil { - return fmt.Errorf("failed to get latest output index: %w", err) - } - - cCtx, cCancel = context.WithTimeout(ctx, c.cfg.NetworkTimeout) - defer cCancel() - isTerminated, err := c.valpoolContract.IsTerminated( - optsutils.NewSimpleCallOpts(cCtx), - nextOutputIndex, - ) - if err != nil { - return fmt.Errorf("failed to whether valpool is terminated or not: %w", err) - } - c.isValManagerEnabled = isTerminated - - cCtx, cCancel = context.WithTimeout(ctx, c.cfg.NetworkTimeout) - defer cCancel() - terminationIndex, err := c.valpoolContract.TERMINATEOUTPUTINDEX(optsutils.NewSimpleCallOpts(cCtx)) - if err != nil { - return fmt.Errorf("failed to get termination index: %w", err) - } - c.terminationIndex = terminationIndex - return nil }) if err != nil { return fmt.Errorf("failed to initiate valpool config: %w", err) } + cCtx, cCancel := context.WithTimeout(ctx, c.cfg.NetworkTimeout) + defer cCancel() + poolTerminationIndex, err := c.valpoolContract.TERMINATEOUTPUTINDEX(optsutils.NewSimpleCallOpts(cCtx)) + if err != nil { + return fmt.Errorf("failed to get termination index: %w", err) + } + c.poolTerminationIndex = poolTerminationIndex + return nil } @@ -380,10 +359,6 @@ func (c *Challenger) subscribeL2OutputSubmitted() { select { case ev := <-c.l2OutputSubmittedEventChan: c.log.Info("watched output submitted event", "l2BlockNumber", ev.L2BlockNumber, "outputRoot", ev.OutputRoot, "outputIndex", ev.L2OutputIndex) - // if the emitted output index is greater than the termination output index, set the config to use the ValidatorManager - if ev.L2OutputIndex.Cmp(c.terminationIndex) > 0 { - c.isValManagerEnabled = true - } // if the emitted output index is less than or equal to the checkpoint, it is considered reorg occurred. if ev.L2OutputIndex.Cmp(c.checkpoint) <= 0 { c.wg.Add(1) @@ -479,7 +454,7 @@ func (c *Challenger) handleOutput(outputIndex *big.Int) { return } - canCreateChallenge, err := c.CanCreateChallenge(c.ctx) + canCreateChallenge, err := c.CanCreateChallenge(c.ctx, outputIndex) if err != nil { c.log.Error(err.Error()) continue @@ -586,17 +561,22 @@ func (c *Challenger) handleChallenge(outputIndex *big.Int, asserter common.Addre // if challenger if isChallenger && c.cfg.ChallengerEnabled { - // if output has been already deleted, cancel challenge to refund pending bond - if isOutputDeleted && status != chal.StatusChallengerTimeout { - tx, err := c.CancelChallenge(c.ctx, outputIndex) - if err != nil { - c.log.Error("failed to create cancel challenge tx", "err", err, "outputIndex", outputIndex) - continue - } - if err := c.submitChallengeTx(tx); err != nil { - c.log.Error("failed to submit cancel challenge tx", "err", err, "outputIndex", outputIndex) - continue + if isOutputDeleted { + // if output has been already deleted, cancel challenge to refund pending bond in ValidatorPool + if c.IsValPoolTerminated(outputIndex) && status != chal.StatusChallengerTimeout { + tx, err := c.CancelChallenge(c.ctx, outputIndex) + if err != nil { + c.log.Error("failed to create cancel challenge tx", "err", err, "outputIndex", outputIndex) + continue + } + if err := c.submitChallengeTx(tx); err != nil { + c.log.Error("failed to submit cancel challenge tx", "err", err, "outputIndex", outputIndex) + continue + } } + // if output has been already deleted, terminate handling + c.log.Info("output is already deleted when handling challenge", "outputIndex", outputIndex) + return } // if output is already finalized, terminate handling @@ -640,22 +620,27 @@ func (c *Challenger) submitChallengeTx(tx *types.Transaction) error { } // CanCreateChallenge checks if challenger is in the status that can make challenge. -func (c *Challenger) CanCreateChallenge(ctx context.Context) (bool, error) { +func (c *Challenger) CanCreateChallenge(ctx context.Context, outputIndex *big.Int) (bool, error) { cCtx, cCancel := context.WithTimeout(ctx, c.cfg.NetworkTimeout) defer cCancel() from := c.cfg.TxManager.From() - if c.isValManagerEnabled { - vaultStatus, err := c.valManagerContract.GetStatus(optsutils.NewSimpleCallOpts(cCtx), from) + if c.IsValPoolTerminated(outputIndex) { + validatorStatus, err := c.valManagerContract.GetStatus(optsutils.NewSimpleCallOpts(cCtx), from) if err != nil { - return false, fmt.Errorf("failed to fetch the vault status: %w", err) + return false, fmt.Errorf("failed to fetch the validator status: %w", err) } - if vaultStatus != val.StatusCanSubmitOutput { - c.log.Warn("vault is not in the status to make a challenge", "status", vaultStatus) + if isInJail, err := c.IsInJail(ctx); err != nil { + return false, err + } else if isInJail { + c.log.Warn("validator is in jail") return false, nil } - c.log.Info("vault status", "status", vaultStatus) + if validatorStatus != StatusActive { + c.log.Warn("validator is not in the status that can create a challenge", "status", validatorStatus) + return false, nil + } } else { balance, err := c.valpoolContract.BalanceOf(optsutils.NewSimpleCallOpts(cCtx), from) if err != nil { @@ -672,12 +657,28 @@ func (c *Challenger) CanCreateChallenge(ctx context.Context) (bool, error) { return false, nil } - c.log.Info("deposit amount", "deposit", balance) + c.log.Info("deposit amount and bond amount", "deposit", balance, "bond", c.requiredBondAmount) } return true, nil } +func (c *Challenger) IsValPoolTerminated(outputIndex *big.Int) bool { + return c.poolTerminationIndex.Cmp(outputIndex) < 0 +} + +func (c *Challenger) IsInJail(ctx context.Context) (bool, error) { + cCtx, cCancel := context.WithTimeout(ctx, c.cfg.NetworkTimeout) + defer cCancel() + from := c.cfg.TxManager.From() + isInJail, err := c.valManagerContract.InJail(optsutils.NewSimpleCallOpts(cCtx), from) + if err != nil { + return false, fmt.Errorf("failed to fetch the jail status: %w", err) + } + + return isInJail, nil +} + func (c *Challenger) IsInChallengeCreationPeriod(ctx context.Context, outputIndex *big.Int) (bool, error) { cCtx, cCancel := context.WithTimeout(ctx, c.cfg.NetworkTimeout) defer cCancel() diff --git a/kroma-validator/config.go b/kroma-validator/config.go index a2b851428..d35968a26 100644 --- a/kroma-validator/config.go +++ b/kroma-validator/config.go @@ -33,7 +33,6 @@ type Config struct { ValidatorPoolAddr common.Address ValidatorManagerAddr common.Address AssetManagerAddr common.Address - GovTokenAddr common.Address ChallengerPollInterval time.Duration NetworkTimeout time.Duration TxManager *txmgr.BufferedTxManager diff --git a/kroma-validator/l2_output_submitter.go b/kroma-validator/l2_output_submitter.go index 01ec8f1e7..d0b738848 100644 --- a/kroma-validator/l2_output_submitter.go +++ b/kroma-validator/l2_output_submitter.go @@ -5,8 +5,6 @@ import ( "context" "errors" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/event" "math/big" _ "net/http/pprof" "sync" @@ -24,7 +22,6 @@ import ( "github.com/ethereum-optimism/optimism/op-service/watcher" "github.com/kroma-network/kroma/kroma-bindings/bindings" "github.com/kroma-network/kroma/kroma-validator/metrics" - val "github.com/kroma-network/kroma/kroma-validator/validator" ) const ( @@ -43,30 +40,25 @@ type L2OutputSubmitter struct { log log.Logger metr metrics.Metricer - l2ooContract *bindings.L2OutputOracle + l2ooContract *bindings.L2OutputOracleCaller l2ooABI *abi.ABI valpoolContract *bindings.ValidatorPoolCaller valManagerContract *bindings.ValidatorManagerCaller valManagerAbi *abi.ABI - singleRoundInterval *big.Int - l2BlockTime *big.Int - requiredBondAmount *big.Int + singleRoundInterval *big.Int + l2BlockTime *big.Int + requiredBondAmount *big.Int + poolTerminationIndex *big.Int - isValManagerEnabled bool - terminationIndex *big.Int - - outputSubmittedSub ethereum.Subscription - - submitChan chan struct{} - l2OutputSubmittedEventChan chan *bindings.L2OutputOracleOutputSubmitted + submitChan chan struct{} wg sync.WaitGroup } // NewL2OutputSubmitter creates a new L2OutputSubmitter. func NewL2OutputSubmitter(cfg Config, l log.Logger, m metrics.Metricer) (*L2OutputSubmitter, error) { - l2ooContract, err := bindings.NewL2OutputOracle(cfg.L2OutputOracleAddr, cfg.L1Client) + l2ooContract, err := bindings.NewL2OutputOracleCaller(cfg.L2OutputOracleAddr, cfg.L1Client) if err != nil { return nil, err } @@ -139,51 +131,21 @@ func (l *L2OutputSubmitter) InitConfig(ctx context.Context) error { } l.requiredBondAmount = requiredBondAmount - cCtx, cCancel = context.WithTimeout(ctx, l.cfg.NetworkTimeout) - defer cCancel() - nextOutputIndex, err := l.l2ooContract.NextOutputIndex(optsutils.NewSimpleCallOpts(cCtx)) - if err != nil { - return fmt.Errorf("failed to get latest output index: %w", err) - } - - cCtx, cCancel = context.WithTimeout(ctx, l.cfg.NetworkTimeout) - defer cCancel() - isTerminated, err := l.valpoolContract.IsTerminated( - optsutils.NewSimpleCallOpts(cCtx), - nextOutputIndex, - ) - if err != nil { - return fmt.Errorf("failed to whether valpool is terminated or not: %w", err) - } - l.isValManagerEnabled = isTerminated - - cCtx, cCancel = context.WithTimeout(ctx, l.cfg.NetworkTimeout) - defer cCancel() - terminationIndex, err := l.valpoolContract.TERMINATEOUTPUTINDEX(optsutils.NewSimpleCallOpts(cCtx)) - if err != nil { - return fmt.Errorf("failed to get termination index: %w", err) - } - l.terminationIndex = terminationIndex - return nil }) if err != nil { return fmt.Errorf("failed to initiate valpool config: %w", err) } - return nil -} - -func (l *L2OutputSubmitter) initSub() { - opts := optsutils.NewSimpleWatchOpts(l.ctx) + cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) + defer cCancel() + poolTerminationIndex, err := l.valpoolContract.TERMINATEOUTPUTINDEX(optsutils.NewSimpleCallOpts(cCtx)) + if err != nil { + return fmt.Errorf("failed to get termination index: %w", err) + } + l.poolTerminationIndex = poolTerminationIndex - l.l2OutputSubmittedEventChan = make(chan *bindings.L2OutputOracleOutputSubmitted) - l.outputSubmittedSub = event.ResubscribeErr(time.Second*10, func(ctx context.Context, err error) (event.Subscription, error) { - if err != nil { - l.log.Warn("resubscribing after failed OutputSubmitted event", "err", err) - } - return l.l2ooContract.WatchOutputSubmitted(opts, l.l2OutputSubmittedEventChan, nil, nil, nil) - }) + return nil } func (l *L2OutputSubmitter) Start(ctx context.Context) error { @@ -193,52 +155,23 @@ func (l *L2OutputSubmitter) Start(ctx context.Context) error { if err := l.InitConfig(l.ctx); err != nil { return err } - l.initSub() l.wg.Add(1) - go l.subscriptionLoop() - - l.wg.Add(1) - go l.submissionLoop() + go l.loop() return nil } func (l *L2OutputSubmitter) Stop() error { - if l.outputSubmittedSub != nil { - l.outputSubmittedSub.Unsubscribe() - } - l.cancel() l.wg.Wait() - if l.l2OutputSubmittedEventChan != nil { - close(l.l2OutputSubmittedEventChan) - } close(l.submitChan) return nil } -func (l *L2OutputSubmitter) subscriptionLoop() { - defer l.wg.Done() - - ticker := time.NewTicker(time.Minute) - defer ticker.Stop() - - for ; ; <-ticker.C { - select { - case <-l.ctx.Done(): - return - default: - l.subscribeL2OutputSubmitted() - - return - } - } -} - -func (l *L2OutputSubmitter) submissionLoop() { +func (l *L2OutputSubmitter) loop() { defer l.wg.Done() for ; ; <-l.submitChan { @@ -251,21 +184,6 @@ func (l *L2OutputSubmitter) submissionLoop() { } } -func (l *L2OutputSubmitter) subscribeL2OutputSubmitted() { - for { - select { - case ev := <-l.l2OutputSubmittedEventChan: - l.log.Info("watched output submitted event", "l2BlockNumber", ev.L2BlockNumber, "outputRoot", ev.OutputRoot, "outputIndex", ev.L2OutputIndex) - // if the emitted output index is greater than the termination output index, set the config to use the ValidatorManager - if ev.L2OutputIndex.Cmp(l.terminationIndex) > 0 { - l.isValManagerEnabled = true - } - case <-l.ctx.Done(): - return - } - } -} - func (l *L2OutputSubmitter) retryAfter(d time.Duration) { l.wg.Add(1) @@ -287,27 +205,22 @@ func (l *L2OutputSubmitter) repeatSubmitL2Output(ctx context.Context) { // If it needs to wait, it will calculate how long the validator should wait and // try again after the delay. func (l *L2OutputSubmitter) trySubmitL2Output(ctx context.Context) (time.Duration, error) { - if l.isValManagerEnabled { - hasSubmittedStartTx, err := l.tryStartValidator(ctx) - if err != nil { - return l.cfg.OutputSubmitterRetryInterval, err - } - if !hasSubmittedStartTx { - return l.cfg.OutputSubmitterRetryInterval, nil - } + nextBlockNumber, err := l.FetchNextBlockNumber(ctx) + if err != nil { + return l.cfg.OutputSubmitterRetryInterval, err } - nextBlockNumber, err := l.FetchNextBlockNumber(ctx) + outputIndex, err := l.FetchNextOutputIndex(ctx) if err != nil { return l.cfg.OutputSubmitterRetryInterval, err } - calculatedWaitTime := l.CalculateWaitTime(ctx, nextBlockNumber) + calculatedWaitTime := l.CalculateWaitTime(ctx, nextBlockNumber, outputIndex) if calculatedWaitTime > 0 { return calculatedWaitTime, nil } - if err = l.doSubmitL2Output(ctx, nextBlockNumber); err != nil { + if err = l.doSubmitL2Output(ctx, nextBlockNumber, outputIndex); err != nil { return l.cfg.OutputSubmitterRetryInterval, err } @@ -315,49 +228,8 @@ func (l *L2OutputSubmitter) trySubmitL2Output(ctx context.Context) (time.Duratio return 0, nil } -// tryStartValidator checks if the validator can start and tries to start it. -func (l *L2OutputSubmitter) tryStartValidator(ctx context.Context) (bool, error) { - cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) - defer cCancel() - from := l.cfg.TxManager.From() - vaultStatus, err := l.valManagerContract.GetStatus(optsutils.NewSimpleCallOpts(cCtx), from) - if err != nil { - return false, fmt.Errorf("failed to fetch the vault status: %w", err) - } - - if vaultStatus == val.StatusNone || vaultStatus == val.StatusInactive || vaultStatus == val.StatusActive { - l.log.Info("vault has not started yet", "currentStatus", vaultStatus) - return false, nil - } - - if vaultStatus == val.StatusCanSubmitOutput { - l.log.Info("vault has started, no need to start again", "currentStatus", val.StatusStarted) - return false, nil - } - - if isInJail, err := l.isInJail(ctx); err != nil { - return false, err - } else if isInJail { - l.log.Warn("validator is in jail") - return false, nil - } - - data, err := l.valManagerAbi.Pack("startValidator") - if err != nil { - return false, fmt.Errorf("failed to create start validator transaction data: %w", err) - } - - if txResponse := l.startValidatorTx(data); txResponse.Err != nil { - return false, txResponse.Err - } - - l.log.Info("startValidator successfully submitted") - - return true, nil -} - // doSubmitL2Output submits l2 Output submission transaction. -func (l *L2OutputSubmitter) doSubmitL2Output(ctx context.Context, nextBlockNumber *big.Int) error { +func (l *L2OutputSubmitter) doSubmitL2Output(ctx context.Context, nextBlockNumber *big.Int, outputIndex *big.Int) error { output, err := l.FetchOutput(ctx, nextBlockNumber) if err != nil { return err @@ -368,7 +240,7 @@ func (l *L2OutputSubmitter) doSubmitL2Output(ctx context.Context, nextBlockNumbe return fmt.Errorf("failed to create submit l2 output transaction data: %w", err) } - if txResponse := l.submitL2OutputTx(data); txResponse.Err != nil { + if txResponse := l.submitL2OutputTx(data, outputIndex); txResponse.Err != nil { return txResponse.Err } @@ -381,7 +253,7 @@ func (l *L2OutputSubmitter) doSubmitL2Output(ctx context.Context, nextBlockNumbe // CalculateWaitTime checks the conditions for submitting L2Output and calculates the required latency. // Returns time 0 if the conditions are such that submission is possible immediately. -func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumber *big.Int) time.Duration { +func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumber *big.Int, outputIndex *big.Int) time.Duration { defaultWaitTime := l.cfg.OutputSubmitterRetryInterval currentBlockNumber, err := l.FetchCurrentBlockNumber(ctx) @@ -389,8 +261,9 @@ func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumb return defaultWaitTime } - canSubmitOutput, err := l.CanSubmitOutput(ctx) + canSubmitOutput, err := l.CanSubmitOutput(ctx, outputIndex) if err != nil { + l.log.Error("failed to check the validator can submit output", "err", err) return defaultWaitTime } if !canSubmitOutput { @@ -409,7 +282,7 @@ func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumb } // Check if it's a public round, or selected for priority validator - roundInfo, err := l.fetchCurrentRound(ctx) + roundInfo, err := l.fetchCurrentRound(ctx, outputIndex) if err != nil { return defaultWaitTime } @@ -425,23 +298,36 @@ func (l *L2OutputSubmitter) CalculateWaitTime(ctx context.Context, nextBlockNumb return 0 } -// CanSubmitOutput checks if the validator can submit L2Output. -func (l *L2OutputSubmitter) CanSubmitOutput(ctx context.Context) (bool, error) { +// CanSubmitOutput checks if the validator satisfies the condition to submit L2Output. +func (l *L2OutputSubmitter) CanSubmitOutput(ctx context.Context, outputIndex *big.Int) (bool, error) { cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) defer cCancel() from := l.cfg.TxManager.From() - if l.isValManagerEnabled { - vaultStatus, err := l.valManagerContract.GetStatus(optsutils.NewSimpleCallOpts(cCtx), from) + if l.IsValPoolTerminated(outputIndex) { + validatorStatus, err := l.getValidatorStatus(ctx) + if err != nil { + return false, err + } + + validatorActivated, err := l.tryActivateValidator(validatorStatus) if err != nil { - return false, fmt.Errorf("failed to fetch the vault status: %w", err) + return false, err + } + if !validatorActivated { + return false, nil } - if vaultStatus != val.StatusCanSubmitOutput { - l.log.Warn("vault has started, but currently has not enough tokens", "currentStatus", val.StatusStarted) + if validatorStatus != StatusActive { + l.log.Warn("validator has activated, but currently has not enough tokens", "currentStatus", validatorStatus) return false, nil } - l.log.Info("vault status", "status", vaultStatus) + if isInJail, err := l.isInJail(ctx); err != nil { + return false, err + } else if isInJail { + l.log.Warn("validator is in jail") + return false, nil + } } else { balance, err := l.valpoolContract.BalanceOf(optsutils.NewSimpleCallOpts(cCtx), from) if err != nil { @@ -458,12 +344,45 @@ func (l *L2OutputSubmitter) CanSubmitOutput(ctx context.Context) (bool, error) { return false, nil } - l.log.Info("deposit amount", "deposit", balance) + l.log.Info("deposit amount and bond amount", "deposit", balance, "bond", l.requiredBondAmount) } return true, nil } +// tryActivateValidator checks if the validator can activate and tries to activate it if not activated yet. +func (l *L2OutputSubmitter) tryActivateValidator(validatorStatus uint8) (bool, error) { + if validatorStatus <= StatusRegistered { + l.log.Warn("validator cannot activate yet", "currentStatus", validatorStatus) + return false, nil + } + if validatorStatus >= StatusInactive { + l.log.Info("validator has already been activated, no need to activate again", "currentStatus", validatorStatus) + return true, nil + } + data, err := l.valManagerAbi.Pack("activateValidator") + if err != nil { + return false, fmt.Errorf("failed to create activate validator transaction data: %w", err) + } + if txResponse := l.activateValidatorTx(data); txResponse.Err != nil { + return false, txResponse.Err + } + l.log.Info("validator successfully activated") + return true, nil +} + +func (l *L2OutputSubmitter) FetchNextOutputIndex(ctx context.Context) (*big.Int, error) { + cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) + defer cCancel() + + outputIndex, err := l.l2ooContract.NextOutputIndex(optsutils.NewSimpleCallOpts(cCtx)) + if err != nil { + return nil, fmt.Errorf("failed to fetch next output index: %w", err) + } + + return outputIndex, nil +} + func (l *L2OutputSubmitter) FetchNextBlockNumber(ctx context.Context) (*big.Int, error) { cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) defer cCancel() @@ -497,19 +416,18 @@ func (l *L2OutputSubmitter) FetchCurrentBlockNumber(ctx context.Context) (*big.I return currentBlockNumber, nil } -func (l *L2OutputSubmitter) getLeftTimeForL2Blocks(currentBlockNumber *big.Int, targetBlockNumber *big.Int) time.Duration { - l.log.Info("submission interval has not elapsed", "currentBlockNumber", currentBlockNumber, "targetBlockNumber", targetBlockNumber) - waitBlockNum := new(big.Int).Sub(targetBlockNumber, currentBlockNumber) +func (l *L2OutputSubmitter) IsValPoolTerminated(outputIndex *big.Int) bool { + return l.poolTerminationIndex.Cmp(outputIndex) < 0 +} - var waitDuration time.Duration - if waitBlockNum.Cmp(common.Big0) == -1 { - waitDuration = l.cfg.OutputSubmitterRetryInterval - } else { - waitDuration = time.Duration(new(big.Int).Mul(waitBlockNum, l.l2BlockTime).Uint64()) * time.Second +func (l *L2OutputSubmitter) getValidatorStatus(ctx context.Context) (uint8, error) { + cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) + defer cCancel() + validatorStatus, err := l.valManagerContract.GetStatus(optsutils.NewSimpleCallOpts(cCtx), l.cfg.TxManager.From()) + if err != nil { + return 0, fmt.Errorf("failed to fetch the validator status: %w", err) } - - l.log.Info("wait for L2 blocks proceeding", "waitDuration", waitDuration) - return waitDuration + return validatorStatus, nil } func (l *L2OutputSubmitter) isInJail(ctx context.Context) (bool, error) { @@ -524,6 +442,21 @@ func (l *L2OutputSubmitter) isInJail(ctx context.Context) (bool, error) { return isInJail, nil } +func (l *L2OutputSubmitter) getLeftTimeForL2Blocks(currentBlockNumber *big.Int, targetBlockNumber *big.Int) time.Duration { + l.log.Info("submission interval has not elapsed", "currentBlockNumber", currentBlockNumber, "targetBlockNumber", targetBlockNumber) + waitBlockNum := new(big.Int).Sub(targetBlockNumber, currentBlockNumber) + + var waitDuration time.Duration + if waitBlockNum.Cmp(common.Big0) == -1 { + waitDuration = l.cfg.OutputSubmitterRetryInterval + } else { + waitDuration = time.Duration(new(big.Int).Mul(waitBlockNum, l.l2BlockTime).Uint64()) * time.Second + } + + l.log.Info("wait for L2 blocks proceeding", "waitDuration", waitDuration) + return waitDuration +} + type roundInfo struct { isPublicRound bool isPriorityValidator bool @@ -538,12 +471,12 @@ func (r *roundInfo) canJoinRound() bool { // fetchCurrentRound fetches next validator address from ValidatorPool or ValidatorManager contract. // It returns if current round is public round, and if selected for priority validator if it's a priority round. -func (l *L2OutputSubmitter) fetchCurrentRound(ctx context.Context) (roundInfo, error) { +func (l *L2OutputSubmitter) fetchCurrentRound(ctx context.Context, outputIndex *big.Int) (roundInfo, error) { cCtx, cCancel := context.WithTimeout(ctx, l.cfg.NetworkTimeout) defer cCancel() ri := roundInfo{canJoinPublicRound: l.cfg.OutputSubmitterAllowPublicRound} - nextValidator, err := l.getNextValidatorAddress(cCtx) + nextValidator, err := l.getNextValidatorAddress(cCtx, outputIndex) if err != nil { l.log.Error("unable to get next validator address", "err", err) ri.isPublicRound = false @@ -573,6 +506,15 @@ func (l *L2OutputSubmitter) fetchCurrentRound(ctx context.Context) (roundInfo, e return ri, nil } +// getNextValidatorAddress selects the appropriate contract and retrieves the next validator address. +func (l *L2OutputSubmitter) getNextValidatorAddress(ctx context.Context, outputIndex *big.Int) (common.Address, error) { + opts := optsutils.NewSimpleCallOpts(ctx) + if l.IsValPoolTerminated(outputIndex) { + return l.valManagerContract.NextValidator(opts) + } + return l.valpoolContract.NextValidator(opts) +} + // FetchOutput gets the output information to the corresponding block number. // It returns the output info if the output can be made, otherwise error. func (l *L2OutputSubmitter) FetchOutput(ctx context.Context, blockNumber *big.Int) (*eth.OutputResponse, error) { @@ -606,61 +548,20 @@ func SubmitL2OutputTxData(abi *abi.ABI, output *eth.OutputResponse) ([]byte, err ) } -// getNextValidatorAddress selects the appropriate contract and retrieves the next validator address. -func (l *L2OutputSubmitter) getNextValidatorAddress(ctx context.Context) (common.Address, error) { - var contract interface { - NextValidator(opts *bind.CallOpts) (common.Address, error) - } - - if l.isValManagerEnabled { - contract = l.valManagerContract - } else { - contract = l.valpoolContract - } - - return contract.NextValidator(optsutils.NewSimpleCallOpts(ctx)) -} - -// startValidatorTx creates start validator tx candidate. +// activateValidatorTx creates activate validator tx candidate. // It sends the candidate to txCandidates channel to process validator's tx candidates in order. -func (l *L2OutputSubmitter) startValidatorTx(data []byte) *txmgr.TxResponse { - gasTipCap, basefee, _, err := l.cfg.TxManager.SuggestGasPriceCaps(l.ctx) - if err != nil { - return &txmgr.TxResponse{ - Receipt: nil, - Err: fmt.Errorf("failed to get gas price info: %w", err), - } - } - gasFeeCap := txmgr.CalcGasFeeCap(basefee, gasTipCap) - - to := &l.cfg.ValidatorManagerAddr - estimatedGas, err := l.cfg.L1Client.EstimateGas(l.ctx, ethereum.CallMsg{ - From: l.cfg.TxManager.From(), - To: to, - GasFeeCap: gasFeeCap, - GasTipCap: gasTipCap, - Data: data, - }) - if err != nil { - return &txmgr.TxResponse{ - Receipt: nil, - Err: fmt.Errorf("failed to estimate gas: %w", err), - } - } - +func (l *L2OutputSubmitter) activateValidatorTx(data []byte) *txmgr.TxResponse { return l.cfg.TxManager.SendTxCandidate(l.ctx, &txmgr.TxCandidate{ - TxData: data, - To: to, - GasLimit: estimatedGas * 3 / 2, + TxData: data, + To: &l.cfg.ValidatorManagerAddr, }) - } // submitL2OutputTx creates l2 output submit tx candidate and sends it to txCandidates channel to process validator's tx candidates in order. -func (l *L2OutputSubmitter) submitL2OutputTx(data []byte) *txmgr.TxResponse { +func (l *L2OutputSubmitter) submitL2OutputTx(data []byte, outputIndex *big.Int) *txmgr.TxResponse { var name string var accessListAddr common.Address - if l.isValManagerEnabled { + if l.IsValPoolTerminated(outputIndex) { name = "ValidatorManager" accessListAddr = l.cfg.ValidatorManagerAddr } else { @@ -691,7 +592,7 @@ func (l *L2OutputSubmitter) submitL2OutputTx(data []byte) *txmgr.TxResponse { } var storageKeys []common.Hash - if l.isValManagerEnabled { + if l.IsValPoolTerminated(outputIndex) { storageKeys = []common.Hash{priorityValidatorSlot} } else { storageKeys = []common.Hash{outputIndexSlot, priorityValidatorSlot} @@ -741,7 +642,3 @@ func (l *L2OutputSubmitter) submitL2OutputTx(data []byte) *txmgr.TxResponse { func (l *L2OutputSubmitter) L2ooAbi() *abi.ABI { return l.l2ooABI } - -func (l *L2OutputSubmitter) ValManagerAbi() *abi.ABI { - return l.valManagerAbi -} diff --git a/kroma-validator/status.go b/kroma-validator/status.go new file mode 100644 index 000000000..b349b89a2 --- /dev/null +++ b/kroma-validator/status.go @@ -0,0 +1,10 @@ +package validator + +const ( + StatusNone uint8 = iota + StatusExited + StatusRegistered + StatusReady + StatusInactive + StatusActive +) diff --git a/kroma-validator/validator/status.go b/kroma-validator/validator/status.go deleted file mode 100644 index 7a2b8b5c5..000000000 --- a/kroma-validator/validator/status.go +++ /dev/null @@ -1,12 +0,0 @@ -package validator - -const ( - // StatusNone is regarded as a challenge is not in progress. - // The other status are regarded as a challenge is in progress. - StatusNone uint8 = iota - StatusInactive - StatusActive - StatusCanStart - StatusStarted - StatusCanSubmitOutput -) diff --git a/op-e2e/actions/l2_challenger.go b/op-e2e/actions/l2_challenger.go index 57ed5c49a..beeffb9a1 100644 --- a/op-e2e/actions/l2_challenger.go +++ b/op-e2e/actions/l2_challenger.go @@ -30,7 +30,7 @@ func (v *L2Validator) ActCreateChallenge(t Testing, outputIndex *big.Int) common return status == chal.StatusNone || status == chal.StatusChallengerTimeout }, "challenge is already in progress") - canCreateChallenge, err := v.challenger.CanCreateChallenge(t.Ctx()) + canCreateChallenge, err := v.challenger.CanCreateChallenge(t.Ctx(), outputIndex) require.NoError(t, err, "unable to check challenger balance") require.True(t, canCreateChallenge, "challenger not enough balance to create challenge") diff --git a/op-e2e/actions/l2_validator.go b/op-e2e/actions/l2_validator.go index 24d22a35a..01e99c513 100644 --- a/op-e2e/actions/l2_validator.go +++ b/op-e2e/actions/l2_validator.go @@ -3,6 +3,7 @@ package actions import ( "context" "crypto/ecdsa" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "math/big" "time" @@ -21,7 +22,7 @@ import ( "github.com/stretchr/testify/require" "github.com/kroma-network/kroma/kroma-bindings/bindings" - "github.com/kroma-network/kroma/kroma-validator" + validator "github.com/kroma-network/kroma/kroma-validator" validatormetrics "github.com/kroma-network/kroma/kroma-validator/metrics" ) @@ -172,7 +173,11 @@ func (v *L2Validator) sendTx(t Testing, toAddr *common.Address, txValue *big.Int func (v *L2Validator) CalculateWaitTime(t Testing) time.Duration { nextBlockNumber, err := v.l2os.FetchNextBlockNumber(t.Ctx()) require.NoError(t, err) - calculatedWaitTime := v.l2os.CalculateWaitTime(t.Ctx(), nextBlockNumber) + + outputIndex, err := v.l2os.FetchNextOutputIndex(t.Ctx()) + require.NoError(t, err) + + calculatedWaitTime := v.l2os.CalculateWaitTime(t.Ctx(), nextBlockNumber, outputIndex) return calculatedWaitTime } @@ -218,15 +223,18 @@ func (v *L2Validator) ActRegisterValidator(t Testing, assets *big.Int) { ) require.NoError(t, err) - v.sendTx(t, &v.valManagerContractAddr, common.Big0, txData, 2) + v.sendTx(t, &v.valManagerContractAddr, common.Big0, txData, 1) } -func (v *L2Validator) ActApprove(t Testing, amount uint64) { - tokenAddr := v.getGovTokenAddr(t) +func (v *L2Validator) ActApprove(t Testing, assets *big.Int) { + assetManagerContract, err := bindings.NewAssetManagerCaller(v.assetManagerContractAddr, v.cfg.L1Client) + tokenAddr, err := assetManagerContract.ASSETTOKEN(&bind.CallOpts{}) + require.NoError(t, err) + governanceTokenABI, err := bindings.GovernanceTokenMetaData.GetAbi() require.NoError(t, err) - txData, err := governanceTokenABI.Pack("approve", &v.assetManagerContractAddr, new(big.Int).SetUint64(amount)) + txData, err := governanceTokenABI.Pack("approve", &v.assetManagerContractAddr, assets) require.NoError(t, err) v.sendTx(t, &tokenAddr, common.Big0, txData, 1) @@ -239,19 +247,9 @@ func (v *L2Validator) fetchOutput(t Testing, blockNumber *big.Int) *eth.OutputRe return output } -func (v *L2Validator) getGovTokenAddr(t Testing) common.Address { - assetManagerABI, err := bindings.AssetManagerMetaData.GetAbi() - require.NoError(t, err) - - callData, err := assetManagerABI.Pack("ASSET_TOKEN") - require.NoError(t, err) - - returnData, err := v.l1.CallContract(t.Ctx(), ethereum.CallMsg{ - To: &v.assetManagerContractAddr, - Data: callData, - }, nil) +func (v *L2Validator) CheckIsValPoolTerminated(t Testing) bool { + outputIndex, err := v.l2os.FetchNextOutputIndex(t.Ctx()) require.NoError(t, err) - tokenAddr := common.BytesToAddress(returnData) - return tokenAddr + return v.l2os.IsValPoolTerminated(outputIndex) } diff --git a/op-e2e/actions/l2_validator_test.go b/op-e2e/actions/l2_validator_test.go index 7c6c0858e..226869fb2 100644 --- a/op-e2e/actions/l2_validator_test.go +++ b/op-e2e/actions/l2_validator_test.go @@ -5,13 +5,13 @@ import ( "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/stretchr/testify/require" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils" - "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/kroma-network/kroma/kroma-bindings/bindings" @@ -42,13 +42,36 @@ func TestValidatorBatchType(t *testing.T) { } } +func proceedWithBlocks(t StatefulTesting, miner *L1Miner, sequencer *L2Sequencer, batcher *L2Batcher, batcherAddr common.Address, n int) { + for i := 0; i < n; i++ { + // L1 block + miner.ActEmptyBlock(t) + // L2 block + sequencer.ActL1HeadSignal(t) + sequencer.ActL2PipelineFull(t) + sequencer.ActBuildToL1Head(t) + // submit and include in L1 + batcher.ActSubmitAll(t) + miner.includeL1Block(t, batcherAddr, 12) + // finalize the first and second L1 blocks, including the batch + miner.ActL1SafeNext(t) + miner.ActL1SafeNext(t) + miner.ActL1FinalizeNext(t) + miner.ActL1FinalizeNext(t) + // derive and see the L2 chain fully finalize + sequencer.ActL2PipelineFull(t) + sequencer.ActL1SafeSignal(t) + sequencer.ActL1FinalizedSignal(t) + } +} + func RunValidatorPoolTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { t := NewDefaultTesting(gt) dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp.DeployConfig.L2GenesisDeltaTimeOffset = deltaTimeOffset sd := e2eutils.Setup(t, dp, defaultAlloc) - log := testlog.Logger(t, log.LvlDebug) + log := testlog.Logger(t, log.LevelDebug) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) rollupSeqCl := sequencer.RollupClient() @@ -66,24 +89,7 @@ func RunValidatorPoolTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { AllowNonFinalized: false, }, miner.EthClient(), seqEngine.EthClient(), sequencer.RollupClient()) - // L1 block - miner.ActEmptyBlock(t) - // L2 block - sequencer.ActL1HeadSignal(t) - sequencer.ActL2PipelineFull(t) - sequencer.ActBuildToL1Head(t) - // submit and include in L1 - batcher.ActSubmitAll(t) - miner.includeL1Block(t, dp.Addresses.Batcher, 12) - // finalize the first and second L1 blocks, including the batch - miner.ActL1SafeNext(t) - miner.ActL1SafeNext(t) - miner.ActL1FinalizeNext(t) - miner.ActL1FinalizeNext(t) - // derive and see the L2 chain fully finalize - sequencer.ActL2PipelineFull(t) - sequencer.ActL1SafeSignal(t) - sequencer.ActL1FinalizedSignal(t) + proceedWithBlocks(t, miner, sequencer, batcher, dp.Addresses.Batcher, 1) // deposit bond for validator validator.ActDeposit(t, 1_000) @@ -100,7 +106,6 @@ func RunValidatorPoolTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { validator.ActSubmitL2Output(t) // include output on L1 miner.includeL1Block(t, validator.address, 12) - miner.ActEmptyBlock(t) // Check submission was successful receipt, err := miner.EthClient().TransactionReceipt(t.Ctx(), validator.LastSubmitL2OutputTx()) require.NoError(t, err) @@ -131,7 +136,7 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { dp := e2eutils.MakeDeployParams(t, defaultRollupTestParams) dp.DeployConfig.L2GenesisDeltaTimeOffset = deltaTimeOffset sd := e2eutils.Setup(t, dp, defaultAlloc) - log := testlog.Logger(t, log.LvlDebug) + log := testlog.Logger(t, log.LevelDebug) miner, seqEngine, sequencer := setupSequencerTest(t, sd, log) rollupSeqCl := sequencer.RollupClient() @@ -149,26 +154,7 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { AllowNonFinalized: false, }, miner.EthClient(), seqEngine.EthClient(), sequencer.RollupClient()) - for i := 0; i < 5; i++ { - // L1 block - miner.ActEmptyBlock(t) - // L2 block - sequencer.ActL1HeadSignal(t) - sequencer.ActL2PipelineFull(t) - sequencer.ActBuildToL1Head(t) - // submit and include in L1 - batcher.ActSubmitAll(t) - miner.includeL1Block(t, dp.Addresses.Batcher, 12) - // finalize the first and second L1 blocks, including the batch - miner.ActL1SafeNext(t) - miner.ActL1SafeNext(t) - miner.ActL1FinalizeNext(t) - miner.ActL1FinalizeNext(t) - // derive and see the L2 chain fully finalize - sequencer.ActL2PipelineFull(t) - sequencer.ActL1SafeSignal(t) - sequencer.ActL1FinalizedSignal(t) - } + proceedWithBlocks(t, miner, sequencer, batcher, dp.Addresses.Batcher, 6) // deposit bond for validator validator.ActDeposit(t, 1_000) @@ -179,19 +165,23 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { validator.ActSubmitL2Output(t) // include output on L1 miner.includeL1Block(t, validator.address, 12) - miner.ActEmptyBlock(t) // Check submission was successful receipt, err := miner.EthClient().TransactionReceipt(t.Ctx(), validator.LastSubmitL2OutputTx()) require.NoError(t, err) require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "submission failed") } + // Check if the ValPool is able to submit the outputs to the ValManager + isValPoolTerminated := validator.CheckIsValPoolTerminated(t) + require.True(t, isValPoolTerminated, "ValPool should be terminated") + // approve governance token - validator.ActApprove(t, 1_000) + assets := new(big.Int).SetUint64(1_000) + validator.ActApprove(t, assets) miner.includeL1Block(t, validator.address, 12) // register validator - validator.ActRegisterValidator(t, new(big.Int).SetUint64(1_000)) + validator.ActRegisterValidator(t, assets) miner.includeL1Block(t, validator.address, 12) require.Equal(t, sequencer.SyncStatus().UnsafeL2, sequencer.SyncStatus().FinalizedL2) @@ -205,7 +195,6 @@ func RunValidatorManagerTest(gt *testing.T, deltaTimeOffset *hexutil.Uint64) { validator.ActSubmitL2Output(t) // include output on L1 miner.includeL1Block(t, validator.address, 12) - miner.ActEmptyBlock(t) // Check submission was successful receipt, err := miner.EthClient().TransactionReceipt(t.Ctx(), validator.LastSubmitL2OutputTx()) require.NoError(t, err) diff --git a/ops-devnet/docker-compose.yml b/ops-devnet/docker-compose.yml index b57772958..1b2d06fdc 100644 --- a/ops-devnet/docker-compose.yml +++ b/ops-devnet/docker-compose.yml @@ -232,6 +232,8 @@ services: VALIDATOR_L2OO_ADDRESS: "${L2OO_ADDRESS}" VALIDATOR_COLOSSEUM_ADDRESS: "${COLOSSEUM_ADDRESS}" VALIDATOR_VALPOOL_ADDRESS: "${VALPOOL_ADDRESS}" + VALIDATOR_VALMANAGER_ADDRESS: "${VALMANAGER_ADDRESS}" + VALIDATOR_ASSETMANAGER_ADDRESS: "${ASSETMANAGER_ADDRESS}" VALIDATOR_ALLOW_NON_FINALIZED: "true" VALIDATOR_PROVER_RPC: "0.0.0.0:0" VALIDATOR_OUTPUT_SUBMITTER_ENABLED: "false" diff --git a/packages/contracts/contracts/test/ValidatorPool.t.sol b/packages/contracts/contracts/test/ValidatorPool.t.sol index 000857a41..cfa0df616 100644 --- a/packages/contracts/contracts/test/ValidatorPool.t.sol +++ b/packages/contracts/contracts/test/ValidatorPool.t.sol @@ -796,25 +796,7 @@ contract ValidatorPool_SystemUpgrade_Test is ValidatorSystemUpgrade_Initializer } function test_deposit_afterSystemUpgrade_reverts() external { - vm.prank(trusted); - pool.deposit{ value: trusted.balance }(); - - bool poolTerminated; - for (uint256 i; i <= terminateOutputIndex + 1; i++) { - uint256 nextOutputIndex = oracle.nextOutputIndex(); - poolTerminated = pool.isTerminated(nextOutputIndex); - if (nextOutputIndex <= terminateOutputIndex) { - assertFalse(poolTerminated); - } else { - assertTrue(poolTerminated); - } - - warpToSubmitTime(); - uint256 nextBlockNumber = oracle.nextBlockNumber(); - bytes32 outputRoot = keccak256(abi.encode(nextBlockNumber)); - vm.prank(pool.nextValidator()); - mockOracle.addOutput(outputRoot, nextBlockNumber); - } + test_isTerminated_succeeds(); vm.deal(trusted, requiredBondAmount); @@ -823,7 +805,7 @@ contract ValidatorPool_SystemUpgrade_Test is ValidatorSystemUpgrade_Initializer pool.deposit{ value: requiredBondAmount }(); } - function test_isTerminated_succeeds() external { + function test_isTerminated_succeeds() public { vm.prank(trusted); pool.deposit{ value: trusted.balance }();