From 32084d270694d7beb4bb8eaa17bad6176b672496 Mon Sep 17 00:00:00 2001 From: Moran Abilea Date: Tue, 23 Jun 2026 09:24:48 +0000 Subject: [PATCH] Add signed tx to armageddon load changing to use signed mode instead of boolean flag Signed-off-by: Moran Abilea --- common/tools/armageddon/armageddon.go | 68 ++++++++++++++++++---- common/tools/armageddon/armageddon_test.go | 58 ++++++++++++++++++ docs/cli/armageddon.md | 3 + testutil/signutil/signer.go | 20 ++++++- testutil/tx/tx_utils.go | 19 +++++- testutil/utils.go | 15 ++--- 6 files changed, 157 insertions(+), 26 deletions(-) diff --git a/common/tools/armageddon/armageddon.go b/common/tools/armageddon/armageddon.go index 35bdfbee1..ec8284f2a 100644 --- a/common/tools/armageddon/armageddon.go +++ b/common/tools/armageddon/armageddon.go @@ -34,6 +34,7 @@ import ( "github.com/hyperledger/fabric-x-orderer/config" genconfig "github.com/hyperledger/fabric-x-orderer/config/generate" "github.com/hyperledger/fabric-x-orderer/node/comm" + "github.com/hyperledger/fabric-x-orderer/node/crypto" "github.com/hyperledger/fabric-x-orderer/testutil/fabric" "github.com/hyperledger/fabric-x-orderer/testutil/signutil" "github.com/hyperledger/fabric-x-orderer/testutil/tx" @@ -123,6 +124,7 @@ type CLI struct { loadTransactions *int loadRate *string loadTxSize *int + loadSignedMode *string // receive command flags receiveUserConfigFile **os.File receiveExpectedNumOfTxs *int @@ -173,6 +175,7 @@ func (cli *CLI) configureCommands() { cli.loadTransactions = load.Flag("transactions", "The number of transactions to be sent").Int() cli.loadRate = load.Flag("rate", "The rate specifies the number of transactions per second to be sent as one or more rate numbers separated by space").String() cli.loadTxSize = load.Flag("txSize", "The required transaction size in bytes").Int() + cli.loadSignedMode = load.Flag("signedMode", "Signing mode has three option: none = unsigned, full = sign including the full certificate, short = sign using known certificate").String() commands["load"] = load receive := cli.app.Command("receive", "Pull txs from some assembler and report statistics") @@ -219,7 +222,7 @@ func (cli *CLI) Run(args []string) { // "load" command case cli.commands["load"].FullCommand(): - load(cli.loadUserConfigFile, cli.loadTransactions, cli.loadRate, cli.loadTxSize) + load(cli.loadUserConfigFile, cli.loadTransactions, cli.loadRate, cli.loadTxSize, cli.loadSignedMode) // "receive" command case cli.commands["receive"].FullCommand(): @@ -478,7 +481,7 @@ func submit(userConfigFile **os.File, transactions *int, rate *int, txSize *int) } // load command makes txs and sends them to all routers -func load(userConfigFile **os.File, transactions *int, rate *string, txSize *int) { +func load(userConfigFile **os.File, transactions *int, rate *string, txSize *int, signedMode *string) { rates := strings.Fields(*rate) // check transaction size txMinimumSize := 16 + 8 + 8 @@ -493,6 +496,9 @@ func load(userConfigFile **os.File, transactions *int, rate *string, txSize *int fmt.Fprintf(os.Stderr, "Error reading config: %s", err) os.Exit(-1) } + + logger.Infof("Load command uses signed transactions mode: %s", *signedMode) + convertedRates := make([]int, len(rates)) for i := 0; i < len(rates); i++ { convertedRates[i], err = strconv.Atoi(rates[i]) @@ -504,13 +510,13 @@ func load(userConfigFile **os.File, transactions *int, rate *string, txSize *int // send txs to the routers for i := 0; i < len(rates); i++ { start := time.Now() - SendTxsToAllAvailableRouters(userConfig, *transactions, convertedRates[i], *txSize, nil) + SendTxsToAllAvailableRouters(userConfig, *transactions, convertedRates[i], *txSize, nil, *signedMode) elapsed := time.Since(start) reportLoadResults(*transactions, elapsed, *txSize) } } -func SendTxsToAllAvailableRouters(userConfig *UserConfig, numOfTxs int, rate int, txSize int, txsMap *protectedMap) { +func SendTxsToAllAvailableRouters(userConfig *UserConfig, numOfTxs int, rate int, txSize int, txsMap *protectedMap, signedMode string) { broadcastClient := NewBroadcastTxClient(userConfig) err := broadcastClient.InitStreams() if err != nil { @@ -541,16 +547,56 @@ func SendTxsToAllAvailableRouters(userConfig *UserConfig, numOfTxs int, rate int go ReceiveResponseFromRouter(userConfig, streamInfo) } - for i := 0; i < numOfTxs; i++ { - env := tx.PrepareEnvWithTimestamp(i, txSize, sessionNumber) + var signer *crypto.ECDSASigner + var certBytes []byte + var org string + var env *common.Envelope - status := rl.GetToken() - if !status { - fmt.Fprintf(os.Stderr, "failed to send tx %d", i+1) + if signedMode == "full" || signedMode == "short" { + org, err = signutil.GetMspIDfromDir(userConfig.MSPDir) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to get mspID from user msp dir: %v", err) os.Exit(3) } - broadcastClient.SendTxToAllRouters(env) + signer, certBytes, err = signutil.LoadCryptoMaterialForSigner(userConfig.MSPDir) + if err != nil { + fmt.Fprintf(os.Stderr, "failed to load crypto materials: %v", err) + os.Exit(3) + } + } + + switch signedMode { + case "full": + for i := 0; i < numOfTxs; i++ { + env = tx.PrepareSignedEnvelopeWithCertificate(i, txSize, sessionNumber, signer, certBytes, org) + status := rl.GetToken() + if !status { + fmt.Fprintf(os.Stderr, "failed to send tx %d in signed mode %s", i+1, signedMode) + os.Exit(3) + } + broadcastClient.SendTxToAllRouters(env) + } + case "short": + for i := 0; i < numOfTxs; i++ { + env = tx.PrepareSignedEnvelopeWithCertificateID(i, txSize, sessionNumber, signer, certBytes, org) + status := rl.GetToken() + if !status { + fmt.Fprintf(os.Stderr, "failed to send tx %d in signed mode %s", i+1, signedMode) + os.Exit(3) + } + broadcastClient.SendTxToAllRouters(env) + } + default: + for i := 0; i < numOfTxs; i++ { + env = tx.PrepareUnsignedEnvelope(i, txSize, sessionNumber) + status := rl.GetToken() + if !status { + fmt.Fprintf(os.Stderr, "failed to send tx %d in unsigned mode", i+1) + os.Exit(3) + } + broadcastClient.SendTxToAllRouters(env) + } } rl.Stop() @@ -902,7 +948,7 @@ func calculateDelayOfTx(data []byte, acceptedTime time.Time) time.Duration { } func sendTx(txsMap *protectedMap, streams []ab.AtomicBroadcast_BroadcastClient, i int, txSize int, sessionNumber []byte) { - env := tx.PrepareEnvWithTimestamp(i, txSize, sessionNumber) + env := tx.PrepareUnsignedEnvelope(i, txSize, sessionNumber) data, _ := tx.GetDataFromEnvelope(env) if txsMap != nil { logger.Debugf("Add tx %x to the map", data) diff --git a/common/tools/armageddon/armageddon_test.go b/common/tools/armageddon/armageddon_test.go index 142057cec..bab56c903 100644 --- a/common/tools/armageddon/armageddon_test.go +++ b/common/tools/armageddon/armageddon_test.go @@ -276,6 +276,64 @@ func TestLoadAndReceive(t *testing.T) { waitForTxToBeSentAndReceived.Wait() } +// Scenario: +// 1. Create a config YAML file to be an input to armageddon +// 2. Run armageddon generate command to create config files in a folder structure +// 3. Run arma with the generated config files to run each of the nodes for all parties +// 4. Run armageddon load command with --signedMode=full +// 5. Run armageddon receive command to verify transactions are received +// +// This test verifies that the --signedMode flag works correctly with the load and receive commands. +// It does NOT verify cryptographic signature correctness, and test only the full option +// TODO: add another test for "--signedMode=short" as well. +func TestLoadAndReceiveSignedModeAsFull(t *testing.T) { + dir, err := os.MkdirTemp("", t.Name()) + require.NoError(t, err) + defer os.RemoveAll(dir) + + // 1. Create network config + configPath := filepath.Join(dir, "config.yaml") + netInfo := testutil.CreateNetwork(t, configPath, 4, 2, "none", "none") + defer netInfo.CleanUp() + + // 2. Generate armageddon config files + armageddonCLI := armageddon.NewCLI() + armageddonCLI.Run([]string{"generate", "--config", configPath, "--output", dir}) + + // 3. Build and run arma nodes + armaBinaryPath, err := gexec.BuildWithEnvironment("github.com/hyperledger/fabric-x-orderer/cmd/arma", []string{"GOPRIVATE=" + os.Getenv("GOPRIVATE")}) + require.NoError(t, err) + require.NotNil(t, armaBinaryPath) + + readyChan := make(chan string, 20) + armaNetwork := testutil.RunArmaNodes(t, dir, armaBinaryPath, readyChan, netInfo) + defer armaNetwork.Stop() + + testutil.WaitReady(t, readyChan, 20, 10) + + // 4. + 5. Run load and receive commands in parallel + userConfigPath := path.Join(dir, "config", "party1", "user_config.yaml") + rate := "100" + txs := "200" + txSize := "128" + signedMode := "full" + + var waitForLoadAndReceive sync.WaitGroup + waitForLoadAndReceive.Add(2) + + go func() { + defer waitForLoadAndReceive.Done() + armageddonCLI.Run([]string{"load", "--config", userConfigPath, "--transactions", txs, "--rate", rate, "--txSize", txSize, "--signedMode", signedMode}) + }() + + go func() { + defer waitForLoadAndReceive.Done() + armageddonCLI.Run([]string{"receive", "--config", userConfigPath, "--pullFromPartyId", "1", "--expectedTxs", txs, "--output", dir}) + }() + + waitForLoadAndReceive.Wait() +} + // Scenario: // 1. Create a config YAML file to be an input to armageddon // 2. Run armageddon generate command to create config files in a folder structure diff --git a/docs/cli/armageddon.md b/docs/cli/armageddon.md index fd92d3ed7..f54c118f0 100644 --- a/docs/cli/armageddon.md +++ b/docs/cli/armageddon.md @@ -122,6 +122,9 @@ Flags: per second to be sent as one or more rate numbers separated by space --txSize=TXSIZE The required transaction size in bytes + --signedMode=SIGNEDMODE Signing mode has three option: none = unsigned, + full = sign including the full certificate, + short = sign using known certificate ``` diff --git a/testutil/signutil/signer.go b/testutil/signutil/signer.go index 8a60d4919..83f8f4bf8 100644 --- a/testutil/signutil/signer.go +++ b/testutil/signutil/signer.go @@ -69,7 +69,7 @@ func CreateTestSigner(t *testing.T, mspID, dir string) *TestSigner { } func CreateSignerForUser(userMspDir string) (identity.SignerSerializer, error) { - mspID, err := getMspIDfromDir(userMspDir) + mspID, err := GetMspIDfromDir(userMspDir) if err != nil { return nil, fmt.Errorf("failed to get mspID from user msp dir: %s, err: %v", userMspDir, err) } @@ -93,7 +93,23 @@ func CreateSignerForUser(userMspDir string) (identity.SignerSerializer, error) { return signer, nil } -func getMspIDfromDir(mspDir string) (string, error) { +func LoadCryptoMaterialForSigner(mspDir string) (*crypto.ECDSASigner, []byte, error) { + keyBytes, err := os.ReadFile(filepath.Join(mspDir, "keystore", "priv_sk")) + if err != nil { + return nil, nil, fmt.Errorf("failed to read private key file: %w", err) + } + privateKey, err := tx.CreateECDSAPrivateKey(keyBytes) + if err != nil { + return nil, nil, fmt.Errorf("failed to create private key: %w", err) + } + certBytes, err := os.ReadFile(filepath.Join(mspDir, "signcerts", "sign-cert.pem")) + if err != nil { + return nil, nil, fmt.Errorf("failed to read sign certificate: %w", err) + } + return (*crypto.ECDSASigner)(privateKey), certBytes, nil +} + +func GetMspIDfromDir(mspDir string) (string, error) { re := regexp.MustCompile(`/ordererOrganizations/([^/]+)/`) matches := re.FindStringSubmatch(mspDir) if matches == nil || len(matches) > 2 { diff --git a/testutil/tx/tx_utils.go b/testutil/tx/tx_utils.go index 5a0b012d1..1c485eefe 100644 --- a/testutil/tx/tx_utils.go +++ b/testutil/tx/tx_utils.go @@ -204,8 +204,9 @@ var headersOverheadSize = func() int { return len(envBytes) - size }() -// PrepareEnvWithTimestamp prepares an envelope of size envSize, accounting for the overhead introduced by additional headers in the transaction. -func PrepareEnvWithTimestamp(txNumber int, envSize int, sessionNumber []byte) *common.Envelope { +// PrepareUnsignedEnvelope prepares an envelope of size envSize, accounting for the overhead introduced by additional headers in the transaction. +// this method will be used for creating envelope for unsigned signing mode. +func PrepareUnsignedEnvelope(txNumber int, envSize int, sessionNumber []byte) *common.Envelope { var dataSize int overheadSize := headersOverheadSize @@ -222,6 +223,20 @@ func PrepareEnvWithTimestamp(txNumber int, envSize int, sessionNumber []byte) *c return CreateStructuredEnvelope(data) } +// PrepareSignedEnvelopeWithCertificate prepares an fully signed envelope. +// this method will be used for creating signed envelope for full signing mode. +func PrepareSignedEnvelopeWithCertificate(txNumber int, envSize int, sessionNumber []byte, signer *crypto.ECDSASigner, certBytes []byte, org string) *common.Envelope { + data := PrepareTxWithTimestamp(txNumber, envSize, sessionNumber) + return CreateSignedStructuredEnvelope(data, signer, certBytes, org) +} + +// TODO: implement the following method: +// PrepareSignedEnvelopeWithCertificateID prepares an signed envelope using known certificates which are referenced by hash. +// this method will be used for creating signed envelope for short signing mode. +func PrepareSignedEnvelopeWithCertificateID(txNumber int, envSize int, sessionNumber []byte, signer *crypto.ECDSASigner, certBytes []byte, org string) *common.Envelope { + return nil +} + func CreateSignedStructuredEnvelope(data []byte, signer *crypto.ECDSASigner, certBytes []byte, org string) *common.Envelope { payload := createSignedStructuredPayload(data, certBytes, org) payloadBytes := deterministicMarshall(payload) diff --git a/testutil/utils.go b/testutil/utils.go index 8d35cedf0..c7aef1628 100644 --- a/testutil/utils.go +++ b/testutil/utils.go @@ -30,7 +30,7 @@ import ( nodeconfig "github.com/hyperledger/fabric-x-orderer/node/config" "github.com/hyperledger/fabric-x-orderer/node/crypto" configMocks "github.com/hyperledger/fabric-x-orderer/test/mocks" - "github.com/hyperledger/fabric-x-orderer/testutil/tx" + "github.com/hyperledger/fabric-x-orderer/testutil/signutil" "github.com/onsi/gomega/gexec" "github.com/stretchr/testify/require" "golang.org/x/sync/errgroup" @@ -694,16 +694,9 @@ func sortArmaNodeInfo(infos []*ArmaNodeInfo) func(i, j int) bool { } func LoadCryptoMaterialsFromDir(t *testing.T, mspDir string) (*crypto.ECDSASigner, []byte, error) { - keyBytes, err := os.ReadFile(filepath.Join(mspDir, "keystore", "priv_sk")) - require.NoError(t, err, "failed to read private key file") - - privateKey, err := tx.CreateECDSAPrivateKey(keyBytes) - require.NoError(t, err, "failed to create private key") - - certBytes, err := os.ReadFile(filepath.Join(mspDir, "signcerts", "sign-cert.pem")) - require.NoError(t, err, "failed to read sign certificate file") - - return (*crypto.ECDSASigner)(privateKey), certBytes, nil + signer, certBytes, err := signutil.LoadCryptoMaterialForSigner(mspDir) + require.NoError(t, err) + return signer, certBytes, nil } func CreateAssemblerBundleForTest(sequence uint64) channelconfig.Resources {