diff --git a/common/testcontainers/testcontainers.go b/common/testcontainers/testcontainers.go index 3a042a9c6..7adcd6eee 100644 --- a/common/testcontainers/testcontainers.go +++ b/common/testcontainers/testcontainers.go @@ -21,9 +21,10 @@ import ( // TestcontainerApps testcontainers struct type TestcontainerApps struct { - postgresContainer *postgres.PostgresContainer - l2GethContainer *testcontainers.DockerContainer - poSL1Container compose.ComposeStack + postgresContainer *postgres.PostgresContainer + l2GethContainer *testcontainers.DockerContainer + poSL1Container compose.ComposeStack + web3SignerContainer *testcontainers.DockerContainer // common time stamp in nanoseconds. Timestamp int @@ -112,6 +113,45 @@ func (t *TestcontainerApps) StartPoSL1Container() error { return nil } +func (t *TestcontainerApps) StartWeb3SignerContainer(chainId int) error { + if t.web3SignerContainer != nil && t.web3SignerContainer.IsRunning() { + return nil + } + var ( + err error + rootDir string + ) + if rootDir, err = findProjectRootDir(); err != nil { + return fmt.Errorf("failed to find project root directory: %v", err) + } + web3SignerConfDir := filepath.Join(rootDir, "common", "testcontainers", "web3signerconf") + + req := testcontainers.ContainerRequest{ + Image: "consensys/web3signer:develop", + ExposedPorts: []string{"9000/tcp"}, + Cmd: []string{"--key-config-path", "/web3signerconf/", "eth1", "--chain-id", fmt.Sprintf("%d", chainId)}, + Files: []testcontainers.ContainerFile{ + { + HostFilePath: web3SignerConfDir, + ContainerFilePath: "/", + FileMode: 0o777, + }, + }, + WaitingFor: wait.ForLog("ready to handle signing requests"), + } + genericContainerReq := testcontainers.GenericContainerRequest{ + ContainerRequest: req, + Started: true, + } + container, err := testcontainers.GenericContainer(context.Background(), genericContainerReq) + if err != nil { + log.Printf("failed to start web3signer container: %s", err) + return err + } + t.web3SignerContainer, _ = container.(*testcontainers.DockerContainer) + return nil +} + // GetPoSL1EndPoint returns the endpoint of the running PoS L1 endpoint func (t *TestcontainerApps) GetPoSL1EndPoint() (string, error) { if t.poSL1Container == nil { @@ -153,6 +193,14 @@ func (t *TestcontainerApps) GetL2GethEndPoint() (string, error) { return endpoint, nil } +// GetL2GethEndPoint returns the endpoint of the running L2Geth container +func (t *TestcontainerApps) GetWeb3SignerEndpoint() (string, error) { + if t.web3SignerContainer == nil || !t.web3SignerContainer.IsRunning() { + return "", errors.New("web3signer is not running") + } + return t.web3SignerContainer.PortEndpoint(context.Background(), "9000/tcp", "http") +} + // GetGormDBClient returns a gorm.DB by connecting to the running postgres container func (t *TestcontainerApps) GetGormDBClient() (*gorm.DB, error) { endpoint, err := t.GetDBEndPoint() @@ -201,6 +249,11 @@ func (t *TestcontainerApps) Free() { t.poSL1Container = nil } } + if t.web3SignerContainer != nil && t.web3SignerContainer.IsRunning() { + if err := t.web3SignerContainer.Terminate(ctx); err != nil { + log.Printf("failed to stop web3signer container: %s", err) + } + } } // findProjectRootDir find project root directory diff --git a/common/testcontainers/web3signerconf/keyconf.yaml b/common/testcontainers/web3signerconf/keyconf.yaml new file mode 100644 index 000000000..1a8ec9675 --- /dev/null +++ b/common/testcontainers/web3signerconf/keyconf.yaml @@ -0,0 +1,3 @@ +type: "file-raw" +keyType: "SECP256K1" +privateKey: "0x1212121212121212121212121212121212121212121212121212121212121212" \ No newline at end of file diff --git a/rollup/internal/controller/sender/sender_test.go b/rollup/internal/controller/sender/sender_test.go index 98719513d..25e96a961 100644 --- a/rollup/internal/controller/sender/sender_test.go +++ b/rollup/internal/controller/sender/sender_test.go @@ -55,6 +55,9 @@ func TestMain(m *testing.M) { if testApps != nil { testApps.Free() } + if testAppsSignerTest != nil { + testAppsSignerTest.Free() + } }() m.Run() } diff --git a/rollup/internal/controller/sender/transaction_signer.go b/rollup/internal/controller/sender/transaction_signer.go index 3905fc019..248f434e7 100644 --- a/rollup/internal/controller/sender/transaction_signer.go +++ b/rollup/internal/controller/sender/transaction_signer.go @@ -116,11 +116,11 @@ type RpcTransaction struct { To *common.Address `json:"to"` Gas uint64 `json:"gas"` GasPrice *big.Int `json:"gasPrice,omitempty"` - MaxPriorityFeePerGas *big.Int `json:"MaxPriorityFeePerGas,omitempty"` + MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas,omitempty"` MaxFeePerGas *big.Int `json:"maxFeePerGas,omitempty"` Nonce uint64 `json:"nonce"` Value *big.Int `json:"value"` - Data []byte `json:"data"` + Data string `json:"data"` } func txDataToRpcTx(from *common.Address, tx *gethTypes.Transaction) (*RpcTransaction, error) { @@ -133,7 +133,7 @@ func txDataToRpcTx(from *common.Address, tx *gethTypes.Transaction) (*RpcTransac GasPrice: tx.GasPrice(), Nonce: tx.Nonce(), Value: tx.Value(), - Data: tx.Data(), + Data: common.Bytes2Hex(tx.Data()), }, nil case gethTypes.DynamicFeeTxType: return &RpcTransaction{ @@ -144,7 +144,7 @@ func txDataToRpcTx(from *common.Address, tx *gethTypes.Transaction) (*RpcTransac MaxFeePerGas: tx.GasFeeCap(), Nonce: tx.Nonce(), Value: tx.Value(), - Data: tx.Data(), + Data: common.Bytes2Hex(tx.Data()), }, nil default: // other tx types (BlobTx) currently not supported by web3signer return nil, fmt.Errorf("failed to convert tx to RpcTransaction, unsupported tx type, %d", tx.Type()) diff --git a/rollup/internal/controller/sender/transaction_signer_test.go b/rollup/internal/controller/sender/transaction_signer_test.go new file mode 100644 index 000000000..74464db0e --- /dev/null +++ b/rollup/internal/controller/sender/transaction_signer_test.go @@ -0,0 +1,114 @@ +package sender + +import ( + "context" + "math/big" + "os" + "scroll-tech/common/testcontainers" + "scroll-tech/rollup/internal/config" + "testing" + + "github.com/holiman/uint256" + "github.com/scroll-tech/go-ethereum/common" + gethTypes "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/log" + "github.com/stretchr/testify/assert" +) + +var ( + testAppsSignerTest *testcontainers.TestcontainerApps + chainId int +) + +func setupEnvSignerTest(t *testing.T) { + glogger := log.NewGlogHandler(log.StreamHandler(os.Stderr, log.LogfmtFormat())) + glogger.Verbosity(log.LvlInfo) + log.Root().SetHandler(glogger) + + chainId = 1 + testAppsSignerTest = testcontainers.NewTestcontainerApps() + assert.NoError(t, testAppsSignerTest.StartWeb3SignerContainer(chainId)) +} + +func TestTransactionSigner(t *testing.T) { + setupEnvSignerTest(t) + t.Run("test both signer types", testBothSignerTypes) +} + +func testBothSignerTypes(t *testing.T) { + endpoint, err := testAppsSignerTest.GetWeb3SignerEndpoint() + assert.NoError(t, err) + + // create remote signer + remoteSignerConf := &config.SignerConfig{ + SignerType: RemoteSignerSignerType, + SignerAddress: "0x1C5A77d9FA7eF466951B2F01F724BCa3A5820b63", + RemoteSignerUrl: endpoint, + } + remoteSigner, err := NewTransactionSigner(remoteSignerConf, big.NewInt(int64(chainId))) + assert.NoError(t, err) + remoteSigner.SetNonce(2) + + // create private key signer + privateKeySignerConf := &config.SignerConfig{ + SignerType: PrivateKeySignerType, + PrivateKey: "1212121212121212121212121212121212121212121212121212121212121212", + } + privateKeySigner, err := NewTransactionSigner(privateKeySignerConf, big.NewInt(int64(chainId))) + assert.NoError(t, err) + privateKeySigner.SetNonce(2) + + assert.Equal(t, remoteSigner.GetAddr(), privateKeySigner.GetAddr()) + + to := common.BytesToAddress([]byte{0, 1, 2, 3}) + data := []byte("data") + + // check LegacyTx and DynamicFeeTx - transactions supported by web3signer + txDatas := []gethTypes.TxData{ + &gethTypes.LegacyTx{ + Nonce: remoteSigner.GetNonce(), + GasPrice: big.NewInt(1000), + Gas: 10000, + To: &to, + Data: data, + }, + &gethTypes.DynamicFeeTx{ + Nonce: remoteSigner.GetNonce(), + Gas: 10000, + To: &to, + Data: data, + ChainID: big.NewInt(int64(chainId)), + GasTipCap: big.NewInt(2000), + GasFeeCap: big.NewInt(3000), + }, + } + for _, txData := range txDatas { + tx := gethTypes.NewTx(txData) + + signedTx1, err := remoteSigner.SignTransaction(context.Background(), tx) + assert.NoError(t, err) + + signedTx2, err := privateKeySigner.SignTransaction(context.Background(), tx) + assert.NoError(t, err) + + assert.Equal(t, signedTx1.Hash(), signedTx2.Hash()) + } + + // BlobTx is not supported + txData := &gethTypes.BlobTx{ + Nonce: remoteSigner.GetNonce(), + Gas: 10000, + To: to, + Data: data, + ChainID: uint256.NewInt(1), + GasTipCap: uint256.NewInt(2000), + GasFeeCap: uint256.NewInt(3000), + BlobFeeCap: uint256.NewInt(1), + BlobHashes: []common.Hash{}, + Sidecar: nil, + } + tx := gethTypes.NewTx(txData) + + _, err = remoteSigner.SignTransaction(context.Background(), tx) + assert.Error(t, err) +}