Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/deleting challenges #33

Merged
merged 6 commits into from
Oct 22, 2024
Merged
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
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
}

beer-1 marked this conversation as resolved.
Show resolved Hide resolved
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
}
beer-1 marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading