Skip to content

Commit

Permalink
Merge branch 'master' into change-warning-msg
Browse files Browse the repository at this point in the history
  • Loading branch information
ganeshvanahalli authored Dec 6, 2023
2 parents 677f708 + 6da8981 commit 527cf2d
Show file tree
Hide file tree
Showing 4 changed files with 257 additions and 13 deletions.
22 changes: 12 additions & 10 deletions arbnode/batch_poster.go
Original file line number Diff line number Diff line change
Expand Up @@ -270,16 +270,6 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e
bridgeAddr: opts.DeployInfo.Bridge,
daWriter: opts.DAWriter,
redisLock: redisLock,
accessList: func(SequencerInboxAccs, AfterDelayedMessagesRead int) types.AccessList {
return AccessList(&AccessListOpts{
SequencerInboxAddr: opts.DeployInfo.SequencerInbox,
DataPosterAddr: opts.TransactOpts.From,
BridgeAddr: opts.DeployInfo.Bridge,
GasRefunderAddr: opts.Config().gasRefunder,
SequencerInboxAccs: SequencerInboxAccs,
AfterDelayedMessagesRead: AfterDelayedMessagesRead,
})
},
}
dataPosterConfigFetcher := func() *dataposter.DataPosterConfig {
return &(opts.Config().DataPoster)
Expand All @@ -298,6 +288,18 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e
if err != nil {
return nil, err
}
// Dataposter sender may be external signer address, so we should initialize
// access list after initializing dataposter.
b.accessList = func(SequencerInboxAccs, AfterDelayedMessagesRead int) types.AccessList {
return AccessList(&AccessListOpts{
SequencerInboxAddr: opts.DeployInfo.SequencerInbox,
DataPosterAddr: b.dataPoster.Sender(),
BridgeAddr: opts.DeployInfo.Bridge,
GasRefunderAddr: opts.Config().gasRefunder,
SequencerInboxAccs: SequencerInboxAccs,
AfterDelayedMessagesRead: AfterDelayedMessagesRead,
})
}
return b, nil
}

Expand Down
5 changes: 4 additions & 1 deletion cmd/nitro/nitro.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,10 @@ func mainImpl() int {
var dataSigner signature.DataSignerFunc
var l1TransactionOptsValidator *bind.TransactOpts
var l1TransactionOptsBatchPoster *bind.TransactOpts
sequencerNeedsKey := (nodeConfig.Node.Sequencer && !nodeConfig.Node.Feed.Output.DisableSigning) || nodeConfig.Node.BatchPoster.Enable
// If sequencer and signing is enabled or batchposter is enabled without
// external signing sequencer will need a key.
sequencerNeedsKey := (nodeConfig.Node.Sequencer && !nodeConfig.Node.Feed.Output.DisableSigning) ||
(nodeConfig.Node.BatchPoster.Enable && nodeConfig.Node.BatchPoster.DataPoster.ExternalSigner.URL == "")
validatorNeedsKey := nodeConfig.Node.Staker.OnlyCreateWalletContract || nodeConfig.Node.Staker.Enable && !strings.EqualFold(nodeConfig.Node.Staker.Strategy, "watchtower")

l1Wallet.ResolveDirectoryNames(nodeConfig.Persistent.Chain)
Expand Down
55 changes: 53 additions & 2 deletions system_tests/batch_poster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,20 @@ import (
"crypto/rand"
"fmt"
"math/big"
"net/http"
"strings"
"testing"
"time"

"github.com/andybalholm/brotli"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"

"github.com/offchainlabs/nitro/arbnode"
"github.com/offchainlabs/nitro/solgen/go/bridgegen"
"github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen"
"github.com/offchainlabs/nitro/util/redisutil"
)

Expand All @@ -27,10 +33,50 @@ func TestRedisBatchPosterParallel(t *testing.T) {
testBatchPosterParallel(t, true)
}

func addNewBatchPoster(ctx context.Context, t *testing.T, builder *NodeBuilder, address common.Address) {
t.Helper()
upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(builder.L2.ConsensusNode.DeployInfo.UpgradeExecutor, builder.L1.Client)
if err != nil {
t.Fatal("Failed to get new upgrade executor", err)
}
sequencerInboxABI, err := abi.JSON(strings.NewReader(bridgegen.SequencerInboxABI))
if err != nil {
t.Fatal("Failed to parse sequencer inbox abi", err)
}
setIsBatchPoster, err := sequencerInboxABI.Pack("setIsBatchPoster", address, true)
if err != nil {
t.Fatal("Failed to pack setIsBatchPoster", err)
}
ownerOpts := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx)
tx, err := upgradeExecutor.ExecuteCall(
&ownerOpts,
builder.L1Info.GetAddress("SequencerInbox"),
setIsBatchPoster)
if err != nil {
t.Fatalf("Error creating transaction to set batch poster: %v", err)
}
if _, err := builder.L1.EnsureTxSucceeded(tx); err != nil {
t.Fatalf("Error setting batch poster: %v", err)
}
}

func testBatchPosterParallel(t *testing.T, useRedis bool) {
t.Parallel()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
httpSrv, srv := newServer(ctx, t)
t.Cleanup(func() {
if err := httpSrv.Shutdown(ctx); err != nil {
t.Fatalf("Error shutting down http server: %v", err)
}
})
go func() {
log.Debug("Server is listening on port 1234...")
if err := httpSrv.ListenAndServeTLS(signerServerCert, signerServerKey); err != nil && err != http.ErrServerClosed {
log.Debug("ListenAndServeTLS() failed", "error", err)
return
}
}()

var redisUrl string
if useRedis {
Expand All @@ -48,14 +94,19 @@ func testBatchPosterParallel(t *testing.T, useRedis bool) {
builder := NewNodeBuilder(ctx).DefaultConfig(t, true)
builder.nodeConfig.BatchPoster.Enable = false
builder.nodeConfig.BatchPoster.RedisUrl = redisUrl
builder.nodeConfig.BatchPoster.DataPoster.ExternalSigner = *externalSignerTestCfg(srv.address)

cleanup := builder.Build(t)
defer cleanup()

testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{})
defer cleanupB()

builder.L2Info.GenerateAccount("User2")

addNewBatchPoster(ctx, t, builder, srv.address)

builder.L1.SendWaitTestTransactions(t, []*types.Transaction{
builder.L1Info.PrepareTxTo("Faucet", &srv.address, 30000, big.NewInt(1e18), nil)})

var txs []*types.Transaction

for i := 0; i < 100; i++ {
Expand Down
188 changes: 188 additions & 0 deletions system_tests/external_signer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package arbtest

import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"math/big"
"net/http"
"os"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/offchainlabs/nitro/arbnode/dataposter"
)

var (
signerPort = 1234
signerURL = fmt.Sprintf("https://localhost:%v", signerPort)
signerMethod = "test_signTransaction"
signerServerCert = "../arbnode/dataposter/testdata/localhost.crt"
signerServerKey = "../arbnode/dataposter/testdata/localhost.key"
signerClientCert = "../arbnode/dataposter/testdata/client.crt"
signerClientPrivateKey = "../arbnode/dataposter/testdata/client.key"
)

func externalSignerTestCfg(addr common.Address) *dataposter.ExternalSignerCfg {
return &dataposter.ExternalSignerCfg{
Address: common.Bytes2Hex(addr.Bytes()),
URL: signerURL,
Method: signerMethod,
RootCA: signerServerCert,
ClientCert: signerClientCert,
ClientPrivateKey: signerClientPrivateKey,
}
}

type server struct {
handlers map[string]func(*json.RawMessage) (string, error)
signerFn bind.SignerFn
address common.Address
}

type request struct {
ID *json.RawMessage `json:"id"`
Method string `json:"method"`
Params *json.RawMessage `json:"params"`
}

type response struct {
ID *json.RawMessage `json:"id"`
Result string `json:"result,omitempty"`
}

// newServer returns http server and server struct that implements RPC methods.
// It sets up an account in temporary directory and cleans up after test is
// done.
func newServer(ctx context.Context, t *testing.T) (*http.Server, *server) {
t.Helper()
signer, address, err := setupAccount("/tmp/keystore")
if err != nil {
t.Fatalf("Error setting up account: %v", err)
}
t.Cleanup(func() { os.RemoveAll("/tmp/keystore") })

s := &server{signerFn: signer, address: address}
s.handlers = map[string]func(*json.RawMessage) (string, error){
signerMethod: s.signTransaction,
}
m := http.NewServeMux()

clientCert, err := os.ReadFile(signerClientCert)
if err != nil {
t.Fatalf("Error reading client certificate: %v", err)
}
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(clientCert)

httpSrv := &http.Server{
Addr: fmt.Sprintf(":%v", signerPort),
Handler: m,
ReadTimeout: 5 * time.Second,
TLSConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientCAs: pool,
},
}
m.HandleFunc("/", s.mux)
return httpSrv, s
}

// setupAccount creates a new account in a given directory, unlocks it, creates
// signer with that account and returns it along with account address.
func setupAccount(dir string) (bind.SignerFn, common.Address, error) {
ks := keystore.NewKeyStore(
dir,
keystore.StandardScryptN,
keystore.StandardScryptP,
)
a, err := ks.NewAccount("password")
if err != nil {
return nil, common.Address{}, fmt.Errorf("creating account account: %w", err)
}
if err := ks.Unlock(a, "password"); err != nil {
return nil, common.Address{}, fmt.Errorf("unlocking account: %w", err)
}
txOpts, err := bind.NewKeyStoreTransactorWithChainID(ks, a, big.NewInt(1337))
if err != nil {
return nil, common.Address{}, fmt.Errorf("creating transactor: %w", err)
}
return txOpts.Signer, a.Address, nil
}

// UnmarshallFirst unmarshalls slice of params and returns the first one.
// Parameters in Go ethereum RPC calls are marashalled as slices. E.g.
// eth_sendRawTransaction or eth_signTransaction, marshall transaction as a
// slice of transactions in a message:
// https://github.com/ethereum/go-ethereum/blob/0004c6b229b787281760b14fb9460ffd9c2496f1/rpc/client.go#L548
func unmarshallFirst(params []byte) (*types.Transaction, error) {
var arr []apitypes.SendTxArgs
if err := json.Unmarshal(params, &arr); err != nil {
return nil, fmt.Errorf("unmarshaling first param: %w", err)
}
if len(arr) != 1 {
return nil, fmt.Errorf("argument should be a single transaction, but got: %d", len(arr))
}
return arr[0].ToTransaction(), nil
}

func (s *server) signTransaction(params *json.RawMessage) (string, error) {
tx, err := unmarshallFirst(*params)
if err != nil {
return "", err
}
signedTx, err := s.signerFn(s.address, tx)
if err != nil {
return "", fmt.Errorf("signing transaction: %w", err)
}
data, err := rlp.EncodeToBytes(signedTx)
if err != nil {
return "", fmt.Errorf("rlp encoding transaction: %w", err)
}
return hexutil.Encode(data), nil
}

func (s *server) mux(w http.ResponseWriter, r *http.Request) {
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
var req request
if err := json.Unmarshal(body, &req); err != nil {
http.Error(w, "can't unmarshal JSON request", http.StatusBadRequest)
return
}
method, ok := s.handlers[req.Method]
if !ok {
http.Error(w, "method not found", http.StatusNotFound)
return
}
result, err := method(req.Params)
if err != nil {
fmt.Printf("error calling method: %v\n", err)
http.Error(w, "error calling method", http.StatusInternalServerError)
return
}
resp := response{ID: req.ID, Result: result}
respBytes, err := json.Marshal(resp)
if err != nil {
http.Error(w, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(respBytes); err != nil {
fmt.Printf("error writing response: %v\n", err)
}
}

0 comments on commit 527cf2d

Please sign in to comment.