Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upstream proxyd/v4.13.0 #6

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6a5462e
feat: Integrate op-node with op-signer for block payload signing (#59)
mininny Dec 3, 2024
6b61b79
chore(deps): bump github.com/rs/cors in /op-conductor-mon
dependabot[bot] Dec 3, 2024
25d9e0a
op-conductor-ops: halt-sequencer command and pre-flight checks to for…
yashvardhan-kukreja Dec 6, 2024
e99553c
Make proxyd error handling more generic for different backend impleme…
angel-ding-cb Dec 9, 2024
3019068
fix conductor_overrideLeader and support forced leadership transfer (…
yashvardhan-kukreja Dec 9, 2024
c617925
feat: support un-overriding the leader with safeguards (#104)
yashvardhan-kukreja Dec 10, 2024
7633469
Merge pull request #102 from angel-ding-cb/rpc-error-handling
jelias2 Dec 11, 2024
7af7c07
Merge pull request #95 from ethereum-optimism/dependabot/go_modules/o…
jelias2 Dec 11, 2024
8f78ea6
chore(deps): bump github.com/btcsuite/btcd in /peer-mgmt-service
dependabot[bot] Dec 11, 2024
6f9aeb2
Merge pull request #107 from ethereum-optimism/dependabot/go_modules/…
jelias2 Dec 11, 2024
b52747f
Add sequencer voting column in status table (#108)
ImTei Dec 12, 2024
fa80857
op-conductor-ops: shorten status table titles (#110)
zhwrd Dec 12, 2024
f34206c
proxyd: add Redis cluster support (#100)
Vui-Chee Dec 12, 2024
b5a4d2a
chore(deps): bump github.com/golang-jwt/jwt/v4 in /op-signer (#97)
dependabot[bot] Dec 13, 2024
5952d72
chore(deps): bump github.com/golang-jwt/jwt/v4 in /op-txproxy (#96)
dependabot[bot] Dec 18, 2024
8e5f685
fix: don't count replacement underpriced errors (#112)
danyalprout Dec 20, 2024
3029e92
Merge tag 'proxyd/v4.13.0' into vui-chee/main/proxyd/v4.13.0
Vui-Chee Dec 23, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This repository is an extension of the [Optimism monorepo](https://github.com/et

## Components
- op-conductor-mon: Monitors multiple op-conductor instances and provides a unified interface for reporting metrics.
- op-signer: Thin gateway that supports `eth_signTransaction` RPC endpoint to sign ethereum tx payloads using private key stored in KMS
- op-signer: Thin gateway that supports various RPC endpoints to sign payloads from op-stack components using private key stored in KMS.
- op-ufm: User facing monitoring creates transactions at regular intervals and observe transaction propagation across different RPC providers.

## Release Process
Expand Down
2 changes: 1 addition & 1 deletion op-conductor-mon/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/gorilla/mux v1.8.0
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.19.1
github.com/rs/cors v1.9.0
github.com/rs/cors v1.11.0
github.com/stretchr/testify v1.9.0
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
gopkg.in/yaml.v3 v3.0.1
Expand Down
4 changes: 2 additions & 2 deletions op-conductor-mon/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,8 @@ github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
Expand Down
108 changes: 86 additions & 22 deletions op-conductor-ops/op-conductor-ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,11 @@ def status(network: str):
sequencers = network_obj.sequencers
table = Table(
"Sequencer ID",
"Conductor Active",
"Sequencer Healthy",
"Conductor Leader",
"Active Sequencer",
"Active",
"Healthy",
"Leader",
"Sequencing",
"Voting",
"Unsafe Number",
"Unsafe Hash",
)
Expand All @@ -75,6 +76,7 @@ def status(network: str):
print_boolean(sequencer.sequencer_healthy),
print_boolean(sequencer.conductor_leader),
print_boolean(sequencer.sequencer_active),
print_boolean(sequencer.voting),
str(sequencer.unsafe_l2_number),
str(sequencer.unsafe_l2_hash),
)
Expand Down Expand Up @@ -105,7 +107,7 @@ def status(network: str):


@app.command()
def transfer_leader(network: str, sequencer_id: str):
def transfer_leader(network: str, sequencer_id: str, force: bool = False):
"""Transfer leadership to a specific sequencer."""
network_obj = get_network(network)

Expand All @@ -119,8 +121,8 @@ def transfer_leader(network: str, sequencer_id: str):
raise typer.Exit(code=1)

healthy = sequencer.sequencer_healthy
if not healthy:
print_error(f"Target sequencer {sequencer_id} is not healthy")
if not healthy and not force:
print_error(f"Target sequencer {sequencer_id} is not healthy. To still perform the leadership transfer, please use --force.")
raise typer.Exit(code=1)

leader = network_obj.find_conductor_leader()
Expand Down Expand Up @@ -212,8 +214,9 @@ def resume(network: str, sequencer_id: str = None):


@app.command()
def override_leader(network: str, sequencer_id: str):
"""Override the conductor_leader response for a sequencer to True.
def override_leader(network: str, sequencer_id: str, remove: bool = False, y: bool = False):
"""Override the conductor_leader response for a sequencer to True. To remove the override, please use --remove.
If you want this command to be un-interactive, please use it with --y.
Note that this does not affect consensus and it should only be used for disaster recovery purposes.
"""
network_obj = get_network(network)
Expand All @@ -223,9 +226,22 @@ def override_leader(network: str, sequencer_id: str):
f"sequencer ID {sequencer_id} not found in network {network}")
raise typer.Exit(code=1)

if remove:
if y:
print_warn("You are trying to remove the override. This would require you to explicitly restart op-node.")
else:
typer.echo("Note: you are trying to remove the override. This would require you to explicitly restart op-node.")
typer.echo("Please be carefully sure of that and proceed by entering 'y' or exit by entering 'n':")
user_input = input().lower()
if user_input not in ['y', 'n']:
print_error("Wrong input provided")
raise typer.Exit(code=1)
if user_input == 'n':
raise typer.Exit(code=0)

resp = requests.post(
sequencer.conductor_rpc_url,
json=make_rpc_payload("conductor_overrideLeader"),
json=make_rpc_payload("conductor_overrideLeader", [not remove]),
)
resp.raise_for_status()
if "error" in resp.json():
Expand All @@ -234,18 +250,21 @@ def override_leader(network: str, sequencer_id: str):
)
raise typer.Exit(code=1)

resp = requests.post(
sequencer.node_rpc_url,
json=make_rpc_payload("admin_overrideLeader"),
)
resp.raise_for_status()
if "error" in resp.json():
print_error(
f"Failed to override sequencer leader status for {sequencer_id}: {resp.json()['error']}"
if not remove:
resp = requests.post(
sequencer.node_rpc_url,
json=make_rpc_payload("admin_overrideLeader"),
)
raise typer.Exit(code=1)
resp.raise_for_status()
if "error" in resp.json():
print_error(
f"Failed to override sequencer leader status for {sequencer_id}: {resp.json()['error']}"
)
raise typer.Exit(code=1)

typer.echo(f"Successfully overrode leader for {sequencer_id}")
typer.echo(f"Successfully overrode leader for {sequencer_id} to {not remove}")
if remove:
typer.echo("As you provided --remove, do remember to restart the op-node pod to remove the leadership-override from it.")


@app.command()
Expand Down Expand Up @@ -322,16 +341,62 @@ def update_cluster_membership(network: str):
if error:
raise typer.Exit(code=1)

@app.command()
def halt_sequencer(network: str, force: bool = False):
"""Halts the currently active sequencer."""
network_obj = get_network(network)
sequencers = network_obj.sequencers

all_conductors_paused = all(
[not sequencer.conductor_active for sequencer in sequencers]
)
if not all_conductors_paused and not force:
print_error(
f"Not all conductors were found to be paused. Please run the `pause` command or use --force to override this behaviour"
)
raise typer.Exit(code=1)

active_sequencer = network_obj.find_active_sequencer()
if active_sequencer is None:
print_error(f"Could not find an active sequencer in the network: {network}")
raise typer.Exit(code=1)

try:
resp = requests.post(
active_sequencer.node_rpc_url,
json=make_rpc_payload(
method="admin_stopSequencer",
params=[],
),
)
resp.raise_for_status()
if "error" in resp.json():
raise Exception(resp.json()["error"])
typer.echo(
f"Successfully halted the active sequencer: {active_sequencer.sequencer_id}"
)
except Exception as e:
print_warn(
f"Failed to halt the active sequencer: {active_sequencer.sequencer_id}"
)


@app.command()
def force_active_sequencer(network: str, sequencer_id: str):
def force_active_sequencer(network: str, sequencer_id: str, force: bool = False):
"""Forces a sequencer to become active using stop/start."""
network_obj = get_network(network)
sequencer = network_obj.get_sequencer_by_id(sequencer_id)
if sequencer is None:
typer.echo(f"sequencer ID {sequencer_id} not found in network {network}")
raise typer.Exit(code=1)

# Pre-flight check: Ensure all conductors are paused
sequencers = network_obj.sequencers
all_paused = all(not sequencer.conductor_active for sequencer in sequencers)
if not all_paused and not force:
print_error("Not all conductors are paused. Run 'pause' command first.")
raise typer.Exit(code=1)

hash = sequencer.unsafe_l2_hash

active_sequencer = network_obj.find_active_sequencer()
Expand Down Expand Up @@ -367,6 +432,5 @@ def force_active_sequencer(network: str, sequencer_id: str):

typer.echo(f"Successfully forced {sequencer_id} to become active")


if __name__ == "__main__":
app()
4 changes: 2 additions & 2 deletions op-signer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
FROM golang:1.21.3-alpine3.18 as builder
FROM golang:1.22.7-alpine3.20 as builder

COPY ./op-signer /app

WORKDIR /app
RUN apk --no-cache add make jq bash git alpine-sdk
RUN make build

FROM alpine:3.18
FROM alpine:3.20
RUN apk --no-cache add ca-certificates

RUN addgroup -S app && adduser -S app -G app
Expand Down
17 changes: 16 additions & 1 deletion op-signer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,26 @@ Signer service and client library

## Setup

Install go1.18
Install go1.21

```bash
make build

source .env.example # (or copy to .envrc if using direnv)
./bin/signer
```

## Configuring Cloud KMS
Modify the `config.yaml` file to connect op-signer with your cloud KMS.
- `name`: DNS name of the client connecting to op-signer. Must match the DNS name in the TLS certificate.
- `key`: key resource name of the Cloud KMS.

You can add a list of `name`/`key` to use different keys for each client connecting with op-signer.

## Testing with local tls
Running op-signer requires mTLS connection between the op-signer and the requesting server.

To test services in your local environment
1. run `./gen-local-tls.sh`
2. Check that `/tls` folder has been created with certificates and keys
2. Set the appropriate flags (`tls.cert`, `tls.key`, `tls.ca`) to the corresponding files under `/tls`
93 changes: 65 additions & 28 deletions op-signer/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/prometheus/client_golang/prometheus"
"github.com/urfave/cli/v2"

"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/log"
Expand Down Expand Up @@ -197,43 +198,79 @@ func MainAppAction(version string) cliapp.LifecycleAction {
}
}

func ClientSign(version string) func(cliCtx *cli.Context) error {
type SignActionType string

const (
SignTransaction SignActionType = "transaction"
SignBlockPayload SignActionType = "block_payload"
)

func ClientSign(version string, action SignActionType) func(cliCtx *cli.Context) error {
return func(cliCtx *cli.Context) error {
cfg := NewConfig(cliCtx)
if err := cfg.Check(); err != nil {
return fmt.Errorf("invalid CLI flags: %w", err)
}

l := oplog.NewLogger(os.Stdout, cfg.LogConfig)
log.Root().SetHandler(l.GetHandler())

txarg := cliCtx.Args().First()
if txarg == "" {
return errors.New("no transaction argument was provided")
}
txraw, err := hexutil.Decode(txarg)
if err != nil {
return errors.New("failed to decode transaction argument")
}

client, err := client.NewSignerClient(l, cfg.ClientEndpoint, cfg.TLSConfig)
if err != nil {
return err
}

tx := &types.Transaction{}
if err := tx.UnmarshalBinary(txraw); err != nil {
return fmt.Errorf("failed to unmarshal transaction argument: %w", err)
oplog.SetGlobalLogHandler(l.Handler())

switch action {
case SignTransaction:
txarg := cliCtx.Args().Get(0)
if txarg == "" {
return errors.New("no transaction argument was provided")
}
txraw, err := hexutil.Decode(txarg)
if err != nil {
return errors.New("failed to decode transaction argument")
}

client, err := client.NewSignerClient(l, cfg.ClientEndpoint, cfg.TLSConfig)
if err != nil {
return err
}

tx := &types.Transaction{}
if err := tx.UnmarshalBinary(txraw); err != nil {
return fmt.Errorf("failed to unmarshal transaction argument: %w", err)
}

tx, err = client.SignTransaction(context.Background(), tx)
if err != nil {
return err
}

result, _ := tx.MarshalJSON()
fmt.Println(string(result))

case SignBlockPayload:
blockPayloadHash := cliCtx.Args().Get(0)
if blockPayloadHash == "" {
return errors.New("no block payload argument was provided")
}

client, err := client.NewSignerClient(l, cfg.ClientEndpoint, cfg.TLSConfig)
if err != nil {
return err
}

signingHash := common.Hash{}
if err := signingHash.UnmarshalText([]byte(blockPayloadHash)); err != nil {
return fmt.Errorf("failed to unmarshal block payload argument: %w", err)
}

signature, err := client.SignBlockPayload(context.Background(), signingHash)
if err != nil {
return err
}

fmt.Println(string(signature[:]))

case "":
return errors.New("no action was provided")
}

tx, err = client.SignTransaction(context.Background(), tx)
if err != nil {
return err
}

result, _ := tx.MarshalJSON()
fmt.Println(string(result))

return nil
}
}
14 changes: 14 additions & 0 deletions op-signer/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"net/http"
"os"

"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/log"
Expand Down Expand Up @@ -89,3 +90,16 @@ func (s *SignerClient) SignTransaction(

return signed, nil
}

func (s *SignerClient) SignBlockPayload(
ctx context.Context,
signingHash common.Hash,
) ([]byte, error) {
var result []byte

if err := s.client.Call(&result, "eth_signBlockPayload", signingHash); err != nil {
return []byte{}, fmt.Errorf("eth_signTransaction failed: %w", err)
}

return result, nil
}
Loading