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

Use Desired RPC Block Number in Chain Watcher #720

Merged
merged 7 commits into from
Jan 7, 2025
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
2 changes: 1 addition & 1 deletion assertions/poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func (m *Manager) waitToPostIfNeeded(
}
minPeriodBlocks := m.chain.MinAssertionPeriodBlocks()
for {
latestBlockNumber, err := m.backend.HeaderU64(ctx)
latestBlockNumber, err := m.chain.DesiredHeaderU64(ctx)
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions assertions/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (m *Manager) syncAssertions(ctx context.Context) {
return
}
toBlock, err := retry.UntilSucceeds(ctx, func() (uint64, error) {
return m.backend.HeaderU64(ctx)
return m.chain.DesiredHeaderU64(ctx)
})
if err != nil {
log.Error("Could not get header by number", "err", err)
Expand Down Expand Up @@ -89,7 +89,7 @@ func (m *Manager) syncAssertions(ctx context.Context) {
for {
select {
case <-ticker.C:
toBlock, err := m.backend.HeaderU64(ctx)
toBlock, err := m.chain.DesiredHeaderU64(ctx)
if err != nil {
log.Error("Could not get header by number", "err", err)
continue
Expand Down
1 change: 1 addition & 0 deletions chain-abstraction/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ type AssertionChain interface {
GetAssertion(ctx context.Context, opts *bind.CallOpts, id AssertionHash) (Assertion, error)
IsChallengeComplete(ctx context.Context, challengeParentAssertionHash AssertionHash) (bool, error)
Backend() ChainBackend
DesiredHeaderU64(ctx context.Context) (uint64, error)
RollupAddress() common.Address
StakerAddress() common.Address
AssertionStatus(
Expand Down
17 changes: 14 additions & 3 deletions chain-abstraction/sol-implementation/assertion_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ func NewAssertionChain(
confirmedChallengesByParentAssertionHash: threadsafe.NewLruSet(1000, threadsafe.LruSetWithMetric[protocol.AssertionHash]("confirmedChallengesByParentAssertionHash")),
averageTimeForBlockCreation: time.Second * 12,
transactor: transactor,
rpcHeadBlockNumber: rpc.FinalizedBlockNumber,
rpcHeadBlockNumber: rpc.LatestBlockNumber,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expects the operator to provide the RPC head block number to another value if desired. In Nitro, this will default to Finalized. Using Latest here makes sure that our tests remain fast without needing to change test setups across the whole codebase with options

withdrawalAddress: copiedOpts.From, // Default to the tx opts' sender.
autoDeposit: true,
}
Expand Down Expand Up @@ -303,6 +303,17 @@ func (a *AssertionChain) Backend() protocol.ChainBackend {
return a.backend
}

func (a *AssertionChain) DesiredHeaderU64(ctx context.Context) (uint64, error) {
header, err := a.backend.HeaderByNumber(ctx, big.NewInt(int64(a.rpcHeadBlockNumber)))
if err != nil {
return 0, err
}
if !header.Number.IsUint64() {
return 0, errors.New("block number is not uint64")
}
return header.Number.Uint64(), nil
}

func (a *AssertionChain) GetAssertion(ctx context.Context, opts *bind.CallOpts, assertionHash protocol.AssertionHash) (protocol.Assertion, error) {
var b [32]byte
copy(b[:], assertionHash.Bytes())
Expand Down Expand Up @@ -685,7 +696,7 @@ func TryConfirmingAssertion(
}
for {
var latestHeaderNumber uint64
latestHeaderNumber, err = chain.Backend().HeaderU64(ctx)
latestHeaderNumber, err = chain.DesiredHeaderU64(ctx)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -1015,7 +1026,7 @@ func (a *AssertionChain) AssertionUnrivaledBlocks(ctx context.Context, assertion
// If there is no second child, we simply return the number of blocks
// since the assertion was created and its parent.
if prevNode.SecondChildBlock == 0 {
num, err := a.backend.HeaderU64(ctx)
num, err := a.DesiredHeaderU64(ctx)
if err != nil {
return 0, err
}
Expand Down
26 changes: 14 additions & 12 deletions challenge-manager/chain-watcher/watcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"context"
"fmt"
"math"
"math/big"
"sync/atomic"
"time"

Expand Down Expand Up @@ -245,7 +246,7 @@ func (w *Watcher) Start(ctx context.Context) {
for {
select {
case <-ticker.C:
toBlock, err := w.backend.HeaderU64(ctx)
toBlock, err := w.chain.DesiredHeaderU64(ctx)
if err != nil {
log.Error("Could not get latest header", "err", err)
continue
Expand Down Expand Up @@ -290,7 +291,7 @@ func (w *Watcher) Start(ctx context.Context) {
// GetRoyalEdges returns all royal, tracked edges in the watcher by assertion
// hash.
func (w *Watcher) GetRoyalEdges(ctx context.Context) (map[protocol.AssertionHash][]*api.JsonTrackedRoyalEdge, error) {
blockNum, err := w.chain.Backend().HeaderU64(ctx)
blockNum, err := w.chain.DesiredHeaderU64(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -386,7 +387,7 @@ func (w *Watcher) ComputeAncestors(
challengedAssertionHash,
)
}
blockHeaderNumber, err := w.chain.Backend().HeaderU64(ctx)
blockHeaderNumber, err := w.chain.DesiredHeaderU64(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -421,7 +422,7 @@ func (w *Watcher) IsEssentialAncestorConfirmable(
challengedAssertionHash,
)
}
blockHeaderNumber, err := w.chain.Backend().HeaderU64(ctx)
blockHeaderNumber, err := w.chain.DesiredHeaderU64(ctx)
if err != nil {
return false, err
}
Expand Down Expand Up @@ -453,7 +454,7 @@ func (w *Watcher) IsConfirmableEssentialEdge(
if !ok {
return false, nil, 0, fmt.Errorf("could not get challenge for top level assertion %#x", challengedAssertionHash)
}
blockHeaderNumber, err := w.chain.Backend().HeaderU64(ctx)
blockHeaderNumber, err := w.chain.DesiredHeaderU64(ctx)
if err != nil {
return false, nil, 0, err
}
Expand Down Expand Up @@ -935,23 +936,24 @@ type filterRange struct {
// Gets the start and end block numbers for our filter queries, starting from
// the latest confirmed assertion's block number up to the latest block number.
func (w *Watcher) getStartEndBlockNum(ctx context.Context) (filterRange, error) {
latestBlock, err := w.chain.Backend().HeaderU64(ctx)
desiredRPCBlock := w.chain.GetDesiredRpcHeadBlockNumber()
latestDesiredBlockHeader, err := w.chain.Backend().HeaderByNumber(ctx, big.NewInt(int64(desiredRPCBlock)))
if err != nil {
return filterRange{}, err
}
startBlock := latestBlock
if !latestDesiredBlockHeader.Number.IsUint64() {
return filterRange{}, errors.New("latest desired block number is not a uint64")
}
latestDesiredBlockNum := latestDesiredBlockHeader.Number.Uint64()
startBlock := latestDesiredBlockNum
if w.maxLookbackBlocks < startBlock {
startBlock = startBlock - w.maxLookbackBlocks
} else {
startBlock = 0
}
headerNumber, err := w.backend.HeaderU64(ctx)
if err != nil {
return filterRange{}, err
}
return filterRange{
startBlockNum: startBlock,
endBlockNum: headerNumber,
endBlockNum: latestDesiredBlockNum,
}, nil
}

Expand Down
6 changes: 4 additions & 2 deletions challenge-manager/challenge-tree/paths.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ type ComputePathWeightArgs struct {
BlockNum uint64
}

var ErrChildrenNotYetSeen = errors.New("child not yet tracked")

// ComputePathWeight from a child edge to a specified ancestor edge. A weight is the sum of the local timers
// of all edges along the path.
//
Expand Down Expand Up @@ -187,11 +189,11 @@ func (ht *RoyalChallengeTree) findEssentialPaths(
lowerChildId, upperChildId := lowerChildIdOpt.Unwrap(), upperChildIdOpt.Unwrap()
lowerChild, ok := ht.edges.TryGet(lowerChildId)
if !ok {
return nil, nil, fmt.Errorf("lower child not yet tracked")
return nil, nil, errors.Wrap(ErrChildrenNotYetSeen, "lower child")
}
upperChild, ok := ht.edges.TryGet(upperChildId)
if !ok {
return nil, nil, fmt.Errorf("upper child not yet tracked")
return nil, nil, errors.Wrap(ErrChildrenNotYetSeen, "upper child")
}
lowerTimer, err := ht.LocalTimer(ctx, lowerChild, blockNum)
if err != nil {
Expand Down
14 changes: 10 additions & 4 deletions challenge-manager/edge-tracker/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,21 +232,21 @@ func (et *Tracker) Act(ctx context.Context) error {
case EdgeStarted:
canOsp, err := canOneStepProve(ctx, et.edge)
if err != nil {
log.Error("Could not check if edge can be one step proven", fields, "err", err)
log.Error("Could not check if edge can be one step proven", append(fields, "err", err)...)
et.fsm.MarkError(err)
return et.fsm.Do(edgeBackToStart{})
}
wasConfirmed, err := et.tryToConfirmEdge(ctx)
if err != nil {
log.Error("Could not check if edge can be confirmed from start state", fields, "err", err)
log.Error("Could not check if edge can be confirmed from start state", append(fields, "err", err)...)
et.fsm.MarkError(err)
}
if wasConfirmed {
return et.fsm.Do(edgeAwaitChallengeCompletion{})
}
hasRival, err := et.edge.HasRival(ctx)
if err != nil {
log.Error("Could not check if edge has rival", fields, "err", err)
log.Error("Could not check if edge has rival", append(fields, "err", err)...)
et.fsm.MarkError(err)
return et.fsm.Do(edgeBackToStart{})
}
Expand All @@ -270,7 +270,7 @@ func (et *Tracker) Act(ctx context.Context) error {
case EdgeAtOneStepProof:
ok, err := et.isEssentialAncestorConfirmable(ctx)
if err != nil {
log.Error("Could not check if closest essential ancestor is confirmable", fields, "err", err)
log.Error("Could not check if closest essential ancestor is confirmable", append(fields, "err", err)...)
et.fsm.MarkError(err)
return et.fsm.Do(edgeBackToStart{})
}
Expand Down Expand Up @@ -464,6 +464,12 @@ func (et *Tracker) tryToConfirmEdge(ctx context.Context) (bool, error) {
chalPeriod,
)
if err != nil {
// If the error is that the child edges have not yet been observed by our chain watcher,
// we can simply return false and nil as they will eventually seen. This may occur when the validator
// is relying on safe or finalized data from the chain watcher.
if errors.Is(err, challengetree.ErrChildrenNotYetSeen) {
return false, nil
}
return false, errors.Wrap(err, "not check if essential edge is confirmable")
}
end := time.Since(start)
Expand Down
6 changes: 3 additions & 3 deletions testing/endtoend/e2e_crash_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ import (
// We cancel the honest validator's context after it opens the first subchallenge and prove that it
// can restart and carry things out to confirm the honest, claimed assertion in the challenge.
func TestEndToEnd_HonestValidatorCrashes(t *testing.T) {
t.Parallel()
neutralCtx, neutralCancel := context.WithCancel(context.Background())
defer neutralCancel()
evilCtx, evilCancel := context.WithCancel(context.Background())
Expand All @@ -41,6 +40,7 @@ func TestEndToEnd_HonestValidatorCrashes(t *testing.T) {
defer honestCancel()

protocolCfg := defaultProtocolParams()
protocolCfg.challengePeriodBlocks = 40
timeCfg := defaultTimeParams()
timeCfg.blockTime = time.Second
inboxCfg := defaultInboxParams()
Expand Down Expand Up @@ -276,8 +276,8 @@ func TestEndToEnd_HonestValidatorCrashes(t *testing.T) {
if sender != txOpts.From {
continue
}
// Skip edges that are not essential roots.
if it.Event.ClaimId == (common.Hash{}) {
// Skip edges that are not essential roots (skip the top-level edge).
if it.Event.ClaimId == (common.Hash{}) || it.Event.Level == 0 {
continue
}
honestEssentialRootIds[it.Event.EdgeId] = false
Expand Down
11 changes: 6 additions & 5 deletions testing/endtoend/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ type protocolParams struct {
func defaultProtocolParams() protocolParams {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reducing values here for faster CI times

return protocolParams{
numBigStepLevels: 1,
challengePeriodBlocks: 40,
challengePeriodBlocks: 15,
layerZeroHeights: protocol.LayerZeroHeights{
BlockChallengeHeight: 1 << 5,
BigStepChallengeHeight: 1 << 5,
SmallStepChallengeHeight: 1 << 5,
BlockChallengeHeight: 1 << 4,
BigStepChallengeHeight: 1 << 4,
SmallStepChallengeHeight: 1 << 4,
},
}
}
Expand Down Expand Up @@ -143,6 +143,7 @@ func TestEndToEnd_MaxWavmOpcodes(t *testing.T) {
BigStepChallengeHeight: 1 << 14,
SmallStepChallengeHeight: 1 << 14,
}
protocolCfg.challengePeriodBlocks = 30
runEndToEndTest(t, &e2eConfig{
backend: simulated,
protocol: protocolCfg,
Expand Down Expand Up @@ -176,10 +177,10 @@ func TestEndToEnd_TwoEvilValidators(t *testing.T) {
}

func TestEndToEnd_ManyEvilValidators(t *testing.T) {
t.Skip("This test is too slow to run in CI")
protocolCfg := defaultProtocolParams()
timeCfg := defaultTimeParams()
timeCfg.assertionPostingInterval = time.Hour
protocolCfg.challengePeriodBlocks = 50
runEndToEndTest(t, &e2eConfig{
backend: simulated,
protocol: protocolCfg,
Expand Down
5 changes: 5 additions & 0 deletions testing/mocks/mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,11 @@ func (m *MockProtocol) GetDesiredRpcHeadBlockNumber() rpc.BlockNumber {
}

// Read-only methods.
func (m *MockProtocol) DesiredHeaderU64(ctx context.Context) (uint64, error) {
args := m.Called()
return args.Get(0).(uint64), args.Error(1)
}

func (m *MockProtocol) Backend() protocol.ChainBackend {
args := m.Called()
return args.Get(0).(protocol.ChainBackend)
Expand Down
Loading