Skip to content

Commit

Permalink
fast confirmation with safe
Browse files Browse the repository at this point in the history
  • Loading branch information
amsanghi committed Jun 27, 2024
1 parent 7f5f406 commit aaaa297
Show file tree
Hide file tree
Showing 6 changed files with 541 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,6 @@
[submodule "arbitrator/langs/bf"]
path = arbitrator/langs/bf
url = https://github.com/OffchainLabs/stylus-sdk-bf.git
[submodule "safe-smart-account"]
path = safe-smart-account
url = https://github.com/safe-global/safe-smart-account.git
2 changes: 1 addition & 1 deletion contracts
1 change: 1 addition & 0 deletions safe-smart-account
Submodule safe-smart-account added at 46f7c6
12 changes: 12 additions & 0 deletions solgen/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,18 @@ func main() {
log.Fatal(err)
}

filePathsSafeSmartAccount, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*", "*.sol", "*.json"))
if err != nil {
log.Fatal(err)
}
filePathsSafeSmartAccountOuter, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*.sol", "*.json"))
if err != nil {
log.Fatal(err)
}

filePaths = append(filePaths, filePathsSafeSmartAccount...)
filePaths = append(filePaths, filePathsSafeSmartAccountOuter...)

modules := make(map[string]*moduleInfo)

for _, path := range filePaths {
Expand Down
225 changes: 225 additions & 0 deletions staker/staker.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import (
"fmt"
"math/big"
"runtime/debug"
"sort"
"strings"
"time"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
Expand All @@ -23,6 +25,8 @@ import (
"github.com/offchainlabs/nitro/arbnode/dataposter"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/cmd/genericconf"
"github.com/offchainlabs/nitro/solgen/go/contractsgen"
"github.com/offchainlabs/nitro/solgen/go/rollupgen"
"github.com/offchainlabs/nitro/staker/txbuilder"
"github.com/offchainlabs/nitro/util"
"github.com/offchainlabs/nitro/util/arbmath"
Expand Down Expand Up @@ -91,6 +95,7 @@ type L1ValidatorConfig struct {
Dangerous DangerousConfig `koanf:"dangerous"`
ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"`
EnableFastConfirmation bool `koanf:"enable-fast-confirmation"`
FastConfirmSafeAddress string `koanf:"fast-confirm-safe-address"`

strategy StakerStrategy
gasRefunder common.Address
Expand Down Expand Up @@ -158,6 +163,7 @@ var DefaultL1ValidatorConfig = L1ValidatorConfig{
Dangerous: DefaultDangerousConfig,
ParentChainWallet: DefaultValidatorL1WalletConfig,
EnableFastConfirmation: false,
FastConfirmSafeAddress: "",
}

var TestL1ValidatorConfig = L1ValidatorConfig{
Expand All @@ -179,6 +185,7 @@ var TestL1ValidatorConfig = L1ValidatorConfig{
Dangerous: DefaultDangerousConfig,
ParentChainWallet: DefaultValidatorL1WalletConfig,
EnableFastConfirmation: false,
FastConfirmSafeAddress: "",
}

var DefaultValidatorL1WalletConfig = genericconf.WalletConfig{
Expand Down Expand Up @@ -208,6 +215,7 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) {
DangerousConfigAddOptions(prefix+".dangerous", f)
genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname)
f.Bool(prefix+".enable-fast-confirmation", DefaultL1ValidatorConfig.EnableFastConfirmation, "enable fast confirmation")
f.String(prefix+".fast-confirm-safe-address", DefaultL1ValidatorConfig.FastConfirmSafeAddress, "safe address for fast confirmation")
}

type DangerousConfig struct {
Expand Down Expand Up @@ -254,6 +262,20 @@ type Staker struct {
inboxReader InboxReaderInterface
statelessBlockValidator *StatelessBlockValidator
fatalErr chan<- error
fastConfirmSafe *FastConfirmSafe
}

type FastConfirmSafe struct {
rollupAddress common.Address
safe *contractsgen.Safe
owners []common.Address
approvedHashesOwners map[common.Hash]map[common.Address]bool
threshold uint64
fastConfirmNextNodeMethod abi.Method
builder *txbuilder.Builder
wallet ValidatorWalletInterface
gasRefunder common.Address
l1Reader *headerreader.HeaderReader
}

type ValidatorWalletInterface interface {
Expand Down Expand Up @@ -303,6 +325,21 @@ func NewStaker(
if config.StartValidationFromStaked && blockValidator != nil {
stakedNotifiers = append(stakedNotifiers, blockValidator)
}
var fastConfirmSafe *FastConfirmSafe
if config.EnableFastConfirmation && config.FastConfirmSafeAddress != "" {
fastConfirmSafe, err = NewFastConfirmSafe(
callOpts,
wallet.RollupAddress(),
common.HexToAddress(config.FastConfirmSafeAddress),
val.builder,
val.wallet,
config.gasRefunder,
l1Reader,
)
if err != nil {
return nil, err
}
}
return &Staker{
L1Validator: val,
l1Reader: l1Reader,
Expand All @@ -315,9 +352,59 @@ func NewStaker(
inboxReader: statelessBlockValidator.inboxReader,
statelessBlockValidator: statelessBlockValidator,
fatalErr: fatalErr,
fastConfirmSafe: fastConfirmSafe,
}, nil
}

func NewFastConfirmSafe(
callOpts bind.CallOpts,
rollupAddress common.Address,
fastConfirmSafeAddress common.Address,
builder *txbuilder.Builder,
wallet ValidatorWalletInterface,
gasRefunder common.Address,
l1Reader *headerreader.HeaderReader,
) (*FastConfirmSafe, error) {
fastConfirmSafe := &FastConfirmSafe{
builder: builder,
rollupAddress: rollupAddress,
wallet: wallet,
gasRefunder: gasRefunder,
l1Reader: l1Reader,
}
safe, err := contractsgen.NewSafe(fastConfirmSafeAddress, builder)
if err != nil {
return nil, err
}
fastConfirmSafe.safe = safe
owners, err := safe.GetOwners(&callOpts)
if err != nil {
return nil, err
}

// This is needed because safe contract needs owners to be sorted.
sort.Slice(owners, func(i, j int) bool {
return owners[i].Cmp(owners[j]) < 0
})
fastConfirmSafe.owners = owners
threshold, err := safe.GetThreshold(&callOpts)
if err != nil {
return nil, err
}
fastConfirmSafe.threshold = threshold.Uint64()
fastConfirmSafe.approvedHashesOwners = make(map[common.Hash]map[common.Address]bool)
rollupUserLogicAbi, err := rollupgen.RollupUserLogicMetaData.GetAbi()
if err != nil {
return nil, err
}
fastConfirmNextNodeMethod, ok := rollupUserLogicAbi.Methods["fastConfirmNextNode"]
if !ok {
return nil, errors.New("RollupUserLogic ABI missing fastConfirmNextNode method")
}
fastConfirmSafe.fastConfirmNextNodeMethod = fastConfirmNextNodeMethod
return fastConfirmSafe, nil
}

func (s *Staker) Initialize(ctx context.Context) error {
err := s.L1Validator.Initialize(ctx)
if err != nil {
Expand Down Expand Up @@ -960,13 +1047,151 @@ func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash,
if !s.config.EnableFastConfirmation {
return nil
}
if s.fastConfirmSafe != nil {
return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot)
}
auth, err := s.builder.Auth(ctx)
if err != nil {
return err
}
_, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot)
return err
}

func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error {
fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot)
if err != nil {
return err
}
callOpts := &bind.CallOpts{Context: ctx}
// Current nonce of the safe.
nonce, err := f.safe.Nonce(callOpts)
if err != nil {
return err
}
// Hash of the safe transaction.
safeTxHash, err := f.safe.GetTransactionHash(
callOpts,
f.rollupAddress,
big.NewInt(0),
fastConfirmCallData,
0,
big.NewInt(0),
big.NewInt(0),
big.NewInt(0),
common.Address{},
common.Address{},
nonce,
)
if err != nil {
return err
}
arbTx, err := f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder)
if err != nil {
return err
}
if arbTx != nil {
_, err = f.l1Reader.WaitForTxApproval(ctx, arbTx)
if err == nil {
log.Info("successfully executed staker transaction", "hash", arbTx.Hash())
} else {
return fmt.Errorf("error waiting for tx receipt: %w", err)
}
}
f.builder.ClearTransactions()
auth, err := f.builder.Auth(ctx)
if err != nil {
return err
}
_, err = f.safe.ApproveHash(auth, safeTxHash)
if err != nil {
return err
}
arbTx, err = f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder)
if err != nil {
return err
}
if arbTx != nil {
_, err = f.l1Reader.WaitForTxApproval(ctx, arbTx)
if err == nil {
log.Info("successfully executed staker transaction", "hash", arbTx.Hash())
} else {
return fmt.Errorf("error waiting for tx receipt: %w", err)
}
}
f.builder.ClearTransactions()
if _, ok := f.approvedHashesOwners[safeTxHash]; !ok {
f.approvedHashesOwners[safeTxHash] = make(map[common.Address]bool)
}
return f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash)
}

func (f *FastConfirmSafe) createFastConfirmCalldata(
blockHash common.Hash, sendRoot common.Hash,
) ([]byte, error) {
calldata, err := f.fastConfirmNextNodeMethod.Inputs.Pack(
blockHash,
sendRoot,
)
if err != nil {
return nil, err
}
fullCalldata := append([]byte{}, f.fastConfirmNextNodeMethod.ID...)
fullCalldata = append(fullCalldata, calldata...)
return fullCalldata, nil
}

func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) error {
var signatures []byte
for _, owner := range f.owners {
if _, ok := f.approvedHashesOwners[safeTxHash][owner]; !ok {
iter, err := f.safe.FilterApproveHash(&bind.FilterOpts{Context: ctx}, [][32]byte{safeTxHash}, []common.Address{owner})
if err != nil {
return err
}
for iter.Next() {
f.approvedHashesOwners[iter.Event.ApprovedHash][iter.Event.Owner] = true
}
}
// If the owner has approved the hash, we add the signature to the transaction.
// We add the signature in the format r, s, v.
// We set v to 1, as it is the only possible value for a approved hash.
// We set r to the owner's address.
// We set s to the empty hash.
// Refer to the Safe contract for more information.
if _, ok := f.approvedHashesOwners[safeTxHash][owner]; ok {
v := uint8(1)
r := common.BytesToHash(owner.Bytes())
s := common.Hash{}
signatures = append(signatures, r.Bytes()...)
signatures = append(signatures, s.Bytes()...)
signatures = append(signatures, v)
}
}
if uint64(len(f.approvedHashesOwners[safeTxHash])) >= f.threshold {
auth, err := f.builder.Auth(ctx)
if err != nil {
return err
}
_, err = f.safe.ExecTransaction(
auth,
f.rollupAddress,
big.NewInt(0),
fastConfirmCallData,
0,
big.NewInt(0),
big.NewInt(0),
big.NewInt(0),
common.Address{},
common.Address{},
signatures,
)
if err != nil {
return err
}
}
return nil
}
func (s *Staker) createConflict(ctx context.Context, info *StakerInfo) error {
if info.CurrentChallenge != nil {
return nil
Expand Down
Loading

0 comments on commit aaaa297

Please sign in to comment.