Skip to content

Commit 3131782

Browse files
committed
fix(kms): make the AWS KMS signer support dynamic-fee transactions
1 parent deb34e0 commit 3131782

7 files changed

Lines changed: 263 additions & 87 deletions

File tree

Makefile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -553,14 +553,15 @@ check-license: ## Verify license headers on Go source files
553553
# Discovery (integration-test-shard-check) lists tests with a plain Go
554554
# toolchain, so the integration package must stay free of the Cartesi CGo
555555
# dependency for the check to build on the CI setup runner.
556-
INTEGRATION_SHARDS := basic quorum prt replay restart withdrawal
556+
INTEGRATION_SHARDS := basic quorum prt replay restart withdrawal awskms
557557

558558
INTEGRATION_SHARD_basic := ^Test(EchoAuthority|RejectException|MultiApp|EchoAuthorityStaging)$$
559559
INTEGRATION_SHARD_quorum := ^Test(EchoQuorum|SameBlockInputs)$$
560560
INTEGRATION_SHARD_prt := ^Test(EchoPrt|RejectExceptionPrt|ForeclosePrt)$$
561561
INTEGRATION_SHARD_replay := ^Test(Foreclose|ForecloseReplay|DivergentClaim)$$
562562
INTEGRATION_SHARD_restart := ^Test(Restart|SnapshotPolicy)$$
563563
INTEGRATION_SHARD_withdrawal := ^TestWithdrawalLifecycle$$
564+
INTEGRATION_SHARD_awskms := ^TestLocalStackAWSIntegration$$
564565

565566
# -----------------------------------------------------------------------------
566567
# Node topology axis — orthogonal to shards.

internal/config/auth/auth.go

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package auth
55

66
import (
77
"context"
8+
"errors"
89
"fmt"
910
"math/big"
1011

@@ -16,6 +17,7 @@ import (
1617
aws_cfg "github.com/aws/aws-sdk-go-v2/config"
1718
aws_kms "github.com/aws/aws-sdk-go-v2/service/kms"
1819

20+
"github.com/cartesi/rollups-node/internal/config"
1921
. "github.com/cartesi/rollups-node/internal/config"
2022
signtx "github.com/cartesi/rollups-node/internal/kms"
2123
"github.com/cartesi/rollups-node/pkg/ethutil"
@@ -60,20 +62,35 @@ func GetTransactOptsFactory(ctx context.Context, chainId *big.Int) (ethutil.Tran
6062
}
6163
return ethutil.NewStaticTransactOptsFactory(txOpts), nil
6264
case AuthKindAWS:
63-
awsc, err := aws_cfg.LoadDefaultConfig(ctx)
65+
keyId, err := GetAuthAwsKmsKeyId()
6466
if err != nil {
6567
return nil, err
6668
}
67-
kmsConfig := aws_kms.NewFromConfig(awsc)
68-
authAwsKmsKeyId, err := GetAuthAwsKmsKeyId()
69+
awsOpts := make([]func (*aws_cfg.LoadOptions) error, 0, 2)
70+
kmsRegion, err := GetAuthAwsKmsRegion()
71+
if !errors.Is(err, config.ErrNotDefined) {
72+
if err != nil {
73+
return nil, err
74+
}
75+
awsOpts = append(awsOpts, aws_cfg.WithRegion(kmsRegion.Value))
76+
}
77+
kmsEndpoint, err := GetAuthAwsKmsEndpoint()
78+
if !errors.Is(err, config.ErrNotDefined) {
79+
if err != nil {
80+
return nil, err
81+
}
82+
awsOpts = append(awsOpts, aws_cfg.WithBaseEndpoint(kmsEndpoint.Value))
83+
}
84+
awsCfg, err := aws_cfg.LoadDefaultConfig(ctx, awsOpts...)
6985
if err != nil {
7086
return nil, err
7187
}
88+
kmsClient := aws_kms.NewFromConfig(awsCfg)
7289
return signtx.CreateAWSTransactOptsFactory(
7390
ctx,
74-
kmsConfig,
75-
aws.String(authAwsKmsKeyId.Value),
76-
types.NewEIP155Signer(chainId),
91+
kmsClient,
92+
aws.String(keyId.Value),
93+
types.LatestSignerForChainID(chainId),
7794
)
7895
default:
7996
return nil, fmt.Errorf("no valid authentication method found")

internal/config/generate/Config.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,15 @@ Must be set alongside `CARTESI_AUTH_AWS_KMS_KEY_ID`."""
375375
omit = true
376376
used-by = ["claimer", "node", "cli", "prt"]
377377

378+
[auth.CARTESI_AUTH_AWS_KMS_ENDPOINT]
379+
go-type = "RedactedString"
380+
description = """
381+
An AWS KMS Endpoint.
382+
383+
When not provided, the default endpoint for the AWS region defined by `CARTESI_AUTH_AWS_KMS_REGION` is automatically used."""
384+
omit = true
385+
used-by = ["claimer", "node", "cli", "prt"]
386+
378387
#
379388
# Database
380389
#

internal/config/generated.go

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/kms/signtx_test.go

Lines changed: 31 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -11,95 +11,15 @@ import (
1111
"math/big"
1212
"testing"
1313

14-
"github.com/cartesi/rollups-node/pkg/ethutil"
15-
1614
"github.com/ethereum/go-ethereum/common"
1715
ethtypes "github.com/ethereum/go-ethereum/core/types"
1816
"github.com/ethereum/go-ethereum/crypto"
19-
"github.com/ethereum/go-ethereum/ethclient"
2017

21-
awscfg "github.com/aws/aws-sdk-go-v2/config"
2218
awskms "github.com/aws/aws-sdk-go-v2/service/kms"
2319
kmstypes "github.com/aws/aws-sdk-go-v2/service/kms/types"
2420
"github.com/stretchr/testify/require"
2521
)
2622

27-
var ARN = ""
28-
29-
/* Create a SignTxFn from a private key. Useful for testing */
30-
func CreateSignTxFnFromPrivateKey(privateKey *ecdsa.PrivateKey) SignTxFn {
31-
return func(_ context.Context, tx *ethtypes.Transaction, s ethtypes.Signer) (*ethtypes.Transaction, error) {
32-
return ethtypes.SignTx(tx, s, privateKey)
33-
}
34-
}
35-
36-
func sendFunds(
37-
value *big.Int,
38-
SignTx SignTxFn,
39-
ctx context.Context,
40-
sender common.Address,
41-
recipient common.Address,
42-
) {
43-
client, err := ethclient.Dial("http://127.0.0.1:8545") // anvil
44-
if err != nil {
45-
panic(err)
46-
}
47-
48-
nonce, err := client.PendingNonceAt(context.Background(), sender)
49-
if err != nil {
50-
panic(err)
51-
}
52-
gasLimit := uint64(21000)
53-
gasPrice, err := client.SuggestGasPrice(ctx)
54-
if err != nil {
55-
panic(err)
56-
}
57-
var data []byte
58-
tx := ethtypes.NewTransaction(nonce, recipient, value, gasLimit, gasPrice, data)
59-
chainID, err := client.NetworkID(context.Background())
60-
if err != nil {
61-
panic(err)
62-
}
63-
signedTx, err := SignTx(ctx, tx, ethtypes.NewEIP155Signer(chainID))
64-
if err != nil {
65-
panic(err)
66-
}
67-
err = client.SendTransaction(context.Background(), signedTx)
68-
if err != nil {
69-
panic(err)
70-
}
71-
}
72-
73-
func TestSignTx(t *testing.T) {
74-
if len(ARN) == 0 {
75-
t.Skip("Skipping test, ARN for KMS key is unset")
76-
}
77-
value20 := big.NewInt(2000000000000000000) // in wei (2 eth)
78-
value10 := big.NewInt(1000000000000000000) // in wei (1 eth)
79-
80-
anvilPrivateKey, err := ethutil.MnemonicToPrivateKey(ethutil.FoundryMnemonic, 0)
81-
if err != nil {
82-
panic(err)
83-
}
84-
anvilPublicKey := anvilPrivateKey.Public().(*ecdsa.PublicKey)
85-
anvilAddress := crypto.PubkeyToAddress(*anvilPublicKey)
86-
87-
config, err := awscfg.LoadDefaultConfig(context.Background())
88-
if err != nil {
89-
panic(err)
90-
}
91-
kms := awskms.NewFromConfig(config)
92-
SignTx, _, KMSAddress, err := CreateAWSSignTxFn(context.Background(), kms, &ARN)
93-
if err != nil {
94-
panic(err)
95-
}
96-
97-
sendFunds(value20, CreateSignTxFnFromPrivateKey(anvilPrivateKey),
98-
context.Background(), anvilAddress, KMSAddress)
99-
sendFunds(value10, SignTx,
100-
context.Background(), KMSAddress, anvilAddress)
101-
}
102-
10323
func TestAWSTransactOptsFactorySignsWithSubmitContext(t *testing.T) {
10424
privateKey, err := crypto.GenerateKey()
10525
require.NoError(t, err)
@@ -128,6 +48,37 @@ func TestAWSTransactOptsFactorySignsWithSubmitContext(t *testing.T) {
12848
require.NoError(t, client.signContext.Err())
12949
}
13050

51+
func TestAWSTransactOptsFactorySignsDynamicFeeTransaction(t *testing.T) {
52+
privateKey, err := crypto.GenerateKey()
53+
require.NoError(t, err)
54+
55+
chainID := big.NewInt(31337)
56+
client := newFakeKMSClient(t, privateKey)
57+
keyID := "alias/test-key"
58+
factory, err := CreateAWSTransactOptsFactory(
59+
context.Background(), client, &keyID, ethtypes.LatestSignerForChainID(chainID),
60+
)
61+
require.NoError(t, err)
62+
63+
opts, err := factory.NewTransactOpts(context.Background())
64+
require.NoError(t, err)
65+
tx := ethtypes.NewTx(&ethtypes.DynamicFeeTx{
66+
ChainID: chainID,
67+
Nonce: 1,
68+
GasTipCap: big.NewInt(1),
69+
GasFeeCap: big.NewInt(2),
70+
Gas: 21000,
71+
To: &common.Address{0x01},
72+
Value: big.NewInt(3),
73+
})
74+
signed, err := opts.Signer(opts.From, tx)
75+
require.NoError(t, err)
76+
77+
sender, err := ethtypes.Sender(ethtypes.LatestSignerForChainID(chainID), signed)
78+
require.NoError(t, err)
79+
require.Equal(t, crypto.PubkeyToAddress(privateKey.PublicKey), sender)
80+
}
81+
13182
type fakeKMSClient struct {
13283
t *testing.T
13384
privateKey *ecdsa.PrivateKey

test/compose/compose.integration.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,18 @@ services:
8383
<<: *env
8484
restart: "no"
8585

86+
localstack:
87+
image: localstack/localstack:4.14.0
88+
networks:
89+
- devnet
90+
healthcheck:
91+
test: ["CMD", "curl", "-fsS", "http://localhost:4566/_localstack/health"]
92+
interval: 2s
93+
timeout: 2s
94+
retries: 30
95+
environment:
96+
SERVICES: kms
97+
8698
# The node is started and managed by TestMain inside the test process.
8799
# This ensures all tests (including restart and snapshot policy tests)
88100
# run with the same infrastructure in both local and CI environments.
@@ -99,6 +111,8 @@ services:
99111
condition: service_healthy
100112
dapp-builder:
101113
condition: service_completed_successfully
114+
localstack:
115+
condition: service_healthy
102116
volumes:
103117
- dapp_images:/var/lib/cartesi-rollups-node/dapps:ro
104118
- node_logs:/var/lib/cartesi-rollups-node/logs
@@ -125,6 +139,12 @@ services:
125139
CARTESI_TEST_ERC20_WITHDRAWAL_DAPP_PATH: /var/lib/cartesi-rollups-node/dapps/erc20-withdrawal-dapp
126140
CARTESI_TEST_NODE_LOG_FILE: /var/lib/cartesi-rollups-node/logs/node.log
127141
CARTESI_INSPECT_URL: http://localhost:10012/
142+
# test/integration/localstack_integration_test.go
143+
AWS_ACCESS_KEY_ID: test
144+
AWS_SECRET_ACCESS_KEY: test
145+
AWS_REGION: us-east-1
146+
LOCALSTACK_KMS_ENDPOINT: http://localstack:4566
147+
LOCALSTACK_KMS_REQUIRED: "true"
128148

129149
volumes:
130150
dapp_images:

0 commit comments

Comments
 (0)