Skip to content

Commit

Permalink
Merge pull request #2520 from OffchainLabs/fast-conf-retry
Browse files Browse the repository at this point in the history
Try to fast confirm past nodes too
  • Loading branch information
joshuacolvin0 authored Aug 6, 2024
2 parents 7defbd2 + b959a78 commit 6073359
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 20 deletions.
4 changes: 4 additions & 0 deletions arbnode/dataposter/dataposter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,10 @@ func (c *stubL1Client) CallContractAtHash(ctx context.Context, msg ethereum.Call
return []byte{}, nil
}

func (c *stubL1Client) CodeAtHash(ctx context.Context, address common.Address, blockHash common.Hash) ([]byte, error) {
return []byte{}, nil
}

func (c *stubL1Client) ChainID(ctx context.Context) (*big.Int, error) {
return nil, nil
}
Expand Down
2 changes: 1 addition & 1 deletion arbutil/wait_for_l1.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import (

type L1Interface interface {
bind.ContractBackend
bind.BlockHashContractCaller
ethereum.ChainReader
ethereum.ChainStateReader
ethereum.TransactionReader
TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error)
BlockNumber(ctx context.Context) (uint64, error)
CallContractAtHash(ctx context.Context, msg ethereum.CallMsg, blockHash common.Hash) ([]byte, error)
PendingCallContract(ctx context.Context, msg ethereum.CallMsg) ([]byte, error)
ChainID(ctx context.Context) (*big.Int, error)
Client() rpc.ClientInterface
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,10 @@ require (
gopkg.in/natefinch/lumberjack.v2 v2.0.0
)

require github.com/google/go-querystring v1.1.0 // indirect
require (
github.com/google/btree v1.1.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
)

require (
github.com/DataDog/zstd v1.4.5 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,10 @@ github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXi
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
Expand Down
26 changes: 20 additions & 6 deletions staker/fast_confirm.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,11 @@ func NewFastConfirmSafe(
return fastConfirmSafe, nil
}

func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error {
fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot)
func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash, nodeHash common.Hash) error {
if f.wallet.Address() == nil {
return errors.New("fast confirmation requires a wallet which is not setup")
}
fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot, nodeHash)
if err != nil {
return err
}
Expand Down Expand Up @@ -112,6 +115,16 @@ func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash com
return err
}
}

alreadyApproved, err := f.safe.ApprovedHashes(&bind.CallOpts{Context: ctx}, *f.wallet.Address(), safeTxHash)
if err != nil {
return err
}
if alreadyApproved.Cmp(common.Big1) == 0 {
_, err = f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash)
return err
}

auth, err := f.builder.Auth(ctx)
if err != nil {
return err
Expand Down Expand Up @@ -162,11 +175,12 @@ func (f *FastConfirmSafe) flushTransactions(ctx context.Context) error {
}

func (f *FastConfirmSafe) createFastConfirmCalldata(
blockHash common.Hash, sendRoot common.Hash,
blockHash common.Hash, sendRoot common.Hash, nodeHash common.Hash,
) ([]byte, error) {
calldata, err := f.fastConfirmNextNodeMethod.Inputs.Pack(
blockHash,
sendRoot,
nodeHash,
)
if err != nil {
return nil, err
Expand All @@ -177,12 +191,12 @@ func (f *FastConfirmSafe) createFastConfirmCalldata(
}

func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) (bool, error) {
if f.wallet.Address() == nil {
return false, errors.New("wallet address is nil")
}
var signatures []byte
approvedHashCount := uint64(0)
for _, owner := range f.owners {
if f.wallet.Address() == nil {
return false, errors.New("wallet address is nil")
}
var approved *big.Int
// No need check if wallet has approved the hash,
// since checkApprovedHashAndExecTransaction is called only after wallet has approved the hash.
Expand Down
95 changes: 84 additions & 11 deletions staker/staker.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/rpc"
"github.com/google/btree"
flag "github.com/spf13/pflag"

"github.com/offchainlabs/nitro/arbnode/dataposter"
Expand Down Expand Up @@ -246,6 +247,11 @@ type LatestConfirmedNotifier interface {
UpdateLatestConfirmed(count arbutil.MessageIndex, globalState validator.GoGlobalState)
}

type validatedNode struct {
number uint64
hash common.Hash
}

type Staker struct {
*L1Validator
stopwaiter.StopWaiter
Expand All @@ -258,6 +264,7 @@ type Staker struct {
highGasBlocksBuffer *big.Int
lastActCalledBlock *big.Int
inactiveLastCheckedNode *nodeAndHash
inactiveValidatedNodes *btree.BTreeG[validatedNode]
bringActiveUntilNode uint64
inboxReader InboxReaderInterface
statelessBlockValidator *StatelessBlockValidator
Expand Down Expand Up @@ -326,6 +333,9 @@ func NewStaker(
return nil, err
}
}
inactiveValidatedNodes := btree.NewG(2, func(a, b validatedNode) bool {
return a.number < b.number || (a.number == b.number && a.hash.Cmp(b.hash) < 0)
})
return &Staker{
L1Validator: val,
l1Reader: l1Reader,
Expand All @@ -339,6 +349,7 @@ func NewStaker(
statelessBlockValidator: statelessBlockValidator,
fatalErr: fatalErr,
fastConfirmSafe: fastConfirmSafe,
inactiveValidatedNodes: inactiveValidatedNodes,
}, nil
}

Expand Down Expand Up @@ -371,29 +382,29 @@ func (s *Staker) Initialize(ctx context.Context) error {
return nil
}

func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error {
func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64, hash common.Hash) error {
if !s.config.EnableFastConfirmation {
return nil
}
nodeInfo, err := s.rollup.LookupNode(ctx, number)
if err != nil {
return err
}
return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot)
return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot, hash)
}

func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error {
func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash, nodeHash common.Hash) error {
if !s.config.EnableFastConfirmation {
return nil
}
if s.fastConfirmSafe != nil {
return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot)
return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot, nodeHash)
}
auth, err := s.builder.Auth(ctx)
if err != nil {
return err
}
_, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot)
_, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot, nodeHash)
return err
}

Expand Down Expand Up @@ -711,11 +722,68 @@ func (s *Staker) Act(ctx context.Context) (*types.Transaction, error) {
info.LatestStakedNodeHash = s.inactiveLastCheckedNode.hash
}

if s.config.EnableFastConfirmation {
firstUnresolvedNode, err := s.rollup.FirstUnresolvedNode(callOpts)
if err != nil {
return nil, err
}
if info.LatestStakedNode >= firstUnresolvedNode {
lastHeader, err := s.l1Reader.LastHeader(ctx)
if err != nil {
return nil, err
}
// To check if a node is correct, we simply check if we're staked on it.
// Since we're staked on it or a later node, this will tell us if it's correct.
// To keep this call consistent with the GetNode call, we pin a specific parent chain block hash.
checkNodeCorrectCallOpts := s.getCallOpts(ctx)
checkNodeCorrectCallOpts.BlockHash = lastHeader.ParentHash
nodeInfo, err := s.rollup.GetNode(checkNodeCorrectCallOpts, firstUnresolvedNode)
if err != nil {
return nil, err
}
validatedNode, haveValidated := s.inactiveValidatedNodes.Get(validatedNode{
number: firstUnresolvedNode,
hash: nodeInfo.NodeHash,
})
confirmedCorrect := haveValidated && validatedNode.hash == nodeInfo.NodeHash
if !confirmedCorrect {
stakedOnNode, err := s.rollup.NodeHasStaker(checkNodeCorrectCallOpts, firstUnresolvedNode, walletAddressOrZero)
if err != nil {
return nil, err
}
confirmedCorrect = stakedOnNode
}
if confirmedCorrect {
err = s.tryFastConfirmationNodeNumber(ctx, firstUnresolvedNode, nodeInfo.NodeHash)
if err != nil {
return nil, err
}
if s.builder.BuildingTransactionCount() > 0 {
// Try to fast confirm previous nodes before working on new ones
log.Info("fast confirming previous node", "node", firstUnresolvedNode)
return s.wallet.ExecuteTransactions(ctx, s.builder, s.config.gasRefunder)
}
}
}
}

latestConfirmedNode, err := s.rollup.LatestConfirmed(callOpts)
if err != nil {
return nil, fmt.Errorf("error getting latest confirmed node: %w", err)
}

// Clear s.inactiveValidatedNodes of any entries before or equal to latestConfirmedNode
for {
validatedNode, ok := s.inactiveValidatedNodes.Min()
if !ok {
break
}
if validatedNode.number > latestConfirmedNode {
break
}
s.inactiveValidatedNodes.DeleteMin()
}

requiredStakeElevated, err := s.isRequiredStakeElevated(ctx)
if err != nil {
return nil, fmt.Errorf("error checking if required stake is elevated: %w", err)
Expand Down Expand Up @@ -900,7 +968,8 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
s.bringActiveUntilNode = info.LatestStakedNode + 1
}
info.CanProgress = false
return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot)
// We can't fast confirm a node that doesn't exist
return nil
}

// Details are already logged with more details in generateNodeAction
Expand All @@ -918,7 +987,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
if err != nil {
return fmt.Errorf("error staking on new node: %w", err)
}
return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot)
return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot, action.hash)
}

// If we have no stake yet, we'll put one down
Expand All @@ -940,7 +1009,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
return fmt.Errorf("error placing new stake on new node: %w", err)
}
info.StakeExists = true
return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot)
return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot, action.hash)
case existingNodeAction:
info.LatestStakedNode = action.number
info.LatestStakedNodeHash = action.hash
Expand All @@ -954,8 +1023,12 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
id: action.number,
hash: action.hash,
}
s.inactiveValidatedNodes.ReplaceOrInsert(validatedNode{
number: action.number,
hash: action.hash,
})
}
return s.tryFastConfirmationNodeNumber(ctx, action.number)
return s.tryFastConfirmationNodeNumber(ctx, action.number, action.hash)
}
log.Info("staking on existing node", "node", action.number)
// We'll return early if we already havea stake
Expand All @@ -968,7 +1041,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
if err != nil {
return fmt.Errorf("error staking on existing node: %w", err)
}
return s.tryFastConfirmationNodeNumber(ctx, action.number)
return s.tryFastConfirmationNodeNumber(ctx, action.number, action.hash)
}

// If we have no stake yet, we'll put one down
Expand All @@ -989,7 +1062,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv
return fmt.Errorf("error placing new stake on existing node: %w", err)
}
info.StakeExists = true
return s.tryFastConfirmationNodeNumber(ctx, action.number)
return s.tryFastConfirmationNodeNumber(ctx, action.number, action.hash)
default:
panic("invalid action type")
}
Expand Down

0 comments on commit 6073359

Please sign in to comment.