hyperledger/fabric · error

at least 3 arguments expected, got %d

Error message

at least 3 arguments expected, got %d

What it means

secondPass converts the intermediate outof string into an actual NOutOf SignaturePolicy. It needs at least 3 arguments: the injected ID context, the threshold t, and at least one principal/policy. Fewer than 3 means the intermediate structure was truncated or bypassed, so the pass aborts.

Source

Thrown at common/policydsl/policyparser.go:130

		case float64:
			toret.WriteString(strconv.Itoa(int(t)))
		case int:
			toret.WriteString(strconv.Itoa(t))
		default:
			return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg))
		}
	}

	return toret.String() + ")", nil
}

// secondPass processes a list of arguments to build a "t-out-of-n" policy.
// It expects the first argument to be a context, the second an integer (threshold t),
// and the rest as either principals (strings) or pre-existing policies.
func secondPass(args ...any) (any, error) {
	/* general sanity check, we expect at least 3 args */
	if len(args) < 3 {
		return nil, fmt.Errorf("at least 3 arguments expected, got %d", len(args))
	}

	/* 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:

View on GitHub (pinned to 2736b63f8f)

Solutions

  1. Ensure the original policy gate has at least one subject: 'OutOf(1, "Org1.member")'.
  2. If calling secondPass directly, pass (ctx *context, t int, principal-or-policy, ...).
  3. Validate generated policy strings contain at least one principal per gate before invoking FromString.
  4. Trace where the intermediate 'outof(...)' string is produced; fix the upstream missing-argument bug.

Example fix

// before
policydsl.FromString("And()") // gate with no subjects
// after
policydsl.FromString("And('Org1.member', 'Org2.member')")
Defensive patterns

Strategy: validation

Validate before calling

if len(args) < 3 {
	return fmt.Errorf("secondPass needs ctx, t, and at least one principal")
}
ctx, ok := args[0].(*context)
if !ok {
	return fmt.Errorf("first arg must be *context")
}

Type guard

func isContextCall(args []any) bool {
	if len(args) < 3 { return false }
	_, ok := args[0].(*context)
	return ok
}

Try / catch

policy, err := policydsl.FromString(spec)
if err != nil && strings.Contains(err.Error(), "at least 3 arguments expected") {
	return nil, fmt.Errorf("policy %q has an empty gate: %w", spec, err)
}

Prevention

When it happens

Trigger: An intermediate string like 'outof(ID)' or 'outof(ID, 1)' reaching the second pass — e.g. the original policy had a gate with no subjects (related to error 350 but caught later), or secondPass invoked directly with too few args.

Common situations: Empty org lists in generated policy code producing subject-less gates; custom pipelines that call secondPass without the injected ID argument; policy strings mangled by templating that dropped trailing arguments.

Related errors


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