From 58507267fac7fcc8393af403e4e4f1e713d631da Mon Sep 17 00:00:00 2001 From: "Dr. Q and Company" Date: Thu, 12 Mar 2026 13:31:19 -0400 Subject: [PATCH] Revert "cmd/pushtx, .github/workflows: add raw transaction broadcast tool and build CI" (#2) --- .github/workflows/build.yml | 40 ----- .gitignore | 1 - Makefile | 6 - cmd/pushtx/main.go | 204 ---------------------- cmd/pushtx/main_test.go | 339 ------------------------------------ 5 files changed, 590 deletions(-) delete mode 100644 .github/workflows/build.yml delete mode 100644 cmd/pushtx/main.go delete mode 100644 cmd/pushtx/main_test.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 37a41593cc..0000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Build - -on: - push: - branches: - - master - - copilot/** - pull_request: - branches: - - master - workflow_dispatch: - -permissions: - contents: read - -jobs: - build: - name: Build All - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: false - - - uses: actions/cache@v4 - with: - path: build/cache - key: ${{ runner.os }}-build-tools-cache-${{ hashFiles('build/checksums.txt') }} - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: '1.24' - cache: false - - - name: Build all commands - run: make all - - - name: Run pushtx tests - run: go test ./cmd/pushtx/ -v diff --git a/.gitignore b/.gitignore index 9602bff893..293359a669 100644 --- a/.gitignore +++ b/.gitignore @@ -57,4 +57,3 @@ cmd/geth/geth cmd/rlpdump/rlpdump cmd/workload/workload cmd/keeper/keeper -cmd/pushtx/pushtx diff --git a/Makefile b/Makefile index b7cb3bc38b..f3d7f48f2f 100644 --- a/Makefile +++ b/Makefile @@ -14,12 +14,6 @@ geth: @echo "Done building." @echo "Run \"$(GOBIN)/geth\" to launch geth." -#? pushtx: Build pushtx. -pushtx: - $(GORUN) build/ci.go install ./cmd/pushtx - @echo "Done building." - @echo "Run \"$(GOBIN)/pushtx\" to launch pushtx." - #? evm: Build evm. evm: $(GORUN) build/ci.go install ./cmd/evm diff --git a/cmd/pushtx/main.go b/cmd/pushtx/main.go deleted file mode 100644 index bc0953c615..0000000000 --- a/cmd/pushtx/main.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright 2025 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// pushtx submits a raw signed transaction to an Ethereum JSON-RPC endpoint. -package main - -import ( - "context" - "encoding/hex" - "fmt" - "io" - "math/big" - "os" - "strings" - - "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/rpc" -) - -const defaultRPCURL = "http://127.0.0.1:8545" - -func main() { - if err := run(os.Args[1:], os.Stdin); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -func run(args []string, stdin io.Reader) error { - var ( - rpcURL string - txHex string - ) - // Parse flags manually so the tool stays minimal. - for i := 0; i < len(args); i++ { - switch { - case args[i] == "--rpc" || args[i] == "-rpc": - i++ - if i >= len(args) { - return fmt.Errorf("missing value for %s", args[i-1]) - } - rpcURL = args[i] - case strings.HasPrefix(args[i], "--rpc="): - rpcURL = strings.TrimPrefix(args[i], "--rpc=") - case strings.HasPrefix(args[i], "-rpc="): - rpcURL = strings.TrimPrefix(args[i], "-rpc=") - case args[i] == "-h" || args[i] == "--help": - printUsage() - return nil - case strings.HasPrefix(args[i], "-"): - return fmt.Errorf("unknown flag: %s", args[i]) - default: - if txHex != "" { - return fmt.Errorf("unexpected argument: %s", args[i]) - } - txHex = args[i] - } - } - - if rpcURL == "" { - rpcURL = defaultRPCURL - } - - // Read transaction hex from stdin when no positional argument is given. - if txHex == "" { - data, err := io.ReadAll(stdin) - if err != nil { - return fmt.Errorf("reading stdin: %w", err) - } - txHex = strings.TrimSpace(string(data)) - } - if txHex == "" { - return fmt.Errorf("no transaction data provided (see --help for usage)") - } - - rawTx, err := hex.DecodeString(strings.TrimPrefix(txHex, "0x")) - if err != nil { - return fmt.Errorf("invalid hex data: %w", err) - } - // Normalize to 0x-prefixed form for consistent output. - txHex = "0x" + hex.EncodeToString(rawTx) - - // Decode the transaction so we can display a summary. - var tx types.Transaction - if err := tx.UnmarshalBinary(rawTx); err != nil { - return fmt.Errorf("decoding transaction: %w", err) - } - printTxSummary(&tx) - - // Send to the RPC endpoint. - hash, err := sendRawTransaction(rpcURL, rawTx) - if err != nil { - // Still print the raw hex so the user can submit it elsewhere - // (e.g. etherscan.io/pushTx). - fmt.Println("Raw tx:", txHex) - return fmt.Errorf("sending transaction: %w", err) - } - fmt.Println("Transaction submitted successfully") - fmt.Println("Hash:", hash.Hex()) - - // Print the raw hex transaction as the last output for easy - // copy-paste into block explorers like etherscan.io/pushTx. - fmt.Println("Raw tx:", txHex) - return nil -} - -// sendRawTransaction dials the given RPC endpoint and calls -// eth_sendRawTransaction with the provided raw bytes. -func sendRawTransaction(rpcURL string, rawTx []byte) (common.Hash, error) { - client, err := rpc.Dial(rpcURL) - if err != nil { - return common.Hash{}, fmt.Errorf("connecting to %s: %w", rpcURL, err) - } - defer client.Close() - - var hash common.Hash - err = client.CallContext(context.Background(), &hash, "eth_sendRawTransaction", hexutil.Encode(rawTx)) - if err != nil { - return common.Hash{}, err - } - return hash, nil -} - -// printTxSummary displays the decoded transaction details to stdout. -func printTxSummary(tx *types.Transaction) { - signer := types.LatestSignerForChainID(tx.ChainId()) - from, err := types.Sender(signer, tx) - if err != nil { - from = common.Address{} - } - - fmt.Println("Transaction details:") - fmt.Println(" Type: ", tx.Type()) - fmt.Println(" From: ", from.Hex()) - if tx.To() != nil { - fmt.Println(" To: ", tx.To().Hex()) - } else { - fmt.Println(" To: (contract creation)") - } - fmt.Println(" Nonce: ", tx.Nonce()) - fmt.Println(" Value: ", formatWei(tx.Value())) - fmt.Println(" Gas limit:", tx.Gas()) - fmt.Println(" Gas price:", formatGwei(tx.GasPrice())) - fmt.Println(" Tx cost: ", formatWei(txCost(tx))) - fmt.Println(" Chain ID: ", tx.ChainId()) -} - -// txCost returns value + gas * gasPrice, i.e. the total ETH the sender -// must hold for the transaction to be accepted by the network. -func txCost(tx *types.Transaction) *big.Int { - gasCost := new(big.Int).Mul(new(big.Int).SetUint64(tx.Gas()), tx.GasPrice()) - return new(big.Int).Add(tx.Value(), gasCost) -} - -// formatWei converts a wei amount to a human-readable string showing -// both the wei value and the ETH equivalent. -func formatWei(wei *big.Int) string { - if wei == nil || wei.Sign() == 0 { - return "0 wei (0 ETH)" - } - ether := new(big.Float).Quo(new(big.Float).SetInt(wei), new(big.Float).SetFloat64(1e18)) - return fmt.Sprintf("%s wei (%s ETH)", wei.String(), ether.Text('f', 18)) -} - -// formatGwei converts a wei gas price to a human-readable string in Gwei. -func formatGwei(wei *big.Int) string { - if wei == nil || wei.Sign() == 0 { - return "0 wei (0 Gwei)" - } - gwei := new(big.Float).Quo(new(big.Float).SetInt(wei), new(big.Float).SetFloat64(1e9)) - return fmt.Sprintf("%s wei (%s Gwei)", wei.String(), gwei.Text('f', 9)) -} - -func printUsage() { - fmt.Fprintf(os.Stderr, `Usage: pushtx [--rpc URL] - -Submit a raw signed Ethereum transaction to a JSON-RPC endpoint. - -The transaction data can be provided as a positional argument or via stdin. - -Options: - --rpc URL JSON-RPC endpoint (default: %s) - -h, --help Show this help message - -Examples: - pushtx --rpc http://localhost:8545 0xf86c... - echo 0xf86c... | pushtx --rpc http://localhost:8545 -`, defaultRPCURL) -} diff --git a/cmd/pushtx/main_test.go b/cmd/pushtx/main_test.go deleted file mode 100644 index f5b6c0eb78..0000000000 --- a/cmd/pushtx/main_test.go +++ /dev/null @@ -1,339 +0,0 @@ -// Copyright 2025 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "crypto/ecdsa" - "encoding/json" - "io" - "math/big" - "net/http" - "net/http/httptest" - "os" - "strings" - "testing" - - "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/crypto" -) - -// signedTestTx returns a signed legacy transaction and its hex encoding. -func signedTestTx(t *testing.T) (*types.Transaction, string) { - t.Helper() - - key, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - return signedTestTxWithKey(t, key) -} - -func signedTestTxWithKey(t *testing.T, key *ecdsa.PrivateKey) (*types.Transaction, string) { - t.Helper() - - tx := types.NewTx(&types.LegacyTx{ - Nonce: 6, - GasPrice: big.NewInt(1_000_000_000), // 1 Gwei – real networks reject gas price 0 - Gas: 21055, - To: addrPtr(common.HexToAddress("0x78b5290269740033b05bd8d71c97331295eb5918")), - Value: new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)), // 10 ETH - }) - signer := types.NewEIP155Signer(big.NewInt(1)) - signed, err := types.SignTx(tx, signer, key) - if err != nil { - t.Fatal(err) - } - data, err := signed.MarshalBinary() - if err != nil { - t.Fatal(err) - } - return signed, hexutil.Encode(data) -} - -func addrPtr(a common.Address) *common.Address { return &a } - -// fakeRPC starts an HTTP server that responds to eth_sendRawTransaction. -func fakeRPC(t *testing.T, wantErr bool) *httptest.Server { - t.Helper() - - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var req struct { - Method string `json:"method"` - Params []json.RawMessage `json:"params"` - ID json.RawMessage `json:"id"` - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - if req.Method != "eth_sendRawTransaction" { - json.NewEncoder(w).Encode(map[string]interface{}{ - "jsonrpc": "2.0", - "id": req.ID, - "error": map[string]interface{}{"code": -32601, "message": "method not found"}, - }) - return - } - if wantErr { - json.NewEncoder(w).Encode(map[string]interface{}{ - "jsonrpc": "2.0", - "id": req.ID, - "error": map[string]interface{}{"code": -32000, "message": "already known"}, - }) - return - } - // Decode the raw tx to return its hash as the result. - var hexData string - if err := json.Unmarshal(req.Params[0], &hexData); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - rawBytes, err := hexutil.Decode(hexData) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - var tx types.Transaction - if err := tx.UnmarshalBinary(rawBytes); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - json.NewEncoder(w).Encode(map[string]interface{}{ - "jsonrpc": "2.0", - "id": req.ID, - "result": tx.Hash().Hex(), - }) - })) -} - -func TestRunSuccess(t *testing.T) { - srv := fakeRPC(t, false) - defer srv.Close() - - _, txHex := signedTestTx(t) - err := run([]string{"--rpc", srv.URL, txHex}, strings.NewReader("")) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -func TestRunFromStdin(t *testing.T) { - srv := fakeRPC(t, false) - defer srv.Close() - - _, txHex := signedTestTx(t) - err := run([]string{"--rpc", srv.URL}, strings.NewReader(txHex)) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -func TestRunRPCError(t *testing.T) { - srv := fakeRPC(t, true) - defer srv.Close() - - _, txHex := signedTestTx(t) - - // Capture stdout – raw hex should still be printed on RPC failure. - oldStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stdout = w - - runErr := run([]string{"--rpc", srv.URL, txHex}, strings.NewReader("")) - - w.Close() - os.Stdout = oldStdout - - if runErr == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(runErr.Error(), "already known") { - t.Fatalf("unexpected error message: %v", runErr) - } - - out, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } - if !strings.Contains(string(out), "Raw tx: 0x") { - t.Fatal("expected raw hex in output even on RPC error") - } -} - -func TestRunNoInput(t *testing.T) { - err := run(nil, strings.NewReader("")) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "no transaction data") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestRunBadHex(t *testing.T) { - err := run([]string{"not-hex-data"}, strings.NewReader("")) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "invalid hex") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestRunBadTx(t *testing.T) { - err := run([]string{"0xdeadbeef"}, strings.NewReader("")) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "decoding transaction") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestRunHelp(t *testing.T) { - err := run([]string{"--help"}, strings.NewReader("")) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -func TestRunUnknownFlag(t *testing.T) { - err := run([]string{"--unknown"}, strings.NewReader("")) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "unknown flag") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestRunExtraArgs(t *testing.T) { - err := run([]string{"0xaa", "0xbb"}, strings.NewReader("")) - if err == nil { - t.Fatal("expected error, got nil") - } - if !strings.Contains(err.Error(), "unexpected argument") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestFormatWei(t *testing.T) { - tests := []struct { - wei *big.Int - want string - }{ - {nil, "0 wei (0 ETH)"}, - {big.NewInt(0), "0 wei (0 ETH)"}, - {big.NewInt(1e18), "1000000000000000000 wei (1.000000000000000000 ETH)"}, - {new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)), "10000000000000000000 wei (10.000000000000000000 ETH)"}, - } - for _, tt := range tests { - got := formatWei(tt.wei) - if got != tt.want { - t.Errorf("formatWei(%v) = %q, want %q", tt.wei, got, tt.want) - } - } -} - -func TestFormatGwei(t *testing.T) { - tests := []struct { - wei *big.Int - want string - }{ - {nil, "0 wei (0 Gwei)"}, - {big.NewInt(0), "0 wei (0 Gwei)"}, - {big.NewInt(1_000_000_000), "1000000000 wei (1.000000000 Gwei)"}, - {big.NewInt(20_000_000_000), "20000000000 wei (20.000000000 Gwei)"}, - } - for _, tt := range tests { - got := formatGwei(tt.wei) - if got != tt.want { - t.Errorf("formatGwei(%v) = %q, want %q", tt.wei, got, tt.want) - } - } -} - -func TestTxCost(t *testing.T) { - tx := types.NewTx(&types.LegacyTx{ - GasPrice: big.NewInt(1_000_000_000), // 1 Gwei - Gas: 21055, - Value: new(big.Int).Mul(big.NewInt(10), big.NewInt(1e18)), // 10 ETH - }) - got := txCost(tx) - // Expected: 10 ETH + 21055 * 1 Gwei = 10000000000000000000 + 21055000000000 = 10000021055000000000 - want, _ := new(big.Int).SetString("10000021055000000000", 10) - if got.Cmp(want) != 0 { - t.Errorf("txCost = %s, want %s", got, want) - } -} - -func TestRunEqualsSyntax(t *testing.T) { - srv := fakeRPC(t, false) - defer srv.Close() - - _, txHex := signedTestTx(t) - err := run([]string{"--rpc=" + srv.URL, txHex}, strings.NewReader("")) - if err != nil { - t.Fatal("unexpected error:", err) - } -} - -func TestRunOutputEndsWithRawHex(t *testing.T) { - srv := fakeRPC(t, false) - defer srv.Close() - - _, txHex := signedTestTx(t) - - // Capture stdout to verify "Raw tx:" appears in output. - oldStdout := os.Stdout - r, w, err := os.Pipe() - if err != nil { - t.Fatal(err) - } - os.Stdout = w - - runErr := run([]string{"--rpc", srv.URL, txHex}, strings.NewReader("")) - - w.Close() - os.Stdout = oldStdout - - if runErr != nil { - t.Fatal("unexpected error:", runErr) - } - - out, err := io.ReadAll(r) - if err != nil { - t.Fatal(err) - } - lines := strings.Split(strings.TrimSpace(string(out)), "\n") - lastLine := lines[len(lines)-1] - - // The last line must be the raw hex transaction. - if !strings.HasPrefix(lastLine, "Raw tx: 0x") { - t.Fatalf("last output line = %q, want prefix \"Raw tx: 0x\"", lastLine) - } - // Verify the hex payload round-trips back to the input. - rawHex := strings.TrimPrefix(lastLine, "Raw tx: ") - if rawHex != txHex { - t.Fatalf("raw hex mismatch:\n got %s\n want %s", rawHex, txHex) - } -}