From a619d43bbd811870a5c08156959d1689343c1f6f Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 13 Oct 2023 16:55:09 +0200 Subject: [PATCH 1/9] Implement external signer for dataposter --- arbnode/batch_poster.go | 9 +- arbnode/dataposter/data_poster.go | 153 +++++++++++++------ arbnode/dataposter/dataposter_test.go | 210 ++++++++++++++++++++++++++ arbnode/maintenance.go | 1 + arbnode/node.go | 1 + arbnode/redislock/redis.go | 2 +- broadcastclient/broadcastclient.go | 2 +- cmd/genericconf/config.go | 2 +- cmd/genericconf/server.go | 6 +- cmd/nitro/config_test.go | 19 +++ cmd/nitro/nitro.go | 7 + cmd/rpcsvr/main.go | 134 ++++++++++++++++ das/das.go | 1 + execution/gethexec/node.go | 1 + staker/block_validator.go | 2 +- staker/staker.go | 2 +- util/headerreader/header_reader.go | 1 + util/signature/sign_verify.go | 6 + util/signature/sign_verify_test.go | 6 +- util/signature/verifier.go | 2 +- 20 files changed, 507 insertions(+), 60 deletions(-) create mode 100644 cmd/rpcsvr/main.go diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 76ff8e6a50..acf655e4cf 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" + "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbnode/redislock" @@ -47,6 +48,7 @@ import ( var ( batchPosterWalletBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/wallet/balanceether", nil) batchPosterGasRefunderBalance = metrics.NewRegisteredGaugeFloat64("arb/batchposter/gasrefunder/balanceether", nil) + batchPosterSimpleRedisLockKey = "node.batch-poster.redis-lock.simple-lock-key" ) type batchPosterPosition struct { @@ -166,7 +168,7 @@ func BatchPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { f.String(prefix+".l1-block-bound", DefaultBatchPosterConfig.L1BlockBound, "only post messages to batches when they're within the max future block/timestamp as of this L1 block tag (\"safe\", \"finalized\", \"latest\", or \"ignore\" to ignore this check)") f.Duration(prefix+".l1-block-bound-bypass", DefaultBatchPosterConfig.L1BlockBoundBypass, "post batches even if not within the layer 1 future bounds if we're within this margin of the max delay") redislock.AddConfigOptions(prefix+".redis-lock", f) - dataposter.DataPosterConfigAddOptions(prefix+".data-poster", f) + dataposter.DataPosterConfigAddOptions(prefix+".data-poster", f, dataposter.DefaultDataPosterConfig) genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultBatchPosterConfig.ParentChainWallet.Pathname) } @@ -187,6 +189,7 @@ var DefaultBatchPosterConfig = BatchPosterConfig{ ParentChainWallet: DefaultBatchPosterL1WalletConfig, L1BlockBound: "", L1BlockBoundBypass: time.Hour, + RedisLock: redislock.DefaultCfg, } var DefaultBatchPosterL1WalletConfig = genericconf.WalletConfig{ @@ -235,7 +238,9 @@ func NewBatchPoster(ctx context.Context, dataPosterDB ethdb.Database, l1Reader * return nil, err } redisLockConfigFetcher := func() *redislock.SimpleCfg { - return &config().RedisLock + simpleRedisLockConfig := config().RedisLock + simpleRedisLockConfig.Key = batchPosterSimpleRedisLockKey + return &simpleRedisLockConfig } redisLock, err := redislock.NewSimple(redisClient, redisLockConfigFetcher, func() bool { return syncMonitor.Synced() }) if err != nil { diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 91bdf39d5f..c8a673ec9c 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -15,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "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/ethdb" "github.com/ethereum/go-ethereum/log" @@ -47,7 +48,7 @@ type DataPoster struct { headerReader *headerreader.HeaderReader client arbutil.L1Interface sender common.Address - signer bind.SignerFn + signer signerFn redisLock AttemptLocker config ConfigFetcher replacementTimes []time.Duration @@ -66,6 +67,11 @@ type DataPoster struct { errorCount map[uint64]int // number of consecutive intermittent errors rbf-ing or sending, per nonce } +// signerFn is a signer function callback when a contract requires a method to +// sign the transaction before submission. +// This can be local or external, hence the context parameter. +type signerFn func(context.Context, common.Address, *types.Transaction) (*types.Transaction, error) + type AttemptLocker interface { AttemptLock(context.Context) bool } @@ -103,13 +109,13 @@ type DataPosterOpts struct { } func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, error) { - initConfig := opts.Config() - replacementTimes, err := parseReplacementTimes(initConfig.ReplacementTimes) + cfg := opts.Config() + replacementTimes, err := parseReplacementTimes(cfg.ReplacementTimes) if err != nil { return nil, err } - if opts.HeaderReader.IsParentChainArbitrum() && !initConfig.UseNoOpStorage { - initConfig.UseNoOpStorage = true + if opts.HeaderReader.IsParentChainArbitrum() && !cfg.UseNoOpStorage { + cfg.UseNoOpStorage = true log.Info("Disabling data poster storage, as parent chain appears to be an Arbitrum chain without a mempool") } encF := func() storage.EncoderDecoderInterface { @@ -120,17 +126,17 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro } var queue QueueStorage switch { - case initConfig.UseNoOpStorage: + case cfg.UseNoOpStorage: queue = &noop.Storage{} case opts.RedisClient != nil: var err error - queue, err = redisstorage.NewStorage(opts.RedisClient, opts.RedisKey, &initConfig.RedisSigner, encF) + queue, err = redisstorage.NewStorage(opts.RedisClient, opts.RedisKey, &cfg.RedisSigner, encF) if err != nil { return nil, err } - case initConfig.UseDBStorage: + case cfg.UseDBStorage: storage := dbstorage.New(opts.Database, func() storage.EncoderDecoderInterface { return &storage.EncoderDecoder{} }) - if initConfig.Dangerous.ClearDBStorage { + if cfg.Dangerous.ClearDBStorage { if err := storage.PruneAll(ctx); err != nil { return nil, err } @@ -139,18 +145,53 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro default: queue = slice.NewStorage(func() storage.EncoderDecoderInterface { return &storage.EncoderDecoder{} }) } - return &DataPoster{ - headerReader: opts.HeaderReader, - client: opts.HeaderReader.Client(), - sender: opts.Auth.From, - signer: opts.Auth.Signer, + dp := &DataPoster{ + headerReader: opts.HeaderReader, + client: opts.HeaderReader.Client(), + sender: opts.Auth.From, + signer: func(_ context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return opts.Auth.Signer(addr, tx) + }, config: opts.Config, replacementTimes: replacementTimes, metadataRetriever: opts.MetadataRetriever, queue: queue, redisLock: opts.RedisLock, errorCount: make(map[uint64]int), - }, nil + } + if cfg.ExternalSigner.Enabled { + signer, sender, err := externalSigner(ctx, cfg.ExternalSigner.Address, cfg.ExternalSigner.URL) + if err != nil { + return nil, err + } + dp.signer, dp.sender = signer, sender + } + return dp, nil +} + +// externalSigner returns signer function and ethereum address of the signer. +// Returns an error if address isn't specified or if it can't connect to the +// signer RPC server. +func externalSigner(ctx context.Context, addr string, rpcURL string) (signerFn, common.Address, error) { + if addr == "" { + return nil, common.Address{}, errors.New("external signer (From) address specified") + } + sender := common.HexToAddress(addr) + client, err := rpc.DialContext(ctx, rpcURL) + if err != nil { + return nil, common.Address{}, fmt.Errorf("error connecting external signer: %w", err) + } + return func(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + data, err := tx.MarshalBinary() + if err != nil { + return nil, err + } + var signedTx *types.Transaction + if err := client.CallContext(ctx, &signedTx, "eth_signTransaction", hexutil.Encode(data)); err != nil { + return nil, fmt.Errorf("error calling with context: %w", err) + } + return signedTx, nil + }, sender, nil } func (p *DataPoster) Sender() common.Address { @@ -371,7 +412,7 @@ func (p *DataPoster) PostTransaction(ctx context.Context, dataCreatedAt time.Tim Data: calldata, AccessList: accessList, } - fullTx, err := p.signer(p.sender, types.NewTx(&inner)) + fullTx, err := p.signer(ctx, p.sender, types.NewTx(&inner)) if err != nil { return nil, fmt.Errorf("signing transaction: %w", err) } @@ -450,7 +491,7 @@ func (p *DataPoster) replaceTx(ctx context.Context, prevTx *storage.QueuedTransa newTx.Sent = false newTx.Data.GasFeeCap = newFeeCap newTx.Data.GasTipCap = newTipCap - newTx.FullTx, err = p.signer(p.sender, types.NewTx(&newTx.Data)) + newTx.FullTx, err = p.signer(ctx, p.sender, types.NewTx(&newTx.Data)) if err != nil { return err } @@ -636,20 +677,31 @@ type DataPosterConfig struct { ReplacementTimes string `koanf:"replacement-times"` // This is forcibly disabled if the parent chain is an Arbitrum chain, // so you should probably use DataPoster's waitForL1Finality method instead of reading this field directly. - WaitForL1Finality bool `koanf:"wait-for-l1-finality" reload:"hot"` - MaxMempoolTransactions uint64 `koanf:"max-mempool-transactions" reload:"hot"` - MaxQueuedTransactions int `koanf:"max-queued-transactions" reload:"hot"` - TargetPriceGwei float64 `koanf:"target-price-gwei" reload:"hot"` - UrgencyGwei float64 `koanf:"urgency-gwei" reload:"hot"` - MinFeeCapGwei float64 `koanf:"min-fee-cap-gwei" reload:"hot"` - MinTipCapGwei float64 `koanf:"min-tip-cap-gwei" reload:"hot"` - MaxTipCapGwei float64 `koanf:"max-tip-cap-gwei" reload:"hot"` - NonceRbfSoftConfs uint64 `koanf:"nonce-rbf-soft-confs" reload:"hot"` - AllocateMempoolBalance bool `koanf:"allocate-mempool-balance" reload:"hot"` - UseDBStorage bool `koanf:"use-db-storage"` - UseNoOpStorage bool `koanf:"use-noop-storage"` - LegacyStorageEncoding bool `koanf:"legacy-storage-encoding" reload:"hot"` - Dangerous DangerousConfig `koanf:"dangerous"` + WaitForL1Finality bool `koanf:"wait-for-l1-finality" reload:"hot"` + MaxMempoolTransactions uint64 `koanf:"max-mempool-transactions" reload:"hot"` + MaxQueuedTransactions int `koanf:"max-queued-transactions" reload:"hot"` + TargetPriceGwei float64 `koanf:"target-price-gwei" reload:"hot"` + UrgencyGwei float64 `koanf:"urgency-gwei" reload:"hot"` + MinFeeCapGwei float64 `koanf:"min-fee-cap-gwei" reload:"hot"` + MinTipCapGwei float64 `koanf:"min-tip-cap-gwei" reload:"hot"` + MaxTipCapGwei float64 `koanf:"max-tip-cap-gwei" reload:"hot"` + NonceRbfSoftConfs uint64 `koanf:"nonce-rbf-soft-confs" reload:"hot"` + AllocateMempoolBalance bool `koanf:"allocate-mempool-balance" reload:"hot"` + UseDBStorage bool `koanf:"use-db-storage"` + UseNoOpStorage bool `koanf:"use-noop-storage"` + LegacyStorageEncoding bool `koanf:"legacy-storage-encoding" reload:"hot"` + Dangerous DangerousConfig `koanf:"dangerous"` + ExternalSigner ExternalSignerCfg `koanf:"external-signer"` +} + +type ExternalSignerCfg struct { + // If enabled, this overrides transaction options and uses external signer + // for signing transactions. + Enabled bool `koanf:"enabled"` + // URL of the external signer rpc server. + URL string `koanf:"url"` + // Hex encoded ethereum address of the external signer. + Address string `koanf:"address"` } type DangerousConfig struct { @@ -662,30 +714,37 @@ type DangerousConfig struct { // that flags can be reloaded dynamically. type ConfigFetcher func() *DataPosterConfig -func DataPosterConfigAddOptions(prefix string, f *pflag.FlagSet) { - f.String(prefix+".replacement-times", DefaultDataPosterConfig.ReplacementTimes, "comma-separated list of durations since first posting to attempt a replace-by-fee") - f.Bool(prefix+".wait-for-l1-finality", DefaultDataPosterConfig.WaitForL1Finality, "only treat a transaction as confirmed after L1 finality has been achieved (recommended)") - f.Uint64(prefix+".max-mempool-transactions", DefaultDataPosterConfig.MaxMempoolTransactions, "the maximum number of transactions to have queued in the mempool at once (0 = unlimited)") - f.Int(prefix+".max-queued-transactions", DefaultDataPosterConfig.MaxQueuedTransactions, "the maximum number of unconfirmed transactions to track at once (0 = unlimited)") - f.Float64(prefix+".target-price-gwei", DefaultDataPosterConfig.TargetPriceGwei, "the target price to use for maximum fee cap calculation") - f.Float64(prefix+".urgency-gwei", DefaultDataPosterConfig.UrgencyGwei, "the urgency to use for maximum fee cap calculation") - f.Float64(prefix+".min-fee-cap-gwei", DefaultDataPosterConfig.MinFeeCapGwei, "the minimum fee cap to post transactions at") - f.Float64(prefix+".min-tip-cap-gwei", DefaultDataPosterConfig.MinTipCapGwei, "the minimum tip cap to post transactions at") - f.Float64(prefix+".max-tip-cap-gwei", DefaultDataPosterConfig.MaxTipCapGwei, "the maximum tip cap to post transactions at") - f.Uint64(prefix+".nonce-rbf-soft-confs", DefaultDataPosterConfig.NonceRbfSoftConfs, "the maximum probable reorg depth, used to determine when a transaction will no longer likely need replaced-by-fee") - f.Bool(prefix+".allocate-mempool-balance", DefaultDataPosterConfig.AllocateMempoolBalance, "if true, don't put transactions in the mempool that spend a total greater than the batch poster's balance") - f.Bool(prefix+".use-db-storage", DefaultDataPosterConfig.UseDBStorage, "uses database storage when enabled") - f.Bool(prefix+".use-noop-storage", DefaultDataPosterConfig.UseNoOpStorage, "uses noop storage, it doesn't store anything") - f.Bool(prefix+".legacy-storage-encoding", DefaultDataPosterConfig.LegacyStorageEncoding, "encodes items in a legacy way (as it was before dropping generics)") +func DataPosterConfigAddOptions(prefix string, f *pflag.FlagSet, defaultDataPosterConfig DataPosterConfig) { + f.String(prefix+".replacement-times", defaultDataPosterConfig.ReplacementTimes, "comma-separated list of durations since first posting to attempt a replace-by-fee") + f.Bool(prefix+".wait-for-l1-finality", defaultDataPosterConfig.WaitForL1Finality, "only treat a transaction as confirmed after L1 finality has been achieved (recommended)") + f.Uint64(prefix+".max-mempool-transactions", defaultDataPosterConfig.MaxMempoolTransactions, "the maximum number of transactions to have queued in the mempool at once (0 = unlimited)") + f.Int(prefix+".max-queued-transactions", defaultDataPosterConfig.MaxQueuedTransactions, "the maximum number of unconfirmed transactions to track at once (0 = unlimited)") + f.Float64(prefix+".target-price-gwei", defaultDataPosterConfig.TargetPriceGwei, "the target price to use for maximum fee cap calculation") + f.Float64(prefix+".urgency-gwei", defaultDataPosterConfig.UrgencyGwei, "the urgency to use for maximum fee cap calculation") + f.Float64(prefix+".min-fee-cap-gwei", defaultDataPosterConfig.MinFeeCapGwei, "the minimum fee cap to post transactions at") + f.Float64(prefix+".min-tip-cap-gwei", defaultDataPosterConfig.MinTipCapGwei, "the minimum tip cap to post transactions at") + f.Float64(prefix+".max-tip-cap-gwei", defaultDataPosterConfig.MaxTipCapGwei, "the maximum tip cap to post transactions at") + f.Uint64(prefix+".nonce-rbf-soft-confs", defaultDataPosterConfig.NonceRbfSoftConfs, "the maximum probable reorg depth, used to determine when a transaction will no longer likely need replaced-by-fee") + f.Bool(prefix+".allocate-mempool-balance", defaultDataPosterConfig.AllocateMempoolBalance, "if true, don't put transactions in the mempool that spend a total greater than the batch poster's balance") + f.Bool(prefix+".use-db-storage", defaultDataPosterConfig.UseDBStorage, "uses database storage when enabled") + f.Bool(prefix+".use-noop-storage", defaultDataPosterConfig.UseNoOpStorage, "uses noop storage, it doesn't store anything") + f.Bool(prefix+".legacy-storage-encoding", defaultDataPosterConfig.LegacyStorageEncoding, "encodes items in a legacy way (as it was before dropping generics)") signature.SimpleHmacConfigAddOptions(prefix+".redis-signer", f) addDangerousOptions(prefix+".dangerous", f) + addExternalSignerOptions(prefix+".external-signer", f) } func addDangerousOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".clear-dbstorage", DefaultDataPosterConfig.Dangerous.ClearDBStorage, "clear database storage") } +func addExternalSignerOptions(prefix string, f *pflag.FlagSet) { + f.Bool(prefix+".enabled", DefaultDataPosterConfig.ExternalSigner.Enabled, "enable external signer") + f.String(prefix+".url", DefaultDataPosterConfig.ExternalSigner.URL, "external signer url") + f.String(prefix+".address", DefaultDataPosterConfig.ExternalSigner.Address, "external signer address") +} + var DefaultDataPosterConfig = DataPosterConfig{ ReplacementTimes: "5m,10m,20m,30m,1h,2h,4h,6h,8h,12h,16h,18h,20h,22h", WaitForL1Finality: true, @@ -700,6 +759,7 @@ var DefaultDataPosterConfig = DataPosterConfig{ UseNoOpStorage: false, LegacyStorageEncoding: true, Dangerous: DangerousConfig{ClearDBStorage: false}, + ExternalSigner: ExternalSignerCfg{Enabled: false}, } var DefaultDataPosterConfigForValidator = func() DataPosterConfig { @@ -721,6 +781,7 @@ var TestDataPosterConfig = DataPosterConfig{ AllocateMempoolBalance: true, UseDBStorage: false, UseNoOpStorage: false, + ExternalSigner: ExternalSignerCfg{Enabled: false}, } var TestDataPosterConfigForValidator = func() DataPosterConfig { diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index b8a9c3e499..c4c131c79a 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -1,9 +1,21 @@ package dataposter import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "math/big" + "net/http" "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/google/go-cmp/cmp" ) @@ -41,3 +53,201 @@ func TestParseReplacementTimes(t *testing.T) { }) } } + +type Args struct { + Name string +} + +func TestRPC(t *testing.T) { + srv, err := newServer() + if err != nil { + fmt.Printf("Erorr creating server: %v", err) + return + } + http.HandleFunc("/", srv.mux) + go func() { + fmt.Println("Server is listening on port 1234...") + t.Errorf("error listening: %v", http.ListenAndServe(":1234", nil)) + }() + + if err != nil { + t.Fatalf("Error creating a server: %v", err) + } + ctx := context.Background() + signer, addr, err := externalSigner(ctx, srv.address.Hex(), "http://127.0.0.1:1234") + if err != nil { + t.Fatalf("Error getting external signer: %v", err) + } + tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, big.NewInt(30000), nil) + signedTx, err := signer(ctx, addr, tx) + if err != nil { + t.Errorf("Error signing transaction: %v", err) + } + if diff := cmp.Diff(signedTx, tx); diff != "" { + t.Errorf("diff: %v\n", diff) + } + +} + +// func setupServer(t *testing.T) { +// t.Skip() +// t.Helper() +// srv, err := newServer() +// if err != nil { +// fmt.Printf("Erorr creating server: %v", err) +// return +// } +// http.HandleFunc("/", srv.mux) +// fmt.Println("Server is listening on port 1234...") +// t.Fatal(http.ListenAndServe(":1234", nil)) +// } + +type SigningService struct{} + +func (h *SigningService) SignTx(r *http.Request, args *Args, reply *string) error { + *reply = "Hello, " + args.Name + "!" + return nil +} + +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"` +} + +func newServer() (*server, error) { + signer, address, err := setupAccount("/tmp/keystore") + if err != nil { + return nil, err + } + s := &server{ + signerFn: signer, + address: address, + } + s.handlers = map[string]func(*json.RawMessage) (string, error){ + "eth_signTransaction": s.signTransaction, + } + return s, nil +} + +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) + } + log.Printf("Created account: %s", a.Address.Hex()) + txOpts, err := bind.NewKeyStoreTransactorWithChainID(ks, a, big.NewInt(1)) + 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) (any, error) { + var arr []any + if err := json.Unmarshal(params, &arr); err != nil { + return "", fmt.Errorf("unmarshaling first param: %w", err) + } + return arr[0], nil +} + +// func unmarshallTx(params *json.RawMessage) (*types.Transaction, error) { + +// } + +func encodeTx(tx *types.Transaction) (string, error) { + data, err := tx.MarshalBinary() + if err != nil { + return "", err + } + return hexutil.Encode(data), nil +} + +func (s *server) signTransaction(params *json.RawMessage) (string, error) { + param, err := unmarshallFirst(*params) + if err != nil { + return "", err + } + + fmt.Printf("anodar first parameter: %q\n", param) + data, err := hexutil.Decode("0xe280827530832dc6c09400000000000000000000000000000000000000018080808080") + if err != nil { + return "", fmt.Errorf("decoding hex: %w", err) + } + fmt.Printf("decoded data: %v\n", data) + var tx types.Transaction + if err := tx.UnmarshalBinary(data); err != nil { + return "", fmt.Errorf("unmarshaling tx: %w", err) + } + fmt.Printf("tx: %v\n", tx) + return encodeTx(&tx) + // var txs []*types.Transaction + // for _, arg := range args { + // signedTx, err := s.signerFn(s.address, arg) + // if err != nil { + // return "", fmt.Errorf("signing transaction: %w", err) + // } + // txs = append(txs, signedTx) + // } + // enc, err := rlp.EncodeToBytes(txs) + // if err != nil { + // return "", fmt.Errorf("encoding transactions to bytes: %w", err) + // } + // return hexutil.Encode(enc), 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, "error encoding response", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(respBytes) +} diff --git a/arbnode/maintenance.go b/arbnode/maintenance.go index f5b937cd06..53d038a0f9 100644 --- a/arbnode/maintenance.go +++ b/arbnode/maintenance.go @@ -78,6 +78,7 @@ func MaintenanceConfigAddOptions(prefix string, f *flag.FlagSet) { var DefaultMaintenanceConfig = MaintenanceConfig{ TimeOfDay: "", + Lock: redislock.DefaultCfg, minutesAfterMidnight: 0, } diff --git a/arbnode/node.go b/arbnode/node.go index bf57b1c004..5d06264b65 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -370,6 +370,7 @@ var ConfigDefault = Config{ Dangerous: DefaultDangerousConfig, TransactionStreamer: DefaultTransactionStreamerConfig, ResourceMgmt: resourcemanager.DefaultConfig, + Maintenance: DefaultMaintenanceConfig, } func ConfigDefaultL1Test() *Config { diff --git a/arbnode/redislock/redis.go b/arbnode/redislock/redis.go index c02476f04a..c8252e059f 100644 --- a/arbnode/redislock/redis.go +++ b/arbnode/redislock/redis.go @@ -42,7 +42,7 @@ func AddConfigOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".my-id", "", "this node's id prefix when acquiring the lock (optional)") f.Duration(prefix+".lockout-duration", DefaultCfg.LockoutDuration, "how long lock is held") f.Duration(prefix+".refresh-duration", DefaultCfg.RefreshDuration, "how long between consecutive calls to redis") - f.String(prefix+".key", prefix+".simple-lock-key", "key for lock") + f.String(prefix+".key", DefaultCfg.Key, "key for lock") f.Bool(prefix+".background-lock", DefaultCfg.BackgroundLock, "should node always try grabing lock in background") } diff --git a/broadcastclient/broadcastclient.go b/broadcastclient/broadcastclient.go index 2649c88192..483b0b3b72 100644 --- a/broadcastclient/broadcastclient.go +++ b/broadcastclient/broadcastclient.go @@ -96,7 +96,7 @@ var DefaultConfig = Config{ RequireChainId: false, RequireFeedVersion: false, Verify: signature.DefultFeedVerifierConfig, - URL: []string{""}, + URL: []string{}, Timeout: 20 * time.Second, EnableCompression: true, } diff --git a/cmd/genericconf/config.go b/cmd/genericconf/config.go index 8e75b61772..c3282fe1af 100644 --- a/cmd/genericconf/config.go +++ b/cmd/genericconf/config.go @@ -33,7 +33,7 @@ func ConfConfigAddOptions(prefix string, f *flag.FlagSet) { var ConfConfigDefault = ConfConfig{ Dump: false, EnvPrefix: "", - File: nil, + File: []string{}, S3: DefaultS3Config, String: "", ReloadInterval: 0, diff --git a/cmd/genericconf/server.go b/cmd/genericconf/server.go index 53560dfdb0..3da027ab27 100644 --- a/cmd/genericconf/server.go +++ b/cmd/genericconf/server.go @@ -26,7 +26,7 @@ var HTTPConfigDefault = HTTPConfig{ Port: 8547, API: append(node.DefaultConfig.HTTPModules, "eth", "arb"), RPCPrefix: node.DefaultConfig.HTTPPathPrefix, - CORSDomain: node.DefaultConfig.HTTPCors, + CORSDomain: []string{}, VHosts: node.DefaultConfig.HTTPVirtualHosts, ServerTimeouts: HTTPServerTimeoutConfigDefault, } @@ -91,7 +91,7 @@ var WSConfigDefault = WSConfig{ Port: 8548, API: append(node.DefaultConfig.WSModules, "eth", "arb"), RPCPrefix: node.DefaultConfig.WSPathPrefix, - Origins: node.DefaultConfig.WSOrigins, + Origins: []string{}, ExposeAll: node.DefaultConfig.WSExposeAll, } @@ -137,7 +137,7 @@ type GraphQLConfig struct { var GraphQLConfigDefault = GraphQLConfig{ Enable: false, - CORSDomain: node.DefaultConfig.GraphQLCors, + CORSDomain: []string{}, VHosts: node.DefaultConfig.GraphQLVirtualHosts, } diff --git a/cmd/nitro/config_test.go b/cmd/nitro/config_test.go index 417b256116..ea04d4eb1f 100644 --- a/cmd/nitro/config_test.go +++ b/cmd/nitro/config_test.go @@ -15,10 +15,29 @@ import ( "time" "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/cmd/util/confighelpers" "github.com/offchainlabs/nitro/util/colors" "github.com/offchainlabs/nitro/util/testhelpers" + + "github.com/r3labs/diff/v3" + flag "github.com/spf13/pflag" ) +func TestEmptyCliConfig(t *testing.T) { + f := flag.NewFlagSet("", flag.ContinueOnError) + NodeConfigAddOptions(f) + k, err := confighelpers.BeginCommonParse(f, []string{}) + Require(t, err) + var emptyCliNodeConfig NodeConfig + err = confighelpers.EndCommonParse(k, &emptyCliNodeConfig) + Require(t, err) + if !reflect.DeepEqual(emptyCliNodeConfig, NodeConfigDefault) { + changelog, err := diff.Diff(emptyCliNodeConfig, NodeConfigDefault) + Require(t, err) + Fail(t, "empty cli config differs from expected default", changelog) + } +} + func TestSeqConfig(t *testing.T) { args := strings.Split("--persistent.chain /tmp/data --init.dev-init --node.parent-chain-reader.enable=false --parent-chain.id 5 --chain.id 421613 --parent-chain.wallet.pathname /l1keystore --parent-chain.wallet.password passphrase --http.addr 0.0.0.0 --ws.addr 0.0.0.0 --node.sequencer --execution.sequencer.enable --node.feed.output.enable --node.feed.output.port 9642", " ") _, _, _, err := ParseNode(context.Background(), args) diff --git a/cmd/nitro/nitro.go b/cmd/nitro/nitro.go index 9656f7f5ec..285cc3fe86 100644 --- a/cmd/nitro/nitro.go +++ b/cmd/nitro/nitro.go @@ -591,16 +591,23 @@ type NodeConfig struct { var NodeConfigDefault = NodeConfig{ Conf: genericconf.ConfConfigDefault, Node: arbnode.ConfigDefault, + Execution: gethexec.ConfigDefault, + Validation: valnode.DefaultValidationConfig, ParentChain: conf.L1ConfigDefault, Chain: conf.L2ConfigDefault, LogLevel: int(log.LvlInfo), LogType: "plaintext", + FileLogging: genericconf.DefaultFileLoggingConfig, Persistent: conf.PersistentConfigDefault, HTTP: genericconf.HTTPConfigDefault, WS: genericconf.WSConfigDefault, IPC: genericconf.IPCConfigDefault, + Auth: genericconf.AuthRPCConfigDefault, + GraphQL: genericconf.GraphQLConfigDefault, Metrics: false, MetricsServer: genericconf.MetricsServerConfigDefault, + Init: InitConfigDefault, + Rpc: genericconf.DefaultRpcConfig, PProf: false, PprofCfg: genericconf.PProfDefault, } diff --git a/cmd/rpcsvr/main.go b/cmd/rpcsvr/main.go new file mode 100644 index 0000000000..5fc0b7fc49 --- /dev/null +++ b/cmd/rpcsvr/main.go @@ -0,0 +1,134 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "log" + "math/big" + "net/http" + + "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" +) + +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"` +} + +func new() (*server, error) { + signer, address, err := setupAccount("/tmp/keystore") + if err != nil { + return nil, err + } + s := &server{ + signerFn: signer, + address: address, + } + s.handlers = map[string]func(*json.RawMessage) (string, error){ + "eth_signTransaction": s.signTransaction, + } + return s, nil +} + +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) + } + log.Printf("Created account: %s", a.Address.Hex()) + txOpts, err := bind.NewKeyStoreTransactorWithChainID(ks, a, big.NewInt(1)) + if err != nil { + return nil, common.Address{}, fmt.Errorf("creating transactor: %w", err) + } + return txOpts.Signer, a.Address, nil +} + +func (s *server) signTransaction(params *json.RawMessage) (string, error) { + var args []apitypes.SendTxArgs + if err := json.Unmarshal(*params, &args); err != nil { + return "", fmt.Errorf("unmarshaling params: %w", err) + } + var txs []*types.Transaction + for _, arg := range args { + signedTx, err := s.signerFn(s.address, arg.ToTransaction()) + if err != nil { + return "", fmt.Errorf("signing transaction: %w", err) + } + txs = append(txs, signedTx) + } + enc, err := rlp.EncodeToBytes(txs) + if err != nil { + return "", fmt.Errorf("encoding transactions to bytes: %w", err) + } + return hexutil.Encode(enc), 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, "error encoding response", http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(respBytes) +} + +func main() { + srv, err := new() + if err != nil { + fmt.Printf("Erorr creating server: %w", err) + return + } + http.HandleFunc("/", srv.mux) + fmt.Println("Server is listening on port 1234...") + log.Fatal(http.ListenAndServe(":1234", nil)) +} diff --git a/das/das.go b/das/das.go index 208a12cc83..9133b73ea4 100644 --- a/das/das.go +++ b/das/das.go @@ -69,6 +69,7 @@ var DefaultDataAvailabilityConfig = DataAvailabilityConfig{ RestAggregator: DefaultRestfulClientAggregatorConfig, ParentChainConnectionAttempts: 15, PanicOnError: false, + IpfsStorage: DefaultIpfsStorageServiceConfig, } func OptionalAddressFromString(s string) (*common.Address, error) { diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index b29309cdbb..1068dda967 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -91,6 +91,7 @@ var ConfigDefault = Config{ TxLookupLimit: 126_230_400, // 1 year at 4 blocks per second Caching: DefaultCachingConfig, Dangerous: DefaultDangerousConfig, + Forwarder: DefaultNodeForwarderConfig, } func ConfigDefaultNonSequencerTest() *Config { diff --git a/staker/block_validator.go b/staker/block_validator.go index 94bc2a0806..108d6d1d49 100644 --- a/staker/block_validator.go +++ b/staker/block_validator.go @@ -750,7 +750,7 @@ func (v *BlockValidator) iterativeValidationProgress(ctx context.Context, ignore } else if reorg != nil { err := v.Reorg(ctx, *reorg) if err != nil { - log.Error("error trying to rorg validation", "pos", *reorg-1, "err", err) + log.Error("error trying to reorg validation", "pos", *reorg-1, "err", err) v.possiblyFatal(err) } } diff --git a/staker/staker.go b/staker/staker.go index d52d1adc77..4148d0a204 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -203,7 +203,7 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".gas-refunder-address", DefaultL1ValidatorConfig.GasRefunderAddress, "The gas refunder contract address (optional)") f.String(prefix+".redis-url", DefaultL1ValidatorConfig.RedisUrl, "redis url for L1 validator") f.Uint64(prefix+".extra-gas", DefaultL1ValidatorConfig.ExtraGas, "use this much more gas than estimation says is necessary to post transactions") - dataposter.DataPosterConfigAddOptions(prefix+".data-poster", f) + dataposter.DataPosterConfigAddOptions(prefix+".data-poster", f, dataposter.DefaultDataPosterConfigForValidator) redislock.AddConfigOptions(prefix+".redis-lock", f) DangerousConfigAddOptions(prefix+".dangerous", f) genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname) diff --git a/util/headerreader/header_reader.go b/util/headerreader/header_reader.go index ab61f8a2ee..ff3b420a1c 100644 --- a/util/headerreader/header_reader.go +++ b/util/headerreader/header_reader.go @@ -81,6 +81,7 @@ func AddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".poll-only", DefaultConfig.PollOnly, "do not attempt to subscribe to header events") f.Bool(prefix+".use-finality-data", DefaultConfig.UseFinalityData, "use l1 data about finalized/safe blocks") f.Duration(prefix+".poll-interval", DefaultConfig.PollInterval, "interval when polling endpoint") + f.Duration(prefix+".subscribe-err-interval", DefaultConfig.SubscribeErrInterval, "interval for subscribe error") f.Duration(prefix+".tx-timeout", DefaultConfig.TxTimeout, "timeout when waiting for a transaction") f.Duration(prefix+".old-header-timeout", DefaultConfig.OldHeaderTimeout, "warns if the latest l1 block is at least this old") } diff --git a/util/signature/sign_verify.go b/util/signature/sign_verify.go index 2911912979..5ed852bfbc 100644 --- a/util/signature/sign_verify.go +++ b/util/signature/sign_verify.go @@ -31,6 +31,12 @@ func SignVerifyConfigAddOptions(prefix string, f *flag.FlagSet) { } var DefaultSignVerifyConfig = SignVerifyConfig{ + ECDSA: DefultFeedVerifierConfig, + SymmetricFallback: false, + SymmetricSign: false, + Symmetric: EmptySimpleHmacConfig, +} +var TestSignVerifyConfig = SignVerifyConfig{ ECDSA: VerifierConfig{ AcceptSequencer: true, }, diff --git a/util/signature/sign_verify_test.go b/util/signature/sign_verify_test.go index 8ecb6e5ccc..916fc03a20 100644 --- a/util/signature/sign_verify_test.go +++ b/util/signature/sign_verify_test.go @@ -17,7 +17,7 @@ func TestSignVerifyModes(t *testing.T) { signingAddr := crypto.PubkeyToAddress(privateKey.PublicKey) dataSigner := DataSignerFromPrivateKey(privateKey) - config := DefaultSignVerifyConfig + config := TestSignVerifyConfig config.SymmetricFallback = false config.SymmetricSign = false config.ECDSA.AcceptSequencer = false @@ -25,14 +25,14 @@ func TestSignVerifyModes(t *testing.T) { signVerifyECDSA, err := NewSignVerify(&config, dataSigner, nil) Require(t, err) - configSymmetric := DefaultSignVerifyConfig + configSymmetric := TestSignVerifyConfig configSymmetric.SymmetricFallback = true configSymmetric.SymmetricSign = true configSymmetric.ECDSA.AcceptSequencer = false signVerifySymmetric, err := NewSignVerify(&configSymmetric, nil, nil) Require(t, err) - configFallback := DefaultSignVerifyConfig + configFallback := TestSignVerifyConfig configFallback.SymmetricFallback = true configFallback.SymmetricSign = false configFallback.ECDSA.AllowedAddresses = []string{signingAddr.Hex()} diff --git a/util/signature/verifier.go b/util/signature/verifier.go index 2bf5b854ed..c2f6529ec6 100644 --- a/util/signature/verifier.go +++ b/util/signature/verifier.go @@ -37,7 +37,7 @@ var ErrMissingSignature = fmt.Errorf("%w: signature not found", ErrSignatureNotV var ErrSignerNotApproved = fmt.Errorf("%w: signer not approved", ErrSignatureNotVerified) func FeedVerifierConfigAddOptions(prefix string, f *flag.FlagSet) { - f.StringArray(prefix+".allowed-addresses", DefultFeedVerifierConfig.AllowedAddresses, "a list of allowed addresses") + f.StringSlice(prefix+".allowed-addresses", DefultFeedVerifierConfig.AllowedAddresses, "a list of allowed addresses") f.Bool(prefix+".accept-sequencer", DefultFeedVerifierConfig.AcceptSequencer, "accept verified message from sequencer") DangerousFeedVerifierConfigAddOptions(prefix+".dangerous", f) } From 42694708bd45b336f3ea88c2490b6bf49a0ebf79 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 13 Oct 2023 16:56:11 +0200 Subject: [PATCH 2/9] merge master --- arbnode/dataposter/data_poster.go | 20 ++-- arbnode/dataposter/dataposter_test.go | 144 +++++++++----------------- 2 files changed, 63 insertions(+), 101 deletions(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index c8a673ec9c..76787e8c4d 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -20,6 +20,7 @@ import ( "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "github.com/go-redis/redis/v8" "github.com/offchainlabs/nitro/arbnode/dataposter/dbstorage" @@ -182,15 +183,20 @@ func externalSigner(ctx context.Context, addr string, rpcURL string) (signerFn, return nil, common.Address{}, fmt.Errorf("error connecting external signer: %w", err) } return func(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { - data, err := tx.MarshalBinary() - if err != nil { - return nil, err + // According to the "eth_signTransaction" API definition, this shoul be + // RLP encoded transaction object. + // https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_signtransaction + var rlpEncTxStr string + if err := client.CallContext(ctx, &rlpEncTxStr, "eth_signTransaction", tx); err != nil { + return nil, fmt.Errorf("signing transaction: %w", err) } - var signedTx *types.Transaction - if err := client.CallContext(ctx, &signedTx, "eth_signTransaction", hexutil.Encode(data)); err != nil { - return nil, fmt.Errorf("error calling with context: %w", err) + data, err := hexutil.Decode(rlpEncTxStr) + if err != nil { + return nil, fmt.Errorf("error decoding hex: %w", err) } - return signedTx, nil + var signedTx types.Transaction + rlp.DecodeBytes(data, &signedTx) + return &signedTx, nil }, sender, nil } diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index c4c131c79a..6334d2917b 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -5,9 +5,9 @@ import ( "encoding/json" "fmt" "io" - "log" "math/big" "net/http" + "os" "testing" "time" @@ -16,6 +16,8 @@ import ( "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/google/go-cmp/cmp" ) @@ -54,59 +56,32 @@ func TestParseReplacementTimes(t *testing.T) { } } -type Args struct { - Name string -} - -func TestRPC(t *testing.T) { - srv, err := newServer() - if err != nil { - fmt.Printf("Erorr creating server: %v", err) - return - } - http.HandleFunc("/", srv.mux) +func TestExternalSigner(t *testing.T) { + ctx := context.Background() + httpSrv, srv := newServer(ctx, t) + t.Cleanup(func() { httpSrv.Shutdown(ctx) }) go func() { fmt.Println("Server is listening on port 1234...") - t.Errorf("error listening: %v", http.ListenAndServe(":1234", nil)) + if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + fmt.Printf("error listening: %v", http.ListenAndServe(":1234", nil)) + } }() - - if err != nil { - t.Fatalf("Error creating a server: %v", err) - } - ctx := context.Background() signer, addr, err := externalSigner(ctx, srv.address.Hex(), "http://127.0.0.1:1234") if err != nil { t.Fatalf("Error getting external signer: %v", err) } - tx := types.NewTransaction(0, common.HexToAddress("0x01"), big.NewInt(0), 3000000, big.NewInt(30000), nil) - signedTx, err := signer(ctx, addr, tx) + tx := types.NewTransaction(13, common.HexToAddress("0x01"), big.NewInt(1), 2, big.NewInt(3), []byte{0x01, 0x02, 0x03}) + got, err := signer(ctx, addr, tx) if err != nil { - t.Errorf("Error signing transaction: %v", err) + t.Fatalf("Error signing transaction with external signer: %v", err) } - if diff := cmp.Diff(signedTx, tx); diff != "" { - t.Errorf("diff: %v\n", diff) + want, err := srv.signerFn(addr, tx) + if err != nil { + t.Fatalf("Error signing transaction: %v", err) + } + if diff := cmp.Diff(want.Hash(), got.Hash()); diff != "" { + t.Errorf("Signing transaction: unexpected diff: %v\n", diff) } - -} - -// func setupServer(t *testing.T) { -// t.Skip() -// t.Helper() -// srv, err := newServer() -// if err != nil { -// fmt.Printf("Erorr creating server: %v", err) -// return -// } -// http.HandleFunc("/", srv.mux) -// fmt.Println("Server is listening on port 1234...") -// t.Fatal(http.ListenAndServe(":1234", nil)) -// } - -type SigningService struct{} - -func (h *SigningService) SignTx(r *http.Request, args *Args, reply *string) error { - *reply = "Hello, " + args.Name + "!" - return nil } type server struct { @@ -123,24 +98,32 @@ type request struct { type response struct { ID *json.RawMessage `json:"id"` - Result string `json:"result"` + Result string `json:"result,omitempty"` } -func newServer() (*server, error) { +// 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 { - return nil, err - } - s := &server{ - signerFn: signer, - address: address, + 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){ "eth_signTransaction": s.signTransaction, } - return s, nil + m := http.NewServeMux() + httpSrv := &http.Server{Addr: ":1234", Handler: m} + 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, @@ -154,7 +137,6 @@ func setupAccount(dir string) (bind.SignerFn, common.Address, error) { if err := ks.Unlock(a, "password"); err != nil { return nil, common.Address{}, fmt.Errorf("unlocking account: %w", err) } - log.Printf("Created account: %s", a.Address.Hex()) txOpts, err := bind.NewKeyStoreTransactorWithChainID(ks, a, big.NewInt(1)) if err != nil { return nil, common.Address{}, fmt.Errorf("creating transactor: %w", err) @@ -167,57 +149,31 @@ func setupAccount(dir string) (bind.SignerFn, common.Address, error) { // 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) (any, error) { - var arr []any +func unmarshallFirst(params []byte) (*types.Transaction, error) { + var arr []apitypes.SendTxArgs if err := json.Unmarshal(params, &arr); err != nil { - return "", fmt.Errorf("unmarshaling first param: %w", err) + return nil, fmt.Errorf("unmarshaling first param: %w", err) } - return arr[0], nil -} - -// func unmarshallTx(params *json.RawMessage) (*types.Transaction, error) { - -// } - -func encodeTx(tx *types.Transaction) (string, error) { - data, err := tx.MarshalBinary() - if err != nil { - return "", err + if len(arr) != 1 { + return nil, fmt.Errorf("argument should be a single transaction, but got: %d", len(arr)) } - return hexutil.Encode(data), nil + return arr[0].ToTransaction(), nil } func (s *server) signTransaction(params *json.RawMessage) (string, error) { - param, err := unmarshallFirst(*params) + tx, err := unmarshallFirst(*params) if err != nil { return "", err } - - fmt.Printf("anodar first parameter: %q\n", param) - data, err := hexutil.Decode("0xe280827530832dc6c09400000000000000000000000000000000000000018080808080") + signedTx, err := s.signerFn(s.address, tx) if err != nil { - return "", fmt.Errorf("decoding hex: %w", err) - } - fmt.Printf("decoded data: %v\n", data) - var tx types.Transaction - if err := tx.UnmarshalBinary(data); err != nil { - return "", fmt.Errorf("unmarshaling tx: %w", err) - } - fmt.Printf("tx: %v\n", tx) - return encodeTx(&tx) - // var txs []*types.Transaction - // for _, arg := range args { - // signedTx, err := s.signerFn(s.address, arg) - // if err != nil { - // return "", fmt.Errorf("signing transaction: %w", err) - // } - // txs = append(txs, signedTx) - // } - // enc, err := rlp.EncodeToBytes(txs) - // if err != nil { - // return "", fmt.Errorf("encoding transactions to bytes: %w", err) - // } - // return hexutil.Encode(enc), 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) { From c3ed563a94c74dc5025170a57418fca08ee25935 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 13 Oct 2023 16:58:55 +0200 Subject: [PATCH 3/9] Drop accidentally added server binary for manual testing --- cmd/rpcsvr/main.go | 134 --------------------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 cmd/rpcsvr/main.go diff --git a/cmd/rpcsvr/main.go b/cmd/rpcsvr/main.go deleted file mode 100644 index 5fc0b7fc49..0000000000 --- a/cmd/rpcsvr/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io" - "log" - "math/big" - "net/http" - - "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" -) - -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"` -} - -func new() (*server, error) { - signer, address, err := setupAccount("/tmp/keystore") - if err != nil { - return nil, err - } - s := &server{ - signerFn: signer, - address: address, - } - s.handlers = map[string]func(*json.RawMessage) (string, error){ - "eth_signTransaction": s.signTransaction, - } - return s, nil -} - -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) - } - log.Printf("Created account: %s", a.Address.Hex()) - txOpts, err := bind.NewKeyStoreTransactorWithChainID(ks, a, big.NewInt(1)) - if err != nil { - return nil, common.Address{}, fmt.Errorf("creating transactor: %w", err) - } - return txOpts.Signer, a.Address, nil -} - -func (s *server) signTransaction(params *json.RawMessage) (string, error) { - var args []apitypes.SendTxArgs - if err := json.Unmarshal(*params, &args); err != nil { - return "", fmt.Errorf("unmarshaling params: %w", err) - } - var txs []*types.Transaction - for _, arg := range args { - signedTx, err := s.signerFn(s.address, arg.ToTransaction()) - if err != nil { - return "", fmt.Errorf("signing transaction: %w", err) - } - txs = append(txs, signedTx) - } - enc, err := rlp.EncodeToBytes(txs) - if err != nil { - return "", fmt.Errorf("encoding transactions to bytes: %w", err) - } - return hexutil.Encode(enc), 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, "error encoding response", http.StatusInternalServerError) - return - } - w.Header().Set("Content-Type", "application/json") - w.Write(respBytes) -} - -func main() { - srv, err := new() - if err != nil { - fmt.Printf("Erorr creating server: %w", err) - return - } - http.HandleFunc("/", srv.mux) - fmt.Println("Server is listening on port 1234...") - log.Fatal(http.ListenAndServe(":1234", nil)) -} From c177f025513f5a17fbaca04c742f01290efb0cc5 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 13 Oct 2023 17:05:35 +0200 Subject: [PATCH 4/9] Fix linter errors --- arbnode/dataposter/data_poster.go | 4 +++- arbnode/dataposter/dataposter_test.go | 17 ++++++++++++----- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 76787e8c4d..15044cc4ec 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -195,7 +195,9 @@ func externalSigner(ctx context.Context, addr string, rpcURL string) (signerFn, return nil, fmt.Errorf("error decoding hex: %w", err) } var signedTx types.Transaction - rlp.DecodeBytes(data, &signedTx) + if err := rlp.DecodeBytes(data, &signedTx); err != nil { + return nil, fmt.Errorf("error decoding signed transaction: %w", err) + } return &signedTx, nil }, sender, nil } diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index 6334d2917b..c190337a05 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -59,11 +59,16 @@ func TestParseReplacementTimes(t *testing.T) { func TestExternalSigner(t *testing.T) { ctx := context.Background() httpSrv, srv := newServer(ctx, t) - t.Cleanup(func() { httpSrv.Shutdown(ctx) }) + t.Cleanup(func() { + if err := httpSrv.Shutdown(ctx); err != nil { + t.Fatalf("Error shutting down http server: %v", err) + } + }) go func() { fmt.Println("Server is listening on port 1234...") if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { - fmt.Printf("error listening: %v", http.ListenAndServe(":1234", nil)) + t.Errorf("ListenAndServe() unexpected error: %v", err) + return } }() signer, addr, err := externalSigner(ctx, srv.address.Hex(), "http://127.0.0.1:1234") @@ -117,7 +122,7 @@ func newServer(ctx context.Context, t *testing.T) (*http.Server, *server) { "eth_signTransaction": s.signTransaction, } m := http.NewServeMux() - httpSrv := &http.Server{Addr: ":1234", Handler: m} + httpSrv := &http.Server{Addr: ":1234", Handler: m, ReadTimeout: 5 * time.Second} m.HandleFunc("/", s.mux) return httpSrv, s } @@ -201,9 +206,11 @@ func (s *server) mux(w http.ResponseWriter, r *http.Request) { resp := response{ID: req.ID, Result: result} respBytes, err := json.Marshal(resp) if err != nil { - http.Error(w, "error encoding response", http.StatusInternalServerError) + http.Error(w, fmt.Sprintf("error encoding response: %v", err), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") - w.Write(respBytes) + if _, err := w.Write(respBytes); err != nil { + fmt.Printf("error writing response: %v\n", err) + } } From 2f7b86750db92c16b09e1114ab661fde9a06eb80 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Fri, 13 Oct 2023 17:36:52 +0200 Subject: [PATCH 5/9] Make signing method configurable to allow both eth_signTransaction and account_signTransaction --- arbnode/dataposter/data_poster.go | 17 ++++++++++------- arbnode/dataposter/dataposter_test.go | 7 ++++++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 15044cc4ec..11b7a954ed 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -161,7 +161,7 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro errorCount: make(map[uint64]int), } if cfg.ExternalSigner.Enabled { - signer, sender, err := externalSigner(ctx, cfg.ExternalSigner.Address, cfg.ExternalSigner.URL) + signer, sender, err := externalSigner(ctx, &cfg.ExternalSigner) if err != nil { return nil, err } @@ -173,12 +173,12 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro // externalSigner returns signer function and ethereum address of the signer. // Returns an error if address isn't specified or if it can't connect to the // signer RPC server. -func externalSigner(ctx context.Context, addr string, rpcURL string) (signerFn, common.Address, error) { - if addr == "" { +func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, common.Address, error) { + if opts.Address == "" { return nil, common.Address{}, errors.New("external signer (From) address specified") } - sender := common.HexToAddress(addr) - client, err := rpc.DialContext(ctx, rpcURL) + sender := common.HexToAddress(opts.Address) + client, err := rpc.DialContext(ctx, opts.URL) if err != nil { return nil, common.Address{}, fmt.Errorf("error connecting external signer: %w", err) } @@ -710,6 +710,8 @@ type ExternalSignerCfg struct { URL string `koanf:"url"` // Hex encoded ethereum address of the external signer. Address string `koanf:"address"` + // API method name (e.g. eth_signTransaction). + Method string `koanf:"method"` } type DangerousConfig struct { @@ -751,6 +753,7 @@ func addExternalSignerOptions(prefix string, f *pflag.FlagSet) { f.Bool(prefix+".enabled", DefaultDataPosterConfig.ExternalSigner.Enabled, "enable external signer") f.String(prefix+".url", DefaultDataPosterConfig.ExternalSigner.URL, "external signer url") f.String(prefix+".address", DefaultDataPosterConfig.ExternalSigner.Address, "external signer address") + f.String(prefix+".method", DefaultDataPosterConfig.ExternalSigner.Method, "external signer method") } var DefaultDataPosterConfig = DataPosterConfig{ @@ -767,7 +770,7 @@ var DefaultDataPosterConfig = DataPosterConfig{ UseNoOpStorage: false, LegacyStorageEncoding: true, Dangerous: DangerousConfig{ClearDBStorage: false}, - ExternalSigner: ExternalSignerCfg{Enabled: false}, + ExternalSigner: ExternalSignerCfg{Enabled: false, Method: "eth_signTransaction"}, } var DefaultDataPosterConfigForValidator = func() DataPosterConfig { @@ -789,7 +792,7 @@ var TestDataPosterConfig = DataPosterConfig{ AllocateMempoolBalance: true, UseDBStorage: false, UseNoOpStorage: false, - ExternalSigner: ExternalSignerCfg{Enabled: false}, + ExternalSigner: ExternalSignerCfg{Enabled: false, Method: "eth_signTransaction"}, } var TestDataPosterConfigForValidator = func() DataPosterConfig { diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index c190337a05..186c85ec9c 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -71,7 +71,12 @@ func TestExternalSigner(t *testing.T) { return } }() - signer, addr, err := externalSigner(ctx, srv.address.Hex(), "http://127.0.0.1:1234") + signer, addr, err := externalSigner(ctx, + &ExternalSignerCfg{ + Address: srv.address.Hex(), + URL: "http://127.0.0.1:1234", + Method: "eth_signTransaction", + }) if err != nil { t.Fatalf("Error getting external signer: %v", err) } From c27cb4b736814df79d6f5d18ee60ca2ba6a35a5f Mon Sep 17 00:00:00 2001 From: Nodar Date: Mon, 16 Oct 2023 13:18:47 +0200 Subject: [PATCH 6/9] Update arbnode/dataposter/data_poster.go Co-authored-by: Joshua Colvin --- arbnode/dataposter/data_poster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 11b7a954ed..a54b05c340 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -183,7 +183,7 @@ func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, com return nil, common.Address{}, fmt.Errorf("error connecting external signer: %w", err) } return func(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { - // According to the "eth_signTransaction" API definition, this shoul be + // According to the "eth_signTransaction" API definition, this should be // RLP encoded transaction object. // https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_signtransaction var rlpEncTxStr string From 46618880e1883f24228746f6b301af44f2dedc26 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Mon, 16 Oct 2023 14:57:06 +0200 Subject: [PATCH 7/9] Check that signed transaction is the one sent for signing --- arbnode/dataposter/data_poster.go | 30 ++++++++++++++------------- arbnode/dataposter/dataposter_test.go | 4 ++-- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index 11b7a954ed..476920978a 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -92,7 +92,7 @@ func parseReplacementTimes(val string) ([]time.Duration, error) { lastReplacementTime = t } if len(res) == 0 { - log.Warn("disabling replace-by-fee for data poster") + log.Warn("Disabling replace-by-fee for data poster") } // To avoid special casing "don't replace again", replace in 10 years. return append(res, time.Hour*24*365*10), nil @@ -160,7 +160,7 @@ func NewDataPoster(ctx context.Context, opts *DataPosterOpts) (*DataPoster, erro redisLock: opts.RedisLock, errorCount: make(map[uint64]int), } - if cfg.ExternalSigner.Enabled { + if cfg.ExternalSigner.URL != "" { signer, sender, err := externalSigner(ctx, &cfg.ExternalSigner) if err != nil { return nil, err @@ -182,22 +182,26 @@ func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, com if err != nil { return nil, common.Address{}, fmt.Errorf("error connecting external signer: %w", err) } + + var hasher types.Signer return func(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { // According to the "eth_signTransaction" API definition, this shoul be // RLP encoded transaction object. // https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_signtransaction - var rlpEncTxStr string - if err := client.CallContext(ctx, &rlpEncTxStr, "eth_signTransaction", tx); err != nil { + var data hexutil.Bytes + if err := client.CallContext(ctx, &data, opts.Method, tx); err != nil { return nil, fmt.Errorf("signing transaction: %w", err) } - data, err := hexutil.Decode(rlpEncTxStr) - if err != nil { - return nil, fmt.Errorf("error decoding hex: %w", err) - } var signedTx types.Transaction if err := rlp.DecodeBytes(data, &signedTx); err != nil { return nil, fmt.Errorf("error decoding signed transaction: %w", err) } + if hasher == nil { + hasher = types.LatestSignerForChainID(tx.ChainId()) + } + if hasher.Hash(tx) != hasher.Hash(&signedTx) { + return nil, fmt.Errorf("transaction: %x from external signer differs from request: %x", hasher.Hash(tx), hasher.Hash(&signedTx)) + } return &signedTx, nil }, sender, nil } @@ -703,10 +707,9 @@ type DataPosterConfig struct { } type ExternalSignerCfg struct { - // If enabled, this overrides transaction options and uses external signer + // URL of the external signer rpc server, if set this overrides transaction + // options and uses external signer // for signing transactions. - Enabled bool `koanf:"enabled"` - // URL of the external signer rpc server. URL string `koanf:"url"` // Hex encoded ethereum address of the external signer. Address string `koanf:"address"` @@ -750,7 +753,6 @@ func addDangerousOptions(prefix string, f *pflag.FlagSet) { } func addExternalSignerOptions(prefix string, f *pflag.FlagSet) { - f.Bool(prefix+".enabled", DefaultDataPosterConfig.ExternalSigner.Enabled, "enable external signer") f.String(prefix+".url", DefaultDataPosterConfig.ExternalSigner.URL, "external signer url") f.String(prefix+".address", DefaultDataPosterConfig.ExternalSigner.Address, "external signer address") f.String(prefix+".method", DefaultDataPosterConfig.ExternalSigner.Method, "external signer method") @@ -770,7 +772,7 @@ var DefaultDataPosterConfig = DataPosterConfig{ UseNoOpStorage: false, LegacyStorageEncoding: true, Dangerous: DangerousConfig{ClearDBStorage: false}, - ExternalSigner: ExternalSignerCfg{Enabled: false, Method: "eth_signTransaction"}, + ExternalSigner: ExternalSignerCfg{}, } var DefaultDataPosterConfigForValidator = func() DataPosterConfig { @@ -792,7 +794,7 @@ var TestDataPosterConfig = DataPosterConfig{ AllocateMempoolBalance: true, UseDBStorage: false, UseNoOpStorage: false, - ExternalSigner: ExternalSignerCfg{Enabled: false, Method: "eth_signTransaction"}, + ExternalSigner: ExternalSignerCfg{}, } var TestDataPosterConfigForValidator = func() DataPosterConfig { diff --git a/arbnode/dataposter/dataposter_test.go b/arbnode/dataposter/dataposter_test.go index 186c85ec9c..4b4768e0b4 100644 --- a/arbnode/dataposter/dataposter_test.go +++ b/arbnode/dataposter/dataposter_test.go @@ -75,7 +75,7 @@ func TestExternalSigner(t *testing.T) { &ExternalSignerCfg{ Address: srv.address.Hex(), URL: "http://127.0.0.1:1234", - Method: "eth_signTransaction", + Method: "test_signTransaction", }) if err != nil { t.Fatalf("Error getting external signer: %v", err) @@ -124,7 +124,7 @@ func newServer(ctx context.Context, t *testing.T) (*http.Server, *server) { s := &server{signerFn: signer, address: address} s.handlers = map[string]func(*json.RawMessage) (string, error){ - "eth_signTransaction": s.signTransaction, + "test_signTransaction": s.signTransaction, } m := http.NewServeMux() httpSrv := &http.Server{Addr: ":1234", Handler: m, ReadTimeout: 5 * time.Second} From c551ff5595d4d9a56fd31a7b658d756db6f07499 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Mon, 16 Oct 2023 15:00:14 +0200 Subject: [PATCH 8/9] List transactions in correct order in the log line --- arbnode/dataposter/data_poster.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index ff17510144..fa7dfec483 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -200,7 +200,7 @@ func externalSigner(ctx context.Context, opts *ExternalSignerCfg) (signerFn, com hasher = types.LatestSignerForChainID(tx.ChainId()) } if hasher.Hash(tx) != hasher.Hash(&signedTx) { - return nil, fmt.Errorf("transaction: %x from external signer differs from request: %x", hasher.Hash(tx), hasher.Hash(&signedTx)) + return nil, fmt.Errorf("transaction: %x from external signer differs from request: %x", hasher.Hash(&signedTx), hasher.Hash(tx)) } return &signedTx, nil }, sender, nil From 34fe2d041d966f6cbbc425b736c5231db0f3a7b0 Mon Sep 17 00:00:00 2001 From: Nodar Ambroladze Date: Tue, 17 Oct 2023 19:03:08 +0200 Subject: [PATCH 9/9] Add default method for external signer --- arbnode/dataposter/data_poster.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/arbnode/dataposter/data_poster.go b/arbnode/dataposter/data_poster.go index fa7dfec483..cf4aab9e37 100644 --- a/arbnode/dataposter/data_poster.go +++ b/arbnode/dataposter/data_poster.go @@ -772,7 +772,7 @@ var DefaultDataPosterConfig = DataPosterConfig{ UseNoOpStorage: false, LegacyStorageEncoding: true, Dangerous: DangerousConfig{ClearDBStorage: false}, - ExternalSigner: ExternalSignerCfg{}, + ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"}, } var DefaultDataPosterConfigForValidator = func() DataPosterConfig { @@ -794,7 +794,7 @@ var TestDataPosterConfig = DataPosterConfig{ AllocateMempoolBalance: true, UseDBStorage: false, UseNoOpStorage: false, - ExternalSigner: ExternalSignerCfg{}, + ExternalSigner: ExternalSignerCfg{Method: "eth_signTransaction"}, } var TestDataPosterConfigForValidator = func() DataPosterConfig {