Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Download ABI from release condidate branch if stable is missing #65

Merged
merged 3 commits into from
Jul 18, 2024
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
8 changes: 4 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:

strategy:
matrix:
node-version: [16, 18, 19, 20]
node-version: [16, 18, 20, 22]

defaults:
run:
Expand All @@ -55,7 +55,7 @@ jobs:

strategy:
matrix:
node-version: [16, 18, 19, 20]
node-version: [16, 18, 20, 22]

defaults:
run:
Expand Down Expand Up @@ -85,7 +85,7 @@ jobs:

strategy:
matrix:
node-version: [16, 18, 19, 20]
node-version: [16, 18, 20, 22]

defaults:
run:
Expand Down Expand Up @@ -115,7 +115,7 @@ jobs:

strategy:
matrix:
python-version: [3.9, "3.10", 3.11]
python-version: [3.9, "3.10", 3.11, 3.12]

defaults:
run:
Expand Down
2 changes: 1 addition & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"yarn-error.log",
".gitignore",
"cspell.json",
"**/venv/**",
"**/venv*/**",
"**/node_modules/**",
"**/__pycache__/**",
"**/requirements.txt",
Expand Down
95 changes: 89 additions & 6 deletions python/src/skale_contracts/project.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
"""Contains Project class"""

# cspell:words maxsplit

from __future__ import annotations
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
from itertools import count
from typing import TYPE_CHECKING, Generator

from eth_utils.address import to_canonical_address
import requests
from semver.version import Version as SemVersion

from .constants import REPOSITORY_URL, NETWORK_TIMEOUT
from .instance import Instance, InstanceData
Expand All @@ -13,6 +19,18 @@
from .network import Network


def alternative_versions_generator(version: str) -> Generator[str, None, None]:
"""Provides versions that have compatible ABI"""
sem_version = SemVersion.parse(version)
if sem_version.prerelease:
prerelease_title = sem_version.prerelease.split('.', maxsplit=1)[0]
if prerelease_title == 'stable':
for prerelease_version in count():
yield str(
sem_version.replace(prerelease=f'rc.{prerelease_version}')
)


class Project(ABC):
"""Represents set of smart contracts known as project"""

Expand Down Expand Up @@ -53,11 +71,23 @@ def get_instance(self, alias_or_address: str) -> Instance:

def download_abi_file(self, version: str) -> str:
"""Download file with ABI"""
url = self.get_abi_url(version)
response = requests.get(url, timeout=NETWORK_TIMEOUT)
if response.status_code != 200:
raise RuntimeError(f"Can't download abi file from {url}")
return response.text
exceptions: list[str] = []
abi_file = self._download_abi_file_by_version(
version,
exceptions
)
if abi_file:
return abi_file

# Stable version can be absent for some time after upgrade.
# Try release candidate branch
abi_file = self._download_alternative_abi_file(
version,
exceptions
)
if abi_file:
return abi_file
raise RuntimeError('\n'.join(exceptions))

def get_abi_url(self, version: str) -> str:
"""Calculate URL of ABI file"""
Expand All @@ -78,3 +108,56 @@ def get_instance_data_url(self, alias: str) -> str:
@abstractmethod
def create_instance(self, address: Address) -> Instance:
"""Create instance object based on known address"""

def get_abi_urls(self, version: str) -> list[str]:
"""Calculate URLs of ABI file"""
return [
self._get_github_release_abi_url(version),
self._get_github_repository_abi_url(version)
]

# Private

def _download_abi_file_by_version(
self,
version: str,
exceptions: list[str]
) -> str | None:
for abi_url in self.get_abi_urls(version):
response = requests.get(abi_url, timeout=NETWORK_TIMEOUT)
if response.status_code != 200:
exceptions.append(f"Can't download abi file from {abi_url}")
else:
return response.text
return None

def _download_alternative_abi_file(
self,
version: str,
exceptions: list[str]
) -> str | None:
abi_file: str | None = None
for alternative_version in alternative_versions_generator(version):
alternative_abi_file = self._download_abi_file_by_version(
alternative_version,
exceptions
)
if alternative_abi_file:
abi_file = alternative_abi_file
else:
# If abiFile is none
# the previous one is the latest
break

return abi_file

def _get_github_release_abi_url(self, version: str) -> str:
return f'{self.github_repo}releases/download/{version}/' + \
f'{self.get_abi_filename(version)}'

def _get_github_repository_abi_url(self, version: str) -> str:
url = self.github_repo.replace(
'github.com',
'raw.githubusercontent.com'
)
return f'{url}abi/{self.get_abi_filename(version)}'
106 changes: 90 additions & 16 deletions typescript/base/src/project.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as semver from "semver";
import { Instance, InstanceData } from "./instance";
import { MainContractAddress, SkaleABIFile } from "./domain/types";
import axios, { HttpStatusCode } from "axios";
Expand All @@ -10,6 +11,22 @@ import {
import { ProjectMetadata } from "./metadata";
import { REPOSITORY_URL } from "./domain/constants";

const alternativeVersionsGenerator =
function *alternativeVersionsGenerator (version: string) {
const semVersion = semver.parse(version);
const wordIndex = 0;
const nextIndex = 1;
if (semVersion?.prerelease[wordIndex] === "stable") {
for (let prereleaseVersion = 0; ; prereleaseVersion += nextIndex) {
semVersion.prerelease = [
"rc",
prereleaseVersion
];
yield semVersion.format();
}
}
};

export abstract class Project<ContractType> {
protected metadata: ProjectMetadata;

Expand All @@ -34,28 +51,31 @@ export abstract class Project<ContractType> {

async downloadAbiFile (version: string) {
const exceptions: string[] = [];
for (const abiUrl of this.getAbiUrls(version)) {
try {
// Await expression should be executed only
// when it failed on the previous iteration
// eslint-disable-next-line no-await-in-loop
const response = await axios.get(abiUrl);
return response.data as SkaleABIFile;
} catch (exception) {
exceptions.push(`\nDownloading from ${abiUrl} - ${exception}`);
}
let abiFile = await this.downloadAbiFileByVersion(
version,
exceptions
);
if (abiFile !== null) {
return abiFile;
}

// Stable version can be absent for some time after upgrade.
// Try release candidate branch
abiFile = await this.downloadAlternativeAbiFile(
version,
exceptions
);

if (abiFile !== null) {
return abiFile;
}
throw new Error(exceptions.join(""));
}

getAbiUrls (version: string) {
return [
`${this.githubRepo}releases/download/` +
`${version}/${this.getAbiFilename(version)}`,
`${this.githubRepo.replace(
"github.com",
"raw.githubusercontent.com"
)}abi/${this.getAbiFilename(version)}`
this.getGithubReleaseAbiUrl(version),
this.getGithubRepositoryAbiUrl(version)
];
}

Expand Down Expand Up @@ -86,4 +106,58 @@ export abstract class Project<ContractType> {
}
throw new InstanceNotFound(`Can't download data for instance ${alias}`);
}

private getGithubReleaseAbiUrl (version: string) {
return `${this.githubRepo}releases/download/` +
`${version}/${this.getAbiFilename(version)}`;
}

private getGithubRepositoryAbiUrl (version: string) {
return `${this.githubRepo.replace(
"github.com",
"raw.githubusercontent.com"
)}abi/${this.getAbiFilename(version)}`;
}

private async downloadAbiFileByVersion (
version: string,
exceptions: string[]
) {
for (const abiUrl of this.getAbiUrls(version)) {
try {
// Await expression should be executed only
// when it failed on the previous iteration
// eslint-disable-next-line no-await-in-loop
const response = await axios.get(abiUrl);
return response.data as SkaleABIFile;
} catch (exception) {
exceptions.push(`\nDownloading from ${abiUrl} - ${exception}`);
}
}
return null;
}

private async downloadAlternativeAbiFile (
version: string,
exceptions: string[]
) {
let abiFile: SkaleABIFile | null = null;
for (const alternativeVersion
of alternativeVersionsGenerator(version)) {
// Await expression must be executed sequentially
// eslint-disable-next-line no-await-in-loop
const alternativeAbiFile = await this.downloadAbiFileByVersion(
alternativeVersion,
exceptions
);
if (alternativeAbiFile === null) {
// If abiFile is null
// the previous one is the latest
break;
} else {
abiFile = alternativeAbiFile;
}
}
return abiFile;
}
}
Loading