Skip to content

Commit

Permalink
Merge pull request #243 from EspressoSystems/escape-hatch-simple
Browse files Browse the repository at this point in the history
Escape Hatch
  • Loading branch information
zacshowa authored Oct 11, 2024
2 parents e767c57 + dcc965d commit 824b971
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 24 deletions.
1 change: 1 addition & 0 deletions cmd/replay/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ func main() {

// Handle the various pre-conditions if the message is an Espresso message
validatingAgainstEspresso := chainConfig.ArbitrumChainParams.EnableEspresso && arbos.IsEspressoMsg(message.Message)

if validatingAgainstEspresso {
txs, jst, err := arbos.ParseEspressoMsg(message.Message)
if err != nil {
Expand Down
5 changes: 0 additions & 5 deletions execution/gethexec/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,6 @@ func CreateExecutionNode(
return nil, err
}

// sovereign sequencer should not be configured with espresso finality node
if config.Sequencer.EnableEspressoFinalityNode && config.Sequencer.EnableEspressoSovereign {
return nil, errors.New("espresso finality node cannot be configured with espresso sovereign sequencer")
}

if config.Sequencer.EnableEspressoFinalityNode {
espressoFinalityNode := NewEspressoFinalityNode(execEngine, seqConfigFetcher)
txPublisher = espressoFinalityNode
Expand Down
71 changes: 56 additions & 15 deletions execution/gethexec/sequencer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
lightclient "github.com/EspressoSystems/espresso-sequencer-go/light-client"
"math"
"math/big"
"runtime/debug"
Expand Down Expand Up @@ -80,10 +81,12 @@ type SequencerConfig struct {
expectedSurplusHardThreshold int

// Espresso specific flags
EnableEspressoSovereign bool `koanf:"enable-espresso-sovereign"`
LightClientAddress string `koanf:"light-client-address"`
SwitchDelayThreshold uint64 `koanf:"switch-delay-threshold"`
EspressoFinalityNodeConfig EspressoFinalityNodeConfig `koanf:"espresso-finality-node-config"`
// Espresso Finality Node creates blocks with finalized hotshot transactions
EnableEspressoFinalityNode bool `koanf:"enable-espresso-finality-node"`
EnableEspressoSovereign bool `koanf:"enable-espresso-sovereign"`
}

func (c *SequencerConfig) Validate() error {
Expand Down Expand Up @@ -341,6 +344,8 @@ type Sequencer struct {
expectedSurplusMutex sync.RWMutex
expectedSurplus int64
expectedSurplusUpdated bool

lightClientReader *lightclient.LightClientReader
}

func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderReader, configFetcher SequencerConfigFetcher) (*Sequencer, error) {
Expand All @@ -356,17 +361,39 @@ func NewSequencer(execEngine *ExecutionEngine, l1Reader *headerreader.HeaderRead
}
senderWhitelist[common.HexToAddress(address)] = struct{}{}
}

// For the sovereign sequencer to have an escape hatch, we need to be able to read the state of the light client.
// To accomplish this, we introduce a requirement on the l1Reader/ParentChainReader to not be null. This is a soft
// requirement as the sequencer will still run if we don't have this reader, but it will not create espresso messages.
var (
lightClientReader *lightclient.LightClientReader
err error
)

if l1Reader == nil && config.EnableEspressoSovereign {
return nil, fmt.Errorf("Cannot enable espresso sequencing mode in the sovereign sequencer with no l1 reader")
}

if l1Reader != nil {
lightClientReader, err = lightclient.NewLightClientReader(common.HexToAddress(config.LightClientAddress), l1Reader.Client())
if err != nil {
log.Error("Could not construct light client reader for sequencer. Failing.", "err", err)
return nil, err
}
}

s := &Sequencer{
execEngine: execEngine,
txQueue: make(chan txQueueItem, config.QueueSize),
l1Reader: l1Reader,
config: configFetcher,
senderWhitelist: senderWhitelist,
nonceCache: newNonceCache(config.NonceCacheSize),
l1BlockNumber: 0,
l1Timestamp: 0,
pauseChan: nil,
onForwarderSet: make(chan struct{}, 1),
execEngine: execEngine,
txQueue: make(chan txQueueItem, config.QueueSize),
l1Reader: l1Reader,
config: configFetcher,
senderWhitelist: senderWhitelist,
nonceCache: newNonceCache(config.NonceCacheSize),
l1BlockNumber: 0,
l1Timestamp: 0,
pauseChan: nil,
onForwarderSet: make(chan struct{}, 1),
lightClientReader: lightClientReader,
}
s.nonceFailures = &nonceFailureCache{
containers.NewLruCacheWithOnEvict(config.NonceCacheSize, s.onNonceFailureEvict),
Expand Down Expand Up @@ -935,13 +962,27 @@ func (s *Sequencer) createBlock(ctx context.Context) (returnValue bool) {

start := time.Now()
var (
block *types.Block
err error
block *types.Block
err error
shouldSequenceWithEspresso bool
)

// Initialize shouldSequenceWithEspresso to false and if we have a light client reader then give it a value based on hotshot liveness
// This is a side effect of the sequencer having the capability to run without an L1 reader. For the Espresso integration this is a necessary component of the sequencer.
// However, many tests use the case of having a nil l1 reader
if s.lightClientReader != nil {
shouldSequenceWithEspresso, err = s.lightClientReader.IsHotShotLiveAtHeight(l1Block, s.config().SwitchDelayThreshold)
}

if err != nil {
log.Warn("An error occurred while attempting to determine if hotshot is live at l1 block, sequencing transactions without espresso", "l1Block", l1Block, "err", err)
shouldSequenceWithEspresso = false
}

if config.EnableProfiling {
block, err = s.execEngine.SequenceTransactionsWithProfiling(header, txes, hooks, config.EnableEspressoSovereign)
block, err = s.execEngine.SequenceTransactionsWithProfiling(header, txes, hooks, shouldSequenceWithEspresso)
} else {
block, err = s.execEngine.SequenceTransactions(header, txes, hooks, config.EnableEspressoSovereign)
block, err = s.execEngine.SequenceTransactions(header, txes, hooks, shouldSequenceWithEspresso)
}
elapsed := time.Since(start)
blockCreationTimer.Update(elapsed)
Expand Down
85 changes: 85 additions & 0 deletions system_tests/espresso_e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package arbtest
import (
"context"
"encoding/json"
lightclient "github.com/EspressoSystems/espresso-sequencer-go/light-client"
lightclientmock "github.com/EspressoSystems/espresso-sequencer-go/light-client-mock"
"github.com/ethereum/go-ethereum/common"
"math/big"
"os"
"os/exec"
"testing"
"time"
Expand All @@ -22,6 +26,7 @@ var workingDir = "./espresso-e2e"
var lightClientAddress = "0xb075b82c7a23e0994df4793422a1f03dbcf9136f"

var hotShotUrl = "http://127.0.0.1:41000"
var delayThreshold = 10

var (
jitValidationPort = 54320
Expand Down Expand Up @@ -141,6 +146,17 @@ func waitForEspressoNode(t *testing.T, ctx context.Context) error {
})
}

func waitForHotShotLiveness(t *testing.T, ctx context.Context, lightClientReader *lightclient.LightClientReader) error {
return waitForWith(t, ctx, 400*time.Second, 1*time.Second, func() bool {
log.Info("Waiting for HotShot Liveness")
live, err := lightClientReader.IsHotShotLive(10)
if err != nil {
return false
}
return live
})
}

func waitForL1Node(t *testing.T, ctx context.Context) error {
return waitFor(t, ctx, func() bool {
if e := exec.Command(
Expand Down Expand Up @@ -207,6 +223,15 @@ func TestEspressoE2E(t *testing.T) {
})
Require(t, err)

// make light client reader

lightClientReader, err := lightclient.NewLightClientReader(common.HexToAddress(lightClientAddress), builder.L1.Client)
Require(t, err)
// wait for hotshot liveness

err = waitForHotShotLiveness(t, ctx, lightClientReader)
Require(t, err)

// Check if the tx is executed correctly
err = checkTransferTxOnL2(t, ctx, l2Node, "User10", l2Info)
Require(t, err)
Expand Down Expand Up @@ -246,6 +271,66 @@ func TestEspressoE2E(t *testing.T) {
return balance2.Cmp(transferAmount) >= 0
})
Require(t, err)

// Pause l1 height and verify that the escape hatch is working
checkStaker := os.Getenv("E2E_SKIP_ESCAPE_HATCH_TEST")
if checkStaker == "" {
log.Info("Checking the escape hatch")
// Start to check the escape hatch
address := common.HexToAddress(lightClientAddress)

txOpts := builder.L1Info.GetDefaultTransactOpts("Faucet", ctx)

// Freeze the l1 height
err := lightclientmock.FreezeL1Height(t, builder.L1.Client, address, &txOpts)
log.Info("waiting for light client to report hotshot is down")
Require(t, err)
err = waitForWith(t, ctx, 10*time.Minute, 1*time.Second, func() bool {
isLive, err := lightclientmock.IsHotShotLive(t, builder.L1.Client, address, uint64(delayThreshold))
if err != nil {
return false
}
return !isLive
})
Require(t, err)
log.Info("light client has reported that hotshot is down")
// Wait for the switch to be totally finished
currMsg, err := builder.L2.ConsensusNode.TxStreamer.GetMessageCount()
Require(t, err)
log.Info("waiting for message count", "currMsg", currMsg)
var validatedMsg arbutil.MessageIndex
err = waitForWith(t, ctx, 6*time.Minute, 60*time.Second, func() bool {
validatedCnt := builder.L2.ConsensusNode.BlockValidator.Validated(t)
log.Info("Validation status", "validatedCnt", validatedCnt, "msgCnt", msgCnt)
if validatedCnt >= currMsg {
validatedMsg = validatedCnt
return true
}
return false
})
Require(t, err)
err = checkTransferTxOnL2(t, ctx, l2Node, "User12", l2Info)
Require(t, err)
err = checkTransferTxOnL2(t, ctx, l2Node, "User13", l2Info)
Require(t, err)

err = waitForWith(t, ctx, 3*time.Minute, 20*time.Second, func() bool {
validated := builder.L2.ConsensusNode.BlockValidator.Validated(t)
return validated >= validatedMsg
})
Require(t, err)

// Unfreeze the l1 height
err = lightclientmock.UnfreezeL1Height(t, builder.L1.Client, address, &txOpts)
Require(t, err)

// Check if the validated count is increasing
err = waitForWith(t, ctx, 3*time.Minute, 20*time.Second, func() bool {
validated := builder.L2.ConsensusNode.BlockValidator.Validated(t)
return validated >= validatedMsg+10
})
Require(t, err)
}
}

func checkTransferTxOnL2(
Expand Down
2 changes: 0 additions & 2 deletions system_tests/espresso_finality_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@ func createEspressoFinalityNode(t *testing.T, builder *NodeBuilder) (*TestClient
execConfig.Sequencer.EspressoFinalityNodeConfig.StartBlock = 1
execConfig.Sequencer.EspressoFinalityNodeConfig.HotShotUrl = hotShotUrl

// disable sovereign sequencer
execConfig.Sequencer.EnableEspressoSovereign = false
builder.nodeConfig.TransactionStreamer.SovereignSequencerEnabled = false

return builder.Build2ndNode(t, &SecondNodeParams{
Expand Down
15 changes: 13 additions & 2 deletions system_tests/espresso_sovereign_sequencer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package arbtest
import (
"context"
"fmt"
lightclient "github.com/EspressoSystems/espresso-sequencer-go/light-client"
"github.com/ethereum/go-ethereum/common"
"math/big"
"testing"
"time"
Expand Down Expand Up @@ -39,10 +41,11 @@ func createL1AndL2Node(ctx context.Context, t *testing.T) (*NodeBuilder, func())

// sequencer config
builder.nodeConfig.Sequencer = true
builder.nodeConfig.ParentChainReader.Enable = true // This flag is necessary to enable sequencing transactions with espresso behavior
builder.nodeConfig.Dangerous.NoSequencerCoordinator = true
builder.execConfig.Sequencer.Enable = true
// using the sovereign sequencer
builder.execConfig.Sequencer.EnableEspressoSovereign = true
builder.execConfig.Sequencer.Enable = true
builder.execConfig.Sequencer.LightClientAddress = lightClientAddress

// transaction stream config
builder.nodeConfig.TransactionStreamer.SovereignSequencerEnabled = true
Expand Down Expand Up @@ -79,6 +82,14 @@ func TestSovereignSequencer(t *testing.T) {
err = waitForEspressoNode(t, ctx)
Require(t, err)

// create light client reader
lightClientReader, err := lightclient.NewLightClientReader(common.HexToAddress(lightClientAddress), builder.L1.Client)

Require(t, err)

// wait for hotshot liveness
err = waitForHotShotLiveness(t, ctx, lightClientReader)
Require(t, err)
err = checkTransferTxOnL2(t, ctx, builder.L2, "User14", builder.L2Info)
Require(t, err)

Expand Down
9 changes: 9 additions & 0 deletions system_tests/espresso_transaction_payload_signature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"context"
"crypto/ecdsa"
"fmt"
lightclient "github.com/EspressoSystems/espresso-sequencer-go/light-client"
"github.com/ethereum/go-ethereum/common"
"testing"

"github.com/ethereum/go-ethereum/crypto"
Expand Down Expand Up @@ -35,6 +37,13 @@ func TestEspressoTransactionSignatureForSovereignSequencer(t *testing.T) {
err = waitForEspressoNode(t, ctx)
Require(t, err)

lightClientReader, err := lightclient.NewLightClientReader(common.HexToAddress(lightClientAddress), builder.L1.Client)
Require(t, err)
// wait for hotshot liveness

err = waitForHotShotLiveness(t, ctx, lightClientReader)
Require(t, err)

err = checkTransferTxOnL2(t, ctx, l2Node, "User14", l2Info)
Require(t, err)

Expand Down

0 comments on commit 824b971

Please sign in to comment.