diff --git a/cmd/rpc/admin.go b/cmd/rpc/admin.go index af9d49e8b6..e84aed9301 100644 --- a/cmd/rpc/admin.go +++ b/cmd/rpc/admin.go @@ -297,11 +297,9 @@ func (s *Server) TransactionDAOTransfer(w http.ResponseWriter, r *http.Request, func (s *Server) TransactionSubsidy(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageSubsidyName); err != nil { @@ -316,11 +314,9 @@ func (s *Server) TransactionSubsidy(w http.ResponseWriter, r *http.Request, _ ht func (s *Server) TransactionCreateOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageCreateOrderName); err != nil { @@ -335,11 +331,9 @@ func (s *Server) TransactionCreateOrder(w http.ResponseWriter, r *http.Request, func (s *Server) TransactionEditOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } if err := s.getFeeFromState(ptr, fsm.MessageEditOrderName); err != nil { return nil, err @@ -353,11 +347,9 @@ func (s *Server) TransactionEditOrder(w http.ResponseWriter, r *http.Request, _ func (s *Server) TransactionDeleteOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageDeleteOrderName); err != nil { @@ -372,11 +364,9 @@ func (s *Server) TransactionDeleteOrder(w http.ResponseWriter, r *http.Request, func (s *Server) TransactionDexLimitOrder(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageDexLimitOrderName); err != nil { @@ -391,11 +381,9 @@ func (s *Server) TransactionDexLimitOrder(w http.ResponseWriter, r *http.Request func (s *Server) TransactionDexLiquidityDeposit(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageDexLiquidityDepositName); err != nil { @@ -410,11 +398,9 @@ func (s *Server) TransactionDexLiquidityDeposit(w http.ResponseWriter, r *http.R func (s *Server) TransactionDexLiquidityWithdraw(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { // Call the transaction handler with a callback that creates the transaction s.txHandler(w, r, func(p crypto.PrivateKeyI, ptr *txRequest) (lib.TransactionI, error) { - // Create a default chainid of 0 - chainId := uint64(0) - // Convert comma separated string of committees to uint64 - if c, err := stringToCommittees(ptr.Committees); err == nil { - chainId = c[0] + chainId, err := singleCommitteeID(ptr.Committees) + if err != nil { + return nil, err } // Retrieve the fee required for this type of transaction if err := s.getFeeFromState(ptr, fsm.MessageDexLiquidityWithdrawName); err != nil { @@ -822,6 +808,22 @@ func stringToCommittees(s string) (committees []uint64, error error) { return } +// singleCommitteeID parses the one chain-scoped committee accepted by order, +// subsidy, and DEX transaction endpoints. +func singleCommitteeID(s string) (uint64, error) { + if strings.TrimSpace(s) == "" { + return 0, nil + } + committees, err := stringToCommittees(s) + if err != nil { + return 0, err + } + if len(committees) != 1 { + return 0, fmt.Errorf("expected exactly one committee, got %d", len(committees)) + } + return committees[0], nil +} + // getAddressFromNickname retrieves the account address for the supplied nickname func getAddressFromNickname(ptr *txRequest, keystore *crypto.Keystore) { // Populate Signer field if SignerNickname is present diff --git a/cmd/rpc/admin_test.go b/cmd/rpc/admin_test.go new file mode 100644 index 0000000000..d44d0ce9e9 --- /dev/null +++ b/cmd/rpc/admin_test.go @@ -0,0 +1,54 @@ +package rpc + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestSingleCommitteeID(t *testing.T) { + tests := []struct { + name string + input string + want uint64 + wantError string + }{ + { + name: "empty defaults to root chain", + input: "", + want: 0, + }, + { + name: "blank defaults to root chain", + input: " ", + want: 0, + }, + { + name: "single committee", + input: "12", + want: 12, + }, + { + name: "malformed committee", + input: "nested-12", + wantError: "invalid syntax", + }, + { + name: "multiple committees rejected", + input: "1,2", + wantError: "expected exactly one committee", + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + got, err := singleCommitteeID(test.input) + if test.wantError != "" { + require.ErrorContains(t, err, test.wantError) + return + } + require.NoError(t, err) + require.Equal(t, test.want, got) + }) + } +}