75b02dff3d
* 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
149 lines
4.3 KiB
Go
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
|
|
}
|