certificate chain walker + progress bar fix

- aggregator.CertChain: walks previous_hash from head until genesis_signature
- cmd: 'cert' subcommand, -chain flag for full walk, 'head' shortcut resolves
  latest snapshot's certificate_hash
- ProgressFn now signals both bytes-read and total-from-Content-Length so
  percent is computed against the actual transfer size, not the uncompressed
  target
- verified against preprod: 90-cert chain head→genesis, Ed25519 genesis cert
  shape (64-byte sig over 32-byte signed_message, protocol_message carries
  next_aggregate_verification_key for BLS), STM-signed non-genesis certs

pipeline is now verification-sprint ready
This commit is contained in:
Sulkta 2026-04-23 15:20:32 -07:00
parent e4012bf79a
commit 517df1130d
3 changed files with 120 additions and 15 deletions

View file

@ -158,3 +158,32 @@ func (c *Client) GetCertificate(ctx context.Context, hash string) (*Certificate,
}
return &out, nil
}
// CertChain walks previous_hash backwards from headHash until it hits the
// first certificate that carries a genesis_signature. Returns the chain
// ordered head-first. Caller can invert if root-first is preferred.
//
// The chain length is usually 1-3 certs per epoch boundary; an unbounded
// walk would be a footgun so it caps at maxDepth.
func (c *Client) CertChain(ctx context.Context, headHash string, maxDepth int) ([]*Certificate, error) {
if maxDepth <= 0 {
maxDepth = 1024
}
var chain []*Certificate
next := headHash
for i := 0; i < maxDepth; i++ {
if next == "" {
return nil, fmt.Errorf("chain broke at depth %d: no previous_hash", i)
}
cert, err := c.GetCertificate(ctx, next)
if err != nil {
return nil, fmt.Errorf("depth %d (%s): %w", i, next, err)
}
chain = append(chain, cert)
if cert.GenesisSignature != "" {
return chain, nil
}
next = cert.PreviousHash
}
return nil, fmt.Errorf("cert chain exceeded max depth %d without reaching genesis", maxDepth)
}