Skip to content

Commit

Permalink
Merge pull request #13 from pellartech/defender-challenge-auto-retry
Browse files Browse the repository at this point in the history
Defender challenge auto retry
  • Loading branch information
Sledro authored Jan 24, 2024
2 parents e8fe6a8 + 13d2732 commit 3147261
Show file tree
Hide file tree
Showing 19 changed files with 414 additions and 195 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ It is designed to work in unison with the [lightlink-hummingbird-contracts](http
hb rollup info # Get the current rollup state
hb rollup next # Generate the next rollup block
hb rollup start # Start the rollup loop to generate and submit bundles
hb challenge challenge-da <block_hash> # Challenge data availability
hb challenge challenge-da <block_number> # Challenge data availability
hb defender defend-da <block_hash> # Defend data availability
hd defender info-da <block_hash> # Provides info on an existing challenge
hb defender prove-da --tx <celestia_tx> # Prove data availability
hb defender prove-da <block_hash> # Prove data availability
hb defender start # Start the defender loop to watch and defend challenges
```

Expand Down
6 changes: 1 addition & 5 deletions cli/cmd/defender_defendda.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (

func init() {
DefenderDefendDaCmd.Flags().Bool("dry", false, "dry run will not submit the rollup block to the L1 rollup contract, and will not upload real data to celestia")
DefenderDefendDaCmd.Flags().String("tx", "", "celestia tx hash in which data was submitted")
DefenderDefendDaCmd.MarkFlagRequired("tx")
}

var DefenderDefendDaCmd = &cobra.Command{
Expand Down Expand Up @@ -49,10 +47,8 @@ var DefenderDefendDaCmd = &cobra.Command{

// get block hash and tx hash from args/flags
blockHash := common.HexToHash(args[0])
rawTxHash, _ := cmd.Flags().GetString("tx")
txHash := common.HexToHash(rawTxHash)

tx, err := d.DefendDA(blockHash, txHash)
tx, err := d.DefendDA(blockHash)
if err != nil {
logger.Error("Failed to defend data availability", "err", err)
panic(err)
Expand Down
15 changes: 6 additions & 9 deletions cli/cmd/defender_proveda.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@ import (
)

func init() {
DefenderProveDaCmd.Flags().String("tx", "", "celestia tx hash in which data was submitted")
DefenderProveDaCmd.Flags().Bool("json", false, "output proof in json format")
DefenderProveDaCmd.Flags().Bool("verify", false, "verify the proof against the L1 rollup contract")
}

var DefenderProveDaCmd = &cobra.Command{
Use: "prove-da",
Short: "prove-da will prove a data availability batch",
Args: cobra.MinimumNArgs(1),
ArgAliases: []string{
"block",
},
Run: func(cmd *cobra.Command, args []string) {
cfg := config.Load()
logger := GetLogger(viper.GetString("log-type"))
Expand All @@ -36,14 +39,8 @@ var DefenderProveDaCmd = &cobra.Command{
Logger: logger.With("ctx", "Defender"),
})

rawTxHash, err := cmd.Flags().GetString("tx")
if err != nil {
logger.Error("Missing required tx hash from flag", "err", err)
panic(err)
}

txHash := common.HexToHash(rawTxHash)
proof, err := d.ProveDA(txHash)
blockHash := common.HexToHash(args[0])
proof, err := d.ProveDA(blockHash)
if err != nil {
logger.Error("Failed to prove data availability", "err", err)
panic(err)
Expand Down
4 changes: 3 additions & 1 deletion cli/cmd/defender_start.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"hummingbird/defender"
"hummingbird/node"
"hummingbird/utils"
"time"

"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -22,7 +23,8 @@ var DefenderStartCmd = &cobra.Command{
utils.NoErr(err)

d := defender.NewDefender(n, &defender.Opts{
Logger: logger.With("ctx", "Defender"),
Logger: logger.With("ctx", "Defender"),
WorkerDelay: time.Duration(cfg.Defender.WorkerDelay) * time.Millisecond,
})

err = d.Start()
Expand Down
4 changes: 2 additions & 2 deletions cli/cmd/rollup_next.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ var RollupNextCmd = &cobra.Command{
fmt.Println(" Hash:", hash.Hex())
fmt.Println(" Bundle Size:", len(b.Bundle.Blocks))
fmt.Println(" Celestia Height:", b.CelestiaHeight)
fmt.Println(" Celestia Data Root:", common.BytesToHash(b.CelestiaDataRoot[:]).Hex())
fmt.Println(" Celestia Tx Hash:", b.CelestiaPointer.TxHash.Hex())
fmt.Println(" Celestia Share Start:", b.CelestiaShareStart)
fmt.Println(" Celestia Share Len:", b.CelestiaShareLen)
fmt.Println(" ")

// If dry run is enabled, exit.
Expand Down
3 changes: 3 additions & 0 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@
"l2pollDelay": 10000,
"storeCelestiaPointers": true,
"storeHeaders": true
},
"defender": {
"workerDelay": 60000
}
}
4 changes: 3 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ type Config struct {
StoreCelestiaPointers bool `mapstructure:"storeCelestiaPointers"`
StoreHeaders bool `mapstructure:"storeHeaders"`
} `mapstructure:"rollup"`

Defender struct {
WorkerDelay int `mapstructure:"workerDelay"`
} `mapstructure:"defender"`
// Not typically set in config file.
DryRun bool `mapstructure:"dryRun,omitempty"`
}
Expand Down
129 changes: 121 additions & 8 deletions defender/defender.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package defender
import (
"fmt"
"hummingbird/node"
"time"

"log/slog"
"os"
"os/signal"
"sync"
"syscall"

"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"
Expand All @@ -18,8 +20,9 @@ import (
)

type Opts struct {
Logger *slog.Logger
DryRun bool // DryRun indicates whether or not to actually submit the block to the L1 rollup contract.
Logger *slog.Logger
WorkerDelay time.Duration
DryRun bool // DryRun indicates whether or not to actually submit the block to the L1 rollup contract.
}

type Defender struct {
Expand All @@ -32,6 +35,9 @@ func NewDefender(node *node.Node, opts *Opts) *Defender {
}

func (d *Defender) Start() error {
d.retryMissedDAChallenges()
go d.retryActiveDAChallengesWorker()

if err := d.WatchAndDefendDAChallenges(); err != nil {
return fmt.Errorf("error watching and defending DA challenges: %w", err)
}
Expand Down Expand Up @@ -63,9 +69,23 @@ func (d *Defender) WatchAndDefendDAChallenges() error {
wg.Add(1)
go func(challenge *challengeContract.ChallengeChallengeDAUpdate) {
defer wg.Done()
err := d.handleDAChallenge(challenge)

header, err := d.Ethereum.GetRollupHeaderByHash(challenge.BlockHash)
if err != nil {
d.Opts.Logger.Error("error getting rollup header by hash:", "error", err)
}
err = d.Store.StoreLastScannedBlockNumber(header.Epoch)
if err != nil {
d.Opts.Logger.Error("error storing last scanned block number:", "error", err)
}

err = d.handleDAChallenge(challenge)
if err != nil {
d.Opts.Logger.Error("error handling challenge:", "challenge", challenge, "error", err)
err := d.Store.StoreActiveDAChallenge(challenge)
if err != nil {
d.Opts.Logger.Error("error storing active DA challenge:", "challenge", challenge, "error", err)
}
}
}(challenge)
}
Expand Down Expand Up @@ -97,7 +117,7 @@ func (d *Defender) handleDAChallenge(challenge *challengeContract.ChallengeChall

d.Opts.Logger.Info("Found CelestiaTx", "tx_hash", celestiaTx.TxHash.Hex(), "block_hash", blockHash.Hex())

tx, err := d.DefendDA(challenge.BlockHash, celestiaTx.TxHash)
tx, err := d.DefendDA(challenge.BlockHash)
if err != nil {
return fmt.Errorf("error defending DA: %w", err)
}
Expand All @@ -106,8 +126,8 @@ func (d *Defender) handleDAChallenge(challenge *challengeContract.ChallengeChall
return nil
}

func (d *Defender) DefendDA(block common.Hash, txHash common.Hash) (*types.Transaction, error) {
proof, err := d.ProveDA(txHash)
func (d *Defender) DefendDA(block common.Hash) (*types.Transaction, error) {
proof, err := d.ProveDA(block)
if err != nil {
return nil, fmt.Errorf("failed to prove data availability: %w", err)
}
Expand All @@ -116,6 +136,99 @@ func (d *Defender) DefendDA(block common.Hash, txHash common.Hash) (*types.Trans
return d.Ethereum.DefendDataRootInclusion(block, proof)
}

func (d *Defender) ProveDA(txHash common.Hash) (*node.CelestiaProof, error) {
return d.Celestia.GetProof(txHash[:])
func (d *Defender) ProveDA(block common.Hash) (*node.CelestiaProof, error) {
pointer, err := d.GetDAPointer(block)
if err != nil {
return nil, fmt.Errorf("failed to get Celestia pointer: %w", err)
}

if pointer == nil {
return nil, fmt.Errorf("no Celestia pointer found")
}

return d.Celestia.GetProof(pointer)
}

func (d *Defender) retryActiveDAChallengesWorker() {
ticker := time.NewTicker(d.Opts.WorkerDelay)
defer ticker.Stop()

for range ticker.C {
d.Opts.Logger.Info("Retrying active DA challenges...")
challenges, err := d.Store.GetActiveDAChallenges()
if err != nil {
d.Opts.Logger.Error("error getting active DA challenges from store:", "error", err)
continue
}
for _, challenge := range challenges {
block := common.BytesToHash(challenge.BlockHash[:])

// Check if challenge has expired, if so delete from active challenges and continue
if challenge.Expiry.Int64() <= time.Now().Unix() {
d.Opts.Logger.Info("Active DA challenge has expired, deleting from active challenges", "challengeBlock", block, "expiry", challenge.Expiry)
err = d.Store.DeleteActiveDAChallenge(challenge.BlockHash)
if err != nil {
d.Opts.Logger.Error("error deleting active DA challenge:", "challengeBlock", block, "error", err)
}
continue
}

err = d.handleDAChallenge(challenge)
if err != nil {
d.Opts.Logger.Error("error retrying active DA challenge:", "challengeBlock", block, "error", err)
continue
}

err = d.Store.DeleteActiveDAChallenge(challenge.BlockHash)
if err != nil {
d.Opts.Logger.Error("error deleting active DA challenge:", "challengeBlock", block, "error", err)
}
}
d.Opts.Logger.Info("Active DA challenges retry worker finished")
}
}

func (d *Defender) retryMissedDAChallenges() {
d.Opts.Logger.Info("Retrying missed DA challenges...")
lastScannedBlockNumber, err := d.Store.GetLastScannedBlockNumber()
if err != nil {
d.Opts.Logger.Error("error getting last scanned block number:", "error", err)
return
}

opts := &bind.FilterOpts{
Start: lastScannedBlockNumber,
}

status := []uint8{1}

challenges, err := d.Ethereum.FilterChallengeDAUpdate(opts, nil, nil, status)
if err != nil {
d.Opts.Logger.Error("error filtering challenges:", "error", err)
return
}

// iterate through challenges and retry
for challenges.Next() {
challenge := challenges.Event // Access the current challenge

// Check if challenge has already been defended
challengeInfo, err := d.Ethereum.GetDataRootInclusionChallenge(challenge.BlockHash)
if err != nil {
d.Opts.Logger.Error("error getting data root inclusion challenge:", "error", err)
continue
}

if challengeInfo.Status != 1 {
d.Opts.Logger.Info("DA challenge has already been defended", "blockIndex", challenge.BlockIndex)
continue
}

err = d.handleDAChallenge(challenge)
if err != nil {
d.Opts.Logger.Error("error retrying missed DA challenge:", "blockIndex", challenge.BlockIndex, "error", err)
continue
}
}
d.Opts.Logger.Info("Missed DA challenges retry finished")
}
2 changes: 1 addition & 1 deletion defender/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ import (
)

func (d *Defender) InfoDA(block common.Hash) (contracts.ChallengeDaInfo, error) {
return d.Ethereum.GetDataRootInlcusionChallenge(block)
return d.Ethereum.GetDataRootInclusionChallenge(block)
}
6 changes: 0 additions & 6 deletions node/bundle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ import (
"github.com/stretchr/testify/assert"
)

func randHash() common.Hash {
buf := make([]byte, 32)
crand.Read(buf)
return common.BytesToHash(buf)
}

func randAddr() common.Address {
buf := make([]byte, 20)
crand.Read(buf)
Expand Down
Loading

0 comments on commit 3147261

Please sign in to comment.