fish2018/pansou · error

Anubis 验证题目无效

Error message

Anubis 验证题目无效

What it means

After unmarshalling, parseAnubisChallenge validates the challenge: id and randomData must be non-empty and difficulty must be 1–7. Otherwise it returns this sentinel error (miosou.go:265), meaning the challenge parsed but is unusable.

Solutions

  1. Dump the unmarshalled document to see which field is empty/out-of-range and update struct tags if Anubis renamed it.
  2. Widen the accepted difficulty range only if the upstream server legitimately changed bounds.
  3. Confirm the server is a real Anubis instance and not a custom/modified deployment.
  4. Retry — if it's a transiently corrupted page, ensureGate's retries may fetch a clean one.

Example fix

// before
if challenge.Challenge.ID == "" || challenge.Challenge.RandomData == "" || challenge.Rules.Difficulty < 1 || challenge.Rules.Difficulty > 7 {
    return anubisChallengeDocument{}, true, fmt.Errorf("Anubis 验证题目无效")
}
// after — log which invariant failed for diagnosability
if challenge.Challenge.ID == "" || challenge.Challenge.RandomData == "" || challenge.Rules.Difficulty < 1 || challenge.Rules.Difficulty > 7 {
    return anubisChallengeDocument{}, true, fmt.Errorf("Anubis 验证题目无效: id=%q randomData=%q difficulty=%d",
        challenge.Challenge.ID, challenge.Challenge.RandomData, challenge.Rules.Difficulty)
}
Defensive patterns

Strategy: validation

Validate before calling

var probe struct {
    Challenge struct {
        ID         string `json:"id"`
        RandomData string `json:"randomData"`
    } `json:"challenge"`
    Rules struct {
        Difficulty int `json:"difficulty"`
    } `json:"rules"`
}
if err := json.Unmarshal([]byte(html.UnescapeString(blob)), &probe); err != nil {
    return err
}
if probe.Challenge.ID == "" || probe.Challenge.RandomData == "" || probe.Rules.Difficulty < 1 || probe.Rules.Difficulty > 7 {
    return fmt.Errorf("challenge fields missing or difficulty out of range")
}

Type guard

func validAnubisChallenge(c anubisChallengeDocument) bool {
    return c.Challenge.ID != "" && c.Challenge.RandomData != "" &&
        c.Rules.Difficulty >= 1 && c.Rules.Difficulty <= 7
}

Try / catch

challenge, found, err := parseAnubisChallenge(body)
if err != nil {
    if strings.Contains(err.Error(), "验证题目无效") {
        // server format drift: log document and verify struct tags
    }
    return err
}

Prevention

When it happens

Trigger: JSON unmarshals cleanly into anubisChallengeDocument but Challenge.ID or Challenge.RandomData is empty, or Rules.Difficulty is 0 or >7 — i.e. field-name drift or an upstream format change rather than corrupt JSON.

Common situations: Anubis raising its difficulty range or renaming fields so they unmarshal to zero values; a stub/test Anubis deployment emitting minimal JSON; a proxy serving a cached/partial page.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of fish2018/pansou@beaa561337 (2026-09-07). Data as JSON: /api/errors/c70ba61dadb176ea. Report an issue: GitHub.

Appendix: source

Thrown at plugin/miosou/miosou.go:265

		Difficulty int    `json:"difficulty"`
	} `json:"rules"`
	Challenge struct {
		ID         string `json:"id"`
		RandomData string `json:"randomData"`
	} `json:"challenge"`
}

func parseAnubisChallenge(body []byte) (anubisChallengeDocument, bool, error) {
	match := anubisChallengePattern.FindSubmatch(body)
	if len(match) != 2 {
		return anubisChallengeDocument{}, false, nil
	}
	var challenge anubisChallengeDocument
	if err := json.Unmarshal([]byte(html.UnescapeString(string(match[1]))), &challenge); err != nil {
		return anubisChallengeDocument{}, true, fmt.Errorf("解析 Anubis 验证题目失败: %w", err)
	}
	if challenge.Challenge.ID == "" || challenge.Challenge.RandomData == "" || challenge.Rules.Difficulty < 1 || challenge.Rules.Difficulty > 7 {
		return anubisChallengeDocument{}, true, fmt.Errorf("Anubis 验证题目无效")
	}
	if challenge.Rules.Algorithm != "fast" && challenge.Rules.Algorithm != "slow" {
		return anubisChallengeDocument{}, true, fmt.Errorf("不支持的 Anubis 验证算法: %s", challenge.Rules.Algorithm)
	}
	return challenge, true, nil
}

func solveAnubisChallenge(ctx context.Context, randomData string, difficulty int) (string, uint64, error) {
	prefix := []byte(randomData)
	buffer := make([]byte, len(prefix), len(prefix)+20)
	copy(buffer, prefix)
	for nonce := uint64(0); ; nonce++ {
		if nonce&4095 == 0 {
			select {
			case <-ctx.Done():
				return "", 0, fmt.Errorf("计算 Anubis 工作量证明失败: %w", ctx.Err())
			default:
			}

View on GitHub (pinned to beaa561337)