hyperledger/fabric · error

unrecognized type, expected a number, got %s

Error message

unrecognized type, expected a number, got %s

What it means

secondPass expects its second argument to be the threshold t, and only accepts float64 or int (float64 because the expression evaluator treats numbers as float64). Any other type in the threshold slot fails with 'unrecognized type, expected a number, got <T>'.

Source

Thrown at common/policydsl/policyparser.go:151

	/* get the first argument, we expect it to be the context */
	var ctx *context
	switch v := args[0].(type) {
	case *context:
		ctx = v
	default:
		return nil, fmt.Errorf("unrecognized type, expected the context, got %s", reflect.TypeOf(args[0]))
	}

	/* get the second argument, we expect an integer telling us
	   how many of the remaining we expect to have*/
	var t int
	switch arg := args[1].(type) {
	case float64:
		t = int(arg)
	case int:
		t = arg
	default:
		return nil, fmt.Errorf("unrecognized type, expected a number, got %s", reflect.TypeOf(args[1]))
	}

	/* get the n in the t out of n */
	n := len(args) - 2

	/* sanity check - t should be positive, permit equal to n+1, but disallow over n+1 */
	if t < 0 || t > n+1 {
		return nil, fmt.Errorf("invalid t-out-of-n predicate, t %d, n %d", t, n)
	}

	policies := make([]*cb.SignaturePolicy, 0)

	/* handle the rest of the arguments */
	for _, principal := range args[2:] {
		switch t := principal.(type) {
		/* if it's a string, we expect it to be formed as
		   <MSP_ID> . <ROLE>, where MSP_ID is the MSP identifier
		   and ROLE is either a member, an admin, a client, a peer or an orderer*/

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Write the threshold unquoted and numeric in the policy: OutOf(2, ...) not OutOf('2', ...).
  2. Convert the threshold to int (or float64) before any direct secondPass call: secondPass(ctx, int(t), ...).
  3. Reject/normalize non-numeric thresholds in code that assembles policies from user config.
  4. Check upstream gate functions (and/or) — they inject len(args) or 1, so a custom wrapper must do the same.

Example fix

// before
policydsl.FromString("OutOf('2', 'Org1.member', 'Org2.member')")
// after
policydsl.FromString("OutOf(2, 'Org1.member', 'Org2.member')")
Defensive patterns

Strategy: type-guard

Validate before calling

switch args[1].(type) {
case int, float64:
	// ok
default:
	return fmt.Errorf("threshold must be numeric, got %T", args[1])
}

Type guard

func isNumericThreshold(v any) bool {
	switch v.(type) {
	case int, float64:
		return true
	}
	return false
}

Try / catch

if err != nil && strings.Contains(err.Error(), "expected a number") {
	return nil, fmt.Errorf("policy threshold must be unquoted and numeric: %w", err)
}

Prevention

When it happens

Trigger: An intermediate string where the second outof argument is not numeric — e.g. 'outof(ID, "2", ...)' produced by a gate whose threshold was a quoted string, or calling secondPass directly with a string/int64 threshold.

Common situations: User policies like "OutOf('2', 'Org1.member')" where the threshold was quoted and survives as a string into secondPass; programmatic gate construction passing int64/uint64 thresholds; locale/templating code turning numbers into strings.

Related errors


AI-assisted analysis of hyperledger/fabric@2736b63f8f (2026-09-04). Data as JSON: /api/errors/5fa23aec60fbac27. Report an issue: GitHub.