Skip to content

Commit

Permalink
Add espresso test (#18)
Browse files Browse the repository at this point in the history
Co-authored-by: ImJeremyHe <[email protected]>
  • Loading branch information
sveitser and ImJeremyHe authored Dec 6, 2023
1 parent 1868c43 commit 618eb65
Show file tree
Hide file tree
Showing 3 changed files with 280 additions and 0 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ require (
github.com/ipfs/go-libipfs v0.6.2
github.com/ipfs/interface-go-ipfs-core v0.11.0
github.com/ipfs/kubo v0.19.1
github.com/jarcoal/httpmock v1.3.1
github.com/knadh/koanf v1.4.0
github.com/libp2p/go-libp2p v0.27.8
github.com/multiformats/go-multiaddr v0.9.0
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,8 @@ github.com/jackpal/gateway v1.0.5/go.mod h1:lTpwd4ACLXmpyiCTRtfiNyVnUmqT9RivzCDQ
github.com/jackpal/go-nat-pmp v1.0.1/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus=
github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc=
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jbenet/go-cienv v0.0.0-20150120210510-1bb1476777ec/go.mod h1:rGaEvXB4uRSZMmzKNLoXvTu1sfx+1kv/DojUlPrSZGs=
github.com/jbenet/go-cienv v0.1.0 h1:Vc/s0QbQtoxX8MwwSLWWh+xNNZvM3Lw7NsTcHrvvhMc=
github.com/jbenet/go-cienv v0.1.0/go.mod h1:TqNnHUmJgXau0nCzC7kXWeotg3J9W34CUv5Djy1+FlA=
Expand Down Expand Up @@ -1191,6 +1193,7 @@ github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpe
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
Expand Down
276 changes: 276 additions & 0 deletions system_tests/espresso_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
package arbtest

import (
"context"
"encoding/json"
"fmt"
"math/big"
"net/http"
"testing"
"time"

"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
"github.com/jarcoal/httpmock"
"github.com/offchainlabs/nitro/arbos/espresso"
"github.com/offchainlabs/nitro/arbutil"
"github.com/offchainlabs/nitro/validator/server_api"
"github.com/offchainlabs/nitro/validator/valnode"
)

var (
validationPort = 54320
broadcastPort = 9642
)

func espresso_block_txs_generators(t *testing.T, l2Info *BlockchainTestInfo) map[int][][]byte {
return map[int][][]byte{
5: onlyMalformedTxs(t),
15: userTxs(t, l2Info),
25: func(t *testing.T) [][]byte {
// Contains malformed txes, valid transactions and invalid transactions
r := [][]byte{}
r = append(r, onlyMalformedTxs(t)...)
r = append(r, userTxs(t, l2Info)...)
return r
}(t),
}
}

func onlyMalformedTxs(t *testing.T) [][]byte {
return [][]byte{
{1, 2, 3},
{1, 2, 3},
{4, 5, 6},
{1, 2, 4},
}
}

// Two valid transactions and two invalid transactions with invalid nonces
func userTxs(t *testing.T, l2Info *BlockchainTestInfo) [][]byte {
tx1 := l2Info.PrepareTx("Faucet", "Owner", 3e7, big.NewInt(1e16), nil)
tx1Bin, err := json.Marshal(tx1)
if err != nil {
panic(err)
}
tx2 := l2Info.PrepareTx("Owner", "Faucet", 3e7, big.NewInt(1e16), nil)
tx2Bin, err := json.Marshal(tx2)
if err != nil {
panic(err)
}
// 2 valid transactions here
return [][]byte{
tx1Bin,
tx1Bin,
tx2Bin,
tx2Bin,
}
}

func createMockHotShot(ctx context.Context, t *testing.T, l2Info *BlockchainTestInfo) (func(), int) {
httpmock.Activate()

httpmock.RegisterResponder(
"GET",
`=~http://127.0.0.1:50000/availability/header/(\d+)`,
func(req *http.Request) (*http.Response, error) {
log.Info("GET", "url", req.URL)
block := uint64(httpmock.MustGetSubmatchAsUint(req, 1))
header := espresso.Header{
// Since we don't realize the validation of espresso yet,
// mock a simple nmt root here
// See: arbos/espresso/nmt.go
TransactionsRoot: espresso.NmtRoot{Root: []byte{}},
Metadata: espresso.Metadata{
L1Head: block,
Timestamp: uint64(time.Now().Unix()),
},
}
return httpmock.NewJsonResponse(200, header)
})

generators := espresso_block_txs_generators(t, l2Info)

httpmock.RegisterResponder(
"GET",
`=~http://127.0.0.1:50000/availability/block/(\d+)/namespace/100`,
func(req *http.Request) (*http.Response, error) {
txes := []espresso.Transaction{}
block := int(httpmock.MustGetSubmatchAsInt(req, 1))
data, ok := generators[block]
// Since we don't realize the validation of espresso yet,
// we can mock the proof easily.
// See: arbos/espresso/nmt.go
dummyProof, _ := json.Marshal(map[int]int{0: 0})
if block > 100 {
// make the debug message cleaner
return httpmock.NewJsonResponse(404, 0)
}
log.Info("GET", "url", req.URL)
if !ok {
r := espresso.NamespaceResponse{
Proof: (*json.RawMessage)(&dummyProof),
Transactions: &[]espresso.Transaction{},
}
return httpmock.NewJsonResponse(200, r)
}
for _, rawTx := range data {
tx := espresso.Transaction{
Vm: 100,
Payload: rawTx,
}
txes = append(txes, tx)
}
resp := espresso.NamespaceResponse{
Proof: (*json.RawMessage)(&dummyProof),
Transactions: &txes,
}
return httpmock.NewJsonResponse(200, resp)
})

return httpmock.DeactivateAndReset, len(generators)
}

func createL2Node(ctx context.Context, t *testing.T, hotshot_url string) (*TestClient, info, func()) {
builder := NewNodeBuilder(ctx).DefaultConfig(t, false)
builder.takeOwnership = false
builder.nodeConfig.DelayedSequencer.Enable = true
builder.nodeConfig.Sequencer = true
builder.nodeConfig.Espresso = true
builder.execConfig.Sequencer.Enable = true
builder.execConfig.Sequencer.Espresso = true
builder.execConfig.Sequencer.EspressoNamespace = 100
builder.execConfig.Sequencer.HotShotUrl = hotshot_url

builder.nodeConfig.Feed.Output.Enable = true
builder.nodeConfig.Feed.Output.Port = fmt.Sprintf("%d", broadcastPort)

cleanup := builder.Build(t)
return builder.L2, builder.L2Info, cleanup
}

func createValidatorAndPosterNode(ctx context.Context, t *testing.T) (*TestClient, func()) {
builder := NewNodeBuilder(ctx).DefaultConfig(t, true)
builder.nodeConfig.Feed.Input.URL = []string{fmt.Sprintf("ws://127.0.0.1:%d", broadcastPort)}
builder.nodeConfig.BatchPoster.Enable = true
builder.nodeConfig.BlockValidator.Enable = true
builder.nodeConfig.BlockValidator.ValidationServer.URL = fmt.Sprintf("ws://127.0.0.1:%d", validationPort)
cleanup := builder.Build(t)
return builder.L2, cleanup
}

func createValidationNode(ctx context.Context, t *testing.T) func() {
stackConf := node.DefaultConfig
stackConf.HTTPPort = 0
stackConf.DataDir = ""
stackConf.WSHost = "127.0.0.1"
stackConf.WSPort = validationPort
stackConf.WSModules = []string{server_api.Namespace}
stackConf.P2P.NoDiscovery = true
stackConf.P2P.ListenAddr = ""

valnode.EnsureValidationExposedViaAuthRPC(&stackConf)
config := &valnode.TestValidationConfig

stack, err := node.New(&stackConf)
Require(t, err)

configFetcher := func() *valnode.Config { return config }
valnode, err := valnode.CreateValidationNode(configFetcher, stack, nil)
Require(t, err)

err = stack.Start()
Require(t, err)

err = valnode.Start(ctx)
Require(t, err)

go func() {
<-ctx.Done()
stack.Close()
}()

return func() {
valnode.GetExec().Stop()
stack.Close()
}

}

func waitFor(t *testing.T, ctxinput context.Context, condition func() bool) error {
ctx, cancel := context.WithTimeout(ctxinput, 30*time.Second)
defer cancel()

for {
if condition() {
return nil
}
select {
case <-time.After(time.Second):
case <-ctx.Done():
return ctx.Err()
}
}
}

func TestEspresso(t *testing.T) {

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

l2Node, l2Info, cleanL2Node := createL2Node(ctx, t, "http://127.0.0.1:50000")
defer cleanL2Node()

cleanHotShot, blockCnt := createMockHotShot(ctx, t, l2Info)
defer cleanHotShot()

// An initial message for genesis block and every non-empty espresso block
// should lead to a message
expectedMsgCnt := 1 + blockCnt

err := waitFor(t, ctx, func() bool {
cnt, err := l2Node.ConsensusNode.TxStreamer.GetMessageCount()
if err != nil {
panic(err)
}
expected := arbutil.MessageIndex(expectedMsgCnt)
return cnt >= expected
})
Require(t, err)

cleanValNode := createValidationNode(ctx, t)
defer cleanValNode()

node, cleanup := createValidatorAndPosterNode(ctx, t)
defer cleanup()

// Check the validated message
err = waitFor(t, ctx, func() bool {
cnt := node.ConsensusNode.BlockValidator.Validated(t)
expected := arbutil.MessageIndex(expectedMsgCnt)
return cnt >= expected
})
Require(t, err)

blockNum, err := l2Node.Client.BlockNumber(ctx)
Require(t, err)

if blockNum != uint64(blockCnt) {
Fatal(t, "every non-empty espresso block should lead to one L2 block")
}

block2, err := l2Node.Client.BlockByNumber(ctx, big.NewInt(2))
Require(t, err)

// Every arbitrum block has one internal tx
if len(block2.Body().Transactions) != 3 {
Fatal(t, "block 2 should contain 2 valid transactions")
}

block3, err := l2Node.Client.BlockByNumber(ctx, big.NewInt(3))
Require(t, err)

if len(block3.Body().Transactions) != 3 {
Fatal(t, "block 3 should contain 2 valid transactions")
}
}

0 comments on commit 618eb65

Please sign in to comment.