Skip to content

Commit

Permalink
Fix/deleting challenges (#33)
Browse files Browse the repository at this point in the history
* deleting pending events & pending challenges & future challenges when initializing nodes

* add disable_auto_set_l1_height flag to clarify it

* bug fix clone key value from iterator

* cleanup and add some comments

* fix typo

* fix parser cursor

---------

Co-authored-by: beer-1 <[email protected]>
  • Loading branch information
sh-cha and beer-1 authored Oct 22, 2024
1 parent 61574f4 commit 6f5a4d9
Show file tree
Hide file tree
Showing 13 changed files with 276 additions and 105 deletions.
75 changes: 47 additions & 28 deletions challenger/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Challenger

The Challenger is responsible for
The Challenger is responsible for

1. verifying that the `MsgInitiateTokenDeposit` event is properly relayed to `MsgFinalizeTokenDeposit`.
2. checking whether `MsgInitiateTokenDeposit` was relayed on time.
3. verifying that the `Oracle` data is properly relayed to `MsgUpdateOracle`.
Expand Down Expand Up @@ -28,10 +29,12 @@ To configure the Challenger, fill in the values in the `~/.opinit/challenger.jso
"bech32_prefix": "init",
"rpc_address": "tcp://localhost:27657",
},
// L1StartHeight is the height to start the l1 node. If it is 0, it will finds the optimal height and sets it automatically.
// However, if you do not want to use this feature, set it to a non-zero value.
// There is no need for modification under normal circumstances, because it
// is automatically determined when you set the l2 start height,
// DisableAutoSetL1Height is the flag to disable the automatic setting of the l1 height.
// If it is false, it will finds the optimal height and sets l1_start_height automatically
// from l2 start height and l1_start_height is ignored.
// It can be useful when you don't want to use TxSearch.
"disable_auto_set_l1_height": false,
// L1StartHeight is the height to start the l1 node.
"l1_start_height": 0,
// L2StartHeight is the height to start the l2 node. If it is 0, it will start from the latest height.
// If the latest height stored in the db is not 0, this config is ignored.
Expand All @@ -42,9 +45,10 @@ To configure the Challenger, fill in the values in the `~/.opinit/challenger.jso
```

### Start height config examples

If the latest height stored in the db is not 0, start height config is ignored.

```
```shell
Output tx 1
- L1BlockNumber: 10
- L2BlockNumber: 100
Expand All @@ -69,62 +73,75 @@ FinalizedTokenDeposit tx 2
```

#### Config 1

```json
{
l2_start_height: 150,
}
```

When Child's last l1 Sequence is `2`,

- L1 starts from the height 10 + 1 = 11
- L2 starts from the height 100 + 1 = 101


## Handler rules for the components of the Challenger
For registered events or tx handlers, work processed in a block is atomically saved as `pending events`. Therefore, if `pending events` with the `ChallengeEvent` interface cannot be processed due to an interrupt or error, it is guaranteed to be read from the DB and processed. When an event matching the pending event comes in and is processed, or when the block time exceeds the event's timeout, a `Challenge` is created and stored in the DB.
#### The challenger can check the generated `Challenges` and decide what action to take.

For registered events or tx handlers, work processed in a block is atomically saved as `pending events`. Therefore, if `pending events` with the `ChallengeEvent` interface cannot be processed due to an interrupt or error, it is guaranteed to be read from the DB and processed. When an event matching the pending event comes in and is processed, or when the block time exceeds the event's timeout, a `Challenge` is created and stored in the DB.

### The challenger can check the generated `Challenges` and decide what action to take

## Deposit

When the `initiate_token_deposit` event is detected in l1, saves it as a `Deposit` challenge event and check if it is the same as the `MsgFinalizeTokenDeposit` for the same sequence.

```go
// Deposit is the challenge event for the deposit
type Deposit struct {
EventType string `json:"event_type"`
Sequence uint64 `json:"sequence"`
L1BlockHeight int64 `json:"l1_block_height"`
From string `json:"from"`
To string `json:"to"`
L1Denom string `json:"l1_denom"`
Amount string `json:"amount"`
Time time.Time `json:"time"`
Timeout bool `json:"timeout"`
EventType string `json:"event_type"`
Sequence uint64 `json:"sequence"`
L1BlockHeight int64 `json:"l1_block_height"`
From string `json:"from"`
To string `json:"to"`
L1Denom string `json:"l1_denom"`
Amount string `json:"amount"`
Time time.Time `json:"time"`
Timeout bool `json:"timeout"`
}
```

## Output

When the `propose_output` event is detected in l1, saves it as a `Output` challenge event, replays up to l2 block number and check if `OutputRoot` is the same as submitted.

```go
// Output is the challenge event for the output
type Output struct {
EventType string `json:"event_type"`
L2BlockNumber int64 `json:"l2_block_number"`
OutputIndex uint64 `json:"output_index"`
OutputRoot []byte `json:"output_root"`
Time time.Time `json:"time"`
Timeout bool `json:"timeout"`
EventType string `json:"event_type"`
L2BlockNumber int64 `json:"l2_block_number"`
OutputIndex uint64 `json:"output_index"`
OutputRoot []byte `json:"output_root"`
Time time.Time `json:"time"`
Timeout bool `json:"timeout"`
}
```

## Oracle

If `oracle_enable` is turned on in bridge config, saves bytes of the 0th Tx as a `Oracle` challenge event and check if it is the same data in the `MsgUpdateOracle` for the l1 height.

## Batch
Batch data is not verified by the challenger bot.
#### TODO
* Challenger runs a L2 node it in rollup sync challenge mode in CometBFT to check whether the submitted batch is replayed properly.

Batch data is not verified by the challenger bot.

### TODO

- Challenger runs a L2 node it in rollup sync challenge mode in CometBFT to check whether the submitted batch is replayed properly.

## Query

### Status

```bash
curl localhost:3001/status
```
Expand Down Expand Up @@ -159,6 +176,7 @@ curl localhost:3001/status
```

### Challenges

```bash
curl localhost:3001/challenges/{page}
```
Expand All @@ -178,6 +196,7 @@ curl localhost:3001/challenges/{page}
```

### Pending events

```bash
curl localhost:3001/pending_events/host
```
Expand Down Expand Up @@ -209,4 +228,4 @@ curl localhost:3001/pending_events/child
"timeout": false
}
]
```
```
36 changes: 29 additions & 7 deletions challenger/challenger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"strconv"
"sync"
"time"

"github.com/pkg/errors"

Expand Down Expand Up @@ -101,14 +102,33 @@ func (c *Challenger) Initialize(ctx context.Context) error {
return err
}

err = c.host.Initialize(ctx, hostProcessedHeight, c.child, bridgeInfo, c)
var initialBlockTime time.Time
hostInitialBlockTime, err := c.host.Initialize(ctx, hostProcessedHeight, c.child, bridgeInfo, c)
if err != nil {
return err
}
err = c.child.Initialize(ctx, childProcessedHeight, processedOutputIndex+1, c.host, bridgeInfo, c)
if initialBlockTime.Before(hostInitialBlockTime) {
initialBlockTime = hostInitialBlockTime
}

childInitialBlockTime, err := c.child.Initialize(ctx, childProcessedHeight, processedOutputIndex+1, c.host, bridgeInfo, c)
if err != nil {
return err
}
if initialBlockTime.Before(childInitialBlockTime) {
initialBlockTime = childInitialBlockTime
}

// only called when `ResetHeight` was executed.
if !initialBlockTime.IsZero() {
// The db state is reset to a specific height, so we also
// need to delete future challenges which are not applicable anymore.
err := c.DeleteFutureChallenges(initialBlockTime)
if err != nil {
return err
}
}

c.RegisterQuerier()

c.pendingChallenges, err = c.loadPendingChallenges()
Expand Down Expand Up @@ -206,7 +226,9 @@ func (c *Challenger) getProcessedHeights(ctx context.Context, bridgeId uint64) (
}
}

if c.cfg.L1StartHeight == 0 {
if c.cfg.DisableAutoSetL1Height {
l1ProcessedHeight = c.cfg.L1StartHeight
} else {
// get the bridge start height from the host
l1ProcessedHeight, err = c.host.QueryCreateBridgeHeight(ctx, bridgeId)
if err != nil {
Expand All @@ -233,14 +255,14 @@ func (c *Challenger) getProcessedHeights(ctx context.Context, bridgeId uint64) (
if depositTxHeight > l1ProcessedHeight {
l1ProcessedHeight = depositTxHeight
}
if outputL1BlockNumber < l1ProcessedHeight {
if outputL1BlockNumber != 0 && outputL1BlockNumber < l1ProcessedHeight {
l1ProcessedHeight = outputL1BlockNumber
}
}
} else {
l1ProcessedHeight = c.cfg.L1StartHeight
}
l1ProcessedHeight--
if l1ProcessedHeight > 0 {
l1ProcessedHeight--
}

return l1ProcessedHeight, l2ProcessedHeight, processedOutputIndex, err
}
18 changes: 14 additions & 4 deletions challenger/child/child.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,30 @@ func NewChildV1(
}
}

func (ch *Child) Initialize(ctx context.Context, processedHeight int64, startOutputIndex uint64, host hostNode, bridgeInfo opchildtypes.BridgeInfo, challenger challenger) error {
func (ch *Child) Initialize(ctx context.Context, processedHeight int64, startOutputIndex uint64, host hostNode, bridgeInfo opchildtypes.BridgeInfo, challenger challenger) (time.Time, error) {
_, err := ch.BaseChild.Initialize(ctx, processedHeight, startOutputIndex, bridgeInfo)
if err != nil {
return err
return time.Time{}, err
}
ch.host = host
ch.challenger = challenger
ch.registerHandlers()

err = ch.eventHandler.Initialize(bridgeInfo.BridgeConfig.SubmissionInterval)
if err != nil {
return err
return time.Time{}, err
}
return nil

var blockTime time.Time

// only called when `ResetHeight` was executed.
if ch.Node().HeightInitialized() {
blockTime, err = ch.Node().QueryBlockTime(ctx, ch.Node().GetHeight())
if err != nil {
return time.Time{}, err
}
}
return blockTime, nil
}

func (ch *Child) registerHandlers() {
Expand Down
89 changes: 81 additions & 8 deletions challenger/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package challenger
import (
"fmt"
"slices"
"time"

challengertypes "github.com/initia-labs/opinit-bots/challenger/types"
"github.com/initia-labs/opinit-bots/node"
Expand Down Expand Up @@ -84,17 +85,43 @@ func (c *Challenger) loadChallenges() (challenges []challengertypes.Challenge, e
return
}

func (c *Challenger) DeleteFutureChallenges(initialBlockTime time.Time) error {
deletingKeys := make([][]byte, 0)
iterErr := c.db.PrefixedReverseIterate(challengertypes.ChallengeKey, func(key []byte, _ []byte) (stop bool, err error) {
ts, _, err := challengertypes.ParseChallenge(key)
if err != nil {
return true, err
}
if !ts.After(initialBlockTime) {
return true, nil
}

deletingKeys = append(deletingKeys, key)
return false, nil
})
if iterErr != nil {
return iterErr
}

for _, key := range deletingKeys {
err := c.db.Delete(key)
if err != nil {
return err
}
}
return nil
}

func ResetHeights(db types.DB) error {
dbs := []types.DB{
db.WithPrefix([]byte(types.HostName)),
db.WithPrefix([]byte(types.ChildName)),
dbNames := []string{
types.HostName,
types.ChildName,
}

for _, db := range dbs {
if err := node.DeleteSyncInfo(db); err != nil {
for _, dbName := range dbNames {
if err := ResetHeight(db, dbName); err != nil {
return err
}
fmt.Printf("reset height to 0 for node %s\n", string(db.GetPrefix()))
}
return nil
}
Expand All @@ -105,10 +132,56 @@ func ResetHeight(db types.DB, nodeName string) error {
return errors.New("unknown node name")
}
nodeDB := db.WithPrefix([]byte(nodeName))
err := node.DeleteSyncInfo(nodeDB)
if err != nil {

if err := DeletePendingEvents(db); err != nil {
return err
}

if err := DeletePendingChallenges(db); err != nil {
return err
}

if err := node.DeleteSyncInfo(nodeDB); err != nil {
return err
}
fmt.Printf("reset height to 0 for node %s\n", string(nodeDB.GetPrefix()))
return nil
}

func DeletePendingEvents(db types.DB) error {
deletingKeys := make([][]byte, 0)
iterErr := db.PrefixedIterate(challengertypes.PendingEventKey, func(key []byte, _ []byte) (stop bool, err error) {
deletingKeys = append(deletingKeys, key)
return false, nil
})
if iterErr != nil {
return iterErr
}

for _, key := range deletingKeys {
err := db.Delete(key)
if err != nil {
return err
}
}
return nil
}

func DeletePendingChallenges(db types.DB) error {
deletingKeys := make([][]byte, 0)
iterErr := db.PrefixedIterate(challengertypes.PendingChallengeKey, func(key []byte, _ []byte) (stop bool, err error) {
deletingKeys = append(deletingKeys, key)
return false, nil
})
if iterErr != nil {
return iterErr
}

for _, key := range deletingKeys {
err := db.Delete(key)
if err != nil {
return err
}
}
return nil
}
Loading

0 comments on commit 6f5a4d9

Please sign in to comment.