Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 201 additions & 1 deletion testutil/genchangelog/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@
numberRegex = regexp.MustCompile(`[#/](\d{2,})`)
categoryRegex = regexp.MustCompile(`category:\s?(\w+)`)
ticketRegex = regexp.MustCompile(`ticket:(.*)`)

// tagVersionRegex parses major.minor.patch from a release tag, e.g. "v1.10.3" or "v1.10.0-rc1".
tagVersionRegex = regexp.MustCompile(`^v(\d+)\.(\d+)\.(\d+)`)

// clientRepos maps each compatibility-matrix client to its GitHub repo.
clientRepos = []struct {
Name string
Repo string
}{
{"Teku", "Consensys/teku"},
{"Lighthouse", "sigp/lighthouse"},
{"Lodestar", "ChainSafe/lodestar"},
{"Nimbus", "status-im/nimbus-eth2"},
{"Prysm", "OffchainLabs/prysm"},
{"Grandine", "grandinetech/grandine"},
{"Vouch", "attestantio/vouch"},
}
)

// pullRequest is parsed from log.
Expand All @@ -87,6 +104,18 @@
RangeLink string
Categories []tplCategory
ExtraPRs []pullRequest
Clients clientVersions
}

// clientVersions holds the client versions rendered in the compatibility matrix.
type clientVersions struct {
Teku string
Lighthouse string
Lodestar string
Nimbus string
Prysm string
Grandine string
Vouch string
}

// tplCategory is a category section in the changelog.
Expand Down Expand Up @@ -151,6 +180,11 @@
return err
}

data.Clients, err = clientVersionsForTag(data.Tag, token)
if err != nil {
return err
}

b, err := execTemplate(data)
if err != nil {
return err
Expand All @@ -174,7 +208,7 @@
}

if token != "" {
req.SetBasicAuth(token, "x-oauth-basic")
req.Header.Set("Authorization", "Bearer "+token)
}

resp, err := new(http.Client).Do(req)
Expand Down Expand Up @@ -223,6 +257,172 @@
}
}

// clientVersionsForTag returns the compatibility-matrix client versions for the release tag.
// Minor releases (patch == 0) use the latest upstream client versions, while patch releases
// reuse the versions tested in their minor release, so a patch line stays consistent.
func clientVersionsForTag(tag string, token string) (clientVersions, error) {

Check warning on line 263 in testutil/genchangelog/main.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Group together these consecutive parameters of the same type.

See more on https://sonarcloud.io/project/issues?id=ObolNetwork_charon&issues=AZ9f5cSH9v-7QKVTLxok&open=AZ9f5cSH9v-7QKVTLxok&pullRequest=4597
_, _, patch, err := parseTagVersion(tag)
if err != nil {
return clientVersions{}, err
}

if patch == 0 {
fmt.Printf("Minor release %s: fetching latest client versions\n", tag)
return latestClientVersions(token)
}

minorTag, err := minorTagOf(tag)
if err != nil {
return clientVersions{}, err
}

fmt.Printf("Patch release %s: reusing client versions from minor release %s\n", tag, minorTag)

return minorClientVersions(minorTag, token)
}

// latestClientVersions fetches the latest release version of each matrix client from GitHub.
func latestClientVersions(token string) (clientVersions, error) {
versions := make(map[string]string)

for _, c := range clientRepos {
var resp struct {
TagName string `json:"tag_name"`
}

u := fmt.Sprintf("https://api.github.com/repos/%s/releases/latest", c.Repo)
if err := githubGetJSON(u, token, &resp); err != nil {
return clientVersions{}, errors.Wrap(err, "fetch latest client release", z.Str("client", c.Name), z.Str("repo", c.Repo))
}

if resp.TagName == "" {
return clientVersions{}, errors.New("empty client release tag", z.Str("client", c.Name), z.Str("repo", c.Repo))
}

versions[c.Name] = normalizeVersion(resp.TagName)
}

return clientVersionsFromMap(versions), nil
}

// minorClientVersions parses the client versions from the compatibility matrix of a previous minor release.
func minorClientVersions(minorTag string, token string) (clientVersions, error) {

Check warning on line 309 in testutil/genchangelog/main.go

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Group together these consecutive parameters of the same type.

See more on https://sonarcloud.io/project/issues?id=ObolNetwork_charon&issues=AZ9f5cSH9v-7QKVTLxol&open=AZ9f5cSH9v-7QKVTLxol&pullRequest=4597
var resp struct {
Body string `json:"body"`
}

u := "https://api.github.com/repos/obolnetwork/charon/releases/tags/" + minorTag
if err := githubGetJSON(u, token, &resp); err != nil {
return clientVersions{}, errors.Wrap(err, "fetch minor release", z.Str("minor", minorTag))
}

versions := make(map[string]string)

for _, c := range clientRepos {
re := regexp.MustCompile(regexp.QuoteMeta(c.Name) + `\s+(v[\d][\w.\-]*)`)

m := re.FindStringSubmatch(resp.Body)
if len(m) < 2 {
return clientVersions{}, errors.New("client version not found in minor release", z.Str("client", c.Name), z.Str("minor", minorTag))
}

versions[c.Name] = m[1]
}

return clientVersionsFromMap(versions), nil
}

// clientVersionsFromMap builds a clientVersions from a client-name-keyed map.
func clientVersionsFromMap(m map[string]string) clientVersions {
return clientVersions{
Teku: m["Teku"],
Lighthouse: m["Lighthouse"],
Lodestar: m["Lodestar"],
Nimbus: m["Nimbus"],
Prysm: m["Prysm"],
Grandine: m["Grandine"],
Vouch: m["Vouch"],
}
}

// githubGetJSON performs an authenticated GET request and unmarshals the JSON response into v.
func githubGetJSON(url string, token string, v any) error {
req, err := http.NewRequest(http.MethodGet, url, nil) //nolint:noctx // Non-critical tooling code.
if err != nil {
return errors.Wrap(err, "new request")
}

if token != "" {
req.Header.Set("Authorization", "Bearer "+token)
}

resp, err := new(http.Client).Do(req)
if err != nil {
return errors.Wrap(err, "do request")
}
defer resp.Body.Close()

b, err := io.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "read body")
}

if resp.StatusCode != http.StatusOK {
return errors.New("unexpected status code", z.Int("status", resp.StatusCode), z.Str("url", url), z.Str("body", string(b)))
}

if err := json.Unmarshal(b, v); err != nil {
return errors.Wrap(err, "unmarshal response", z.Str("url", url), z.Str("body", string(b)))
}

return nil
}

// normalizeVersion ensures the version tag has a leading "v" to match the matrix convention.
func normalizeVersion(tag string) string {
tag = strings.TrimSpace(tag)
if tag != "" && !strings.HasPrefix(tag, "v") {
tag = "v" + tag
}

return tag
}

// parseTagVersion parses the major, minor and patch numbers from a release tag.
func parseTagVersion(tag string) (major, minor, patch int, err error) {
m := tagVersionRegex.FindStringSubmatch(tag)
if len(m) < 4 {
return 0, 0, 0, errors.New("invalid release tag", z.Str("tag", tag))
}

major, err = strconv.Atoi(m[1])
if err != nil {
return 0, 0, 0, errors.Wrap(err, "parse major", z.Str("tag", tag))
}

minor, err = strconv.Atoi(m[2])
if err != nil {
return 0, 0, 0, errors.Wrap(err, "parse minor", z.Str("tag", tag))
}

patch, err = strconv.Atoi(m[3])
if err != nil {
return 0, 0, 0, errors.Wrap(err, "parse patch", z.Str("tag", tag))
}

return major, minor, patch, nil
}

// minorTagOf returns the minor release tag (patch 0) that the given tag belongs to.
func minorTagOf(tag string) (string, error) {
major, minor, _, err := parseTagVersion(tag)
if err != nil {
return "", err
}

return fmt.Sprintf("v%d.%d.0", major, minor), nil
}

// execTemplate returns the executed changelog template.
func execTemplate(data tplData) ([]byte, error) {
templ, err := template.New("").Parse(tpl)
Expand Down
28 changes: 13 additions & 15 deletions testutil/genchangelog/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

![Obol Logo](https://obol.tech/obolnetwork.png)

<!-- TODO: Add resume of the release in free text -->
<!-- TODO: Add a summary of the release in free text -->

Read the rest of the release notes for more:

Expand All @@ -15,26 +15,24 @@ Read the rest of the release notes for more:
{{end}}
## Compatibility Matrix

<!-- TODO: Update versions with which this version is compatible -->
This release of Charon is backwards compatible with Charon v1.0, v1.1, v1.2.
This release of Charon is backwards compatible with Charon >= v1.0, though only v1.7 and newer are Fulu-ready.

The below matrix details a combination of beacon node (consensus layer) + validator clients and their corresponding versions the DV Labs team have tested with this Charon release. More validator and consensus client will be added to this list as they are supported in our automated testing framework.
The below matrix details a combination of beacon node (consensus layer) + validator clients and their corresponding versions the DV Labs team have tested with this Charon release. More validator and consensus clients will be added to this list as they are supported in our automated testing framework.

**Legend**
- ✅: All duties succeed in testing
- 🟡: All duties succeed in testing, except non-penalised aggregation duties
- 🟠: Duties may fail for this combination
- 🔴: One or more duties fails consistently

<!-- TODO: Update clients versions, results and remarks -->
| Validator 👉 Consensus 👇 | Teku v25.3.0 | Lighthouse v7.0.0 | Lodestar v1.28.1 | Nimbus v25.3.1 | Prysm v5.3.1 | Vouch v1.10.2 | Remarks |
|---------------------------|--------------|-------------------|------------------|----------------|--------------|---------------|---------|
| Teku v25.3.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Lighthouse v7.0.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Lodestar v1.28.1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Nimbus v25.3.1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Prysm v5.3.1 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
| Grandine v1.1.0 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | |
- 🔴: One or more duties fail consistently

| Validator 👉 Consensus 👇 | Teku {{.Clients.Teku}} | Lighthouse {{.Clients.Lighthouse}} | Lodestar {{.Clients.Lodestar}} | Nimbus {{.Clients.Nimbus}} | Prysm {{.Clients.Prysm}} | Vouch {{.Clients.Vouch}} [❗](## "Vouch VC aggregations and sync contributions are not yet supported by Attestant team.") |
Comment thread
KaloyanTanev marked this conversation as resolved.
|---------------------------|--------------|-------------------|------------------|----------------|--------------|---------------|
| Teku {{.Clients.Teku}} [❗](## "Teku BN needs the `--validators-graffiti-client-append-format=DISABLED` flag in order to produce blocks properly.") | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
Comment thread
KaloyanTanev marked this conversation as resolved.
| Lighthouse {{.Clients.Lighthouse}} | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
| Lodestar {{.Clients.Lodestar}} | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
| Nimbus {{.Clients.Nimbus}} | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
| Prysm {{.Clients.Prysm}} | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |
| Grandine {{.Clients.Grandine}} | ✅ | ✅ | ✅ | ✅ | ✅ | 🟡 |

## What's Changed
{{range .ExtraPRs}}
Expand Down
Loading