infra/op-signer/service/service.go
Sam Stokes 75b02dff3d
op-signer: add to this repo (#51)
* op-signer: add to this repo

* circleci: add op-signer jobs/workflows

* ops: update tag service to include op-signer

* readme: add op-signer one sentence description

* ci: add op-signer option to github action

* ops: add op-signer min version
2024-09-10 15:01:09 -04:00

149 lines
4.3 KiB
Go

package service
import (
"context"
"strings"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/prometheus/client_golang/prometheus"
"github.com/ethereum-optimism/infra/op-signer/service/provider"
oprpc "github.com/ethereum-optimism/optimism/op-service/rpc"
"github.com/ethereum-optimism/optimism/op-service/signer"
)
type SignerService struct {
logger log.Logger
config SignerServiceConfig
provider provider.SignatureProvider
}
func NewSignerService(logger log.Logger, config SignerServiceConfig) *SignerService {
return NewSignerServiceWithProvider(logger, config, provider.NewCloudKMSSignatureProvider(logger))
}
func NewSignerServiceWithProvider(
logger log.Logger,
config SignerServiceConfig,
provider provider.SignatureProvider,
) *SignerService {
return &SignerService{logger, config, provider}
}
func (s *SignerService) RegisterAPIs(server *oprpc.Server) {
server.AddAPI(rpc.API{
Namespace: "eth",
Service: s,
})
}
func containsNormalized(s []string, e string) bool {
for _, a := range s {
if strings.EqualFold(a, e) {
return true
}
}
return false
}
// SignTransaction will sign the given transaction with the key configured for the authenticated client
func (s *SignerService) SignTransaction(ctx context.Context, args signer.TransactionArgs) (hexutil.Bytes, error) {
clientInfo := ClientInfoFromContext(ctx)
authConfig, err := s.config.GetAuthConfigForClient(clientInfo.ClientName)
if err != nil {
return nil, rpc.HTTPError{StatusCode: 403, Status: "Forbidden", Body: []byte(err.Error())}
}
labels := prometheus.Labels{"client": clientInfo.ClientName, "status": "error", "error": ""}
defer func() {
MetricSignTransactionTotal.With(labels).Inc()
}()
if err := args.Check(); err != nil {
s.logger.Warn("invalid signing arguments", "err", err)
labels["error"] = "invalid_transaction"
return nil, &InvalidTransactionError{message: err.Error()}
}
if len(authConfig.ToAddresses) > 0 && !containsNormalized(authConfig.ToAddresses, args.To.Hex()) {
return nil, &UnauthorizedTransactionError{"to address not authorized"}
}
if len(authConfig.MaxValue) > 0 && args.Value.ToInt().Cmp(authConfig.MaxValueToInt()) > 0 {
return nil, &UnauthorizedTransactionError{"value exceeds maximum"}
}
txData, err := args.ToTransactionData()
if err != nil {
labels["error"] = "transaction_args_error"
return nil, &InvalidTransactionError{err.Error()}
}
tx := types.NewTx(txData)
txSigner := types.LatestSignerForChainID(tx.ChainId())
digest := txSigner.Hash(tx)
signature, err := s.provider.SignDigest(ctx, authConfig.KeyName, digest.Bytes())
if err != nil {
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{err.Error()}
}
signed, err := tx.WithSignature(txSigner, signature)
if err != nil {
labels["error"] = "invalid_transaction_error"
return nil, &InvalidTransactionError{err.Error()}
}
signerFrom, err := txSigner.Sender(signed)
if err != nil {
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{err.Error()}
}
// sanity check that we used the right account
if args.From != nil && *args.From != signerFrom {
s.logger.Warn("user is trying to sign with different account than actual signer-provider",
"provider", signerFrom, "request", *args.From)
labels["error"] = "sign_error"
return nil, &InvalidTransactionError{"unexpected from address"}
}
txraw, err := signed.MarshalBinary()
if err != nil {
labels["error"] = "transaction_marshal_error"
return nil, &InvalidTransactionError{err.Error()}
}
labels["status"] = "success"
txTo := ""
if tx.To() != nil {
txTo = tx.To().Hex()
}
s.logger.Info(
"Signed transaction",
"digest", hexutil.Encode(digest.Bytes()),
"client.name", clientInfo.ClientName,
"client.keyname", authConfig.KeyName,
"tx.type", tx.Type(),
"tx.raw", hexutil.Encode(txraw),
"tx.value", tx.Value(),
"tx.to", txTo,
"tx.nonce", tx.Nonce(),
"tx.gas", tx.Gas(),
"tx.gasprice", tx.GasPrice(),
"tx.gastipcap", tx.GasTipCap(),
"tx.gasfeecap", tx.GasFeeCap(),
"tx.type", tx.Type(),
"tx.hash", tx.Hash().Hex(),
"tx.chainid", tx.ChainId(),
"tx.blobhashes", tx.BlobHashes(),
"tx.blobfeecap", tx.BlobGasFeeCap(),
"signature", hexutil.Encode(signature),
)
return hexutil.Bytes(txraw), nil
}