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

Add python packaging #1

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
__pycache__/
*.egg-info
build/
dist/
venv/
.cache
.local
.idea
*.pyc

40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@

# Overview
Pre-build binaries for morpav/zceq_solver repository.

The python module `pyzceqsolver` uses by default more generic "core2"
version of the library. Change the symlink inside if you can use
"avx2" version.
"avx2" version.


# Building all wheels in Docker

Run script `docker-build.sh` which builds wheels to directory `dist/` for every Python in `base-env` Docker image.

```
./docker-build.sh
```

# Building Python package/Development mode

## Prerequisites

- clone the repository

- create python virtualenv:

```
virtualenv --python=/usr/bin/python3 .zceqsolverenv
. .zceqsolverenv/bin/activate
```

## Installing in development mode

```
cd zceq_solver--bin
pip install -e .
```

## Building binary distribution package

```
pip install wheel
python ./setup.py bdist_wheel
```
25 changes: 25 additions & 0 deletions docker-build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/sh

IMAGE=docker.bo/pool/main/base-env

docker run -v `pwd`:/build -w /build -v `pwd`/.local:/.local -v `pwd`/.cache:/.cache -u `id -u` $IMAGE bash -c "\
rm dist/* venv/* -rf && \
mkdir venv -p && \
virtualenv venv/py2.7 -p \`which python2.7\` && \
venv/py2.7/bin/pip install -e . && \
venv/py2.7/bin/pip install wheel && \
venv/py2.7/bin/python ./setup.py bdist_wheel && \
python3.6 -m venv venv/py3.6 && \
venv/py3.6/bin/pip install -e . && \
venv/py3.6/bin/pip install wheel && \
venv/py3.6/bin/python ./setup.py bdist_wheel && \
python3.7 -m venv venv/py3.7 && \
venv/py3.7/bin/pip install -e . && \
venv/py3.7/bin/pip install wheel && \
venv/py3.7/bin/python ./setup.py bdist_wheel
virtualenv venv/pypy -p \`which pypy\` && \
venv/pypy/bin/pip install -e . && \
venv/pypy/bin/pip install wheel && \
venv/pypy/bin/python ./setup.py bdist_wheel

"
131 changes: 17 additions & 114 deletions pyzceqsolver/__init__.py
Original file line number Diff line number Diff line change
@@ -1,114 +1,17 @@
from cffi import FFI
import os.path
import inspect

ffi = None
library = None

library_header = """
typedef struct {
char data[1344];
} Solution;

typedef struct {
unsigned int data[512];
} ExpandedSolution;

typedef struct HeaderAndNonce {
char data[140];
} HeaderAndNonce;

typedef struct ZcEquihashSolverT ZcEquihashSolver;

ZcEquihashSolver* CreateSolver(void);

void DestroySolver(ZcEquihashSolver* solver);

int FindSolutions(ZcEquihashSolver* solver, HeaderAndNonce* inputs,
Solution solutions[], int max_solutions);

int ValidateSolution(ZcEquihashSolver* solver, HeaderAndNonce* inputs, Solution* solutions);

void RunBenchmark(long long nonce_start, int iterations);

bool ExpandedToMinimal(Solution* minimal, ExpandedSolution* expanded);

bool MinimalToExpanded(ExpandedSolution* expanded, Solution* minimal);

"""

def load_library(path=None):
global library, ffi
assert library is None

ffi = FFI()
ffi.cdef(library_header)

if path is None:
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'libzceq_solver_sh.so')
library = ffi.dlopen(path)
assert library is not None


class Solver:
def __init__(self):
self.solver_ = self.header_ = self.solutions_ = self.solution_to_check_ = None
self._ensure_library()
assert library and ffi
self.solver_ = library.CreateSolver()
self.header_ = ffi.new("HeaderAndNonce*")
self.solutions_ = ffi.new("Solution[16]")
self.solution_to_check_ = ffi.new("Solution*")
self.expanded_tmp_ = ffi.new("ExpandedSolution*")

def __del__(self):
# Free the underlying resources on destruction
library.DestroySolver(self.solver_);
self.solver_ = None
# cffi's cdata are collected automatically
self.header_ = self.solutions_ = self.solution_to_check_ = None

def _ensure_library(self):
# Try to load library from standard
if (library is None):
load_library()

def run_benchmark(self, iterations=10, nonce_start=0):
library.RunBenchmark(nonce_start, iterations)

def find_solutions(self, block_header):
assert len(block_header) == 140
self.header_.data = block_header
return library.FindSolutions(self.solver_, self.header_, self.solutions_, 16);

def get_solution(self, num):
assert(num >= 0 and num < 16)
return bytes(ffi.buffer(self.solutions_[num].data))

def validate_solution(self, block_header, solution):
assert len(block_header) == 140
assert len(solution) == 1344
self.solution_to_check_.data = solution
return library.ValidateSolution(self.solver_, self.header_, self.solution_to_check_);

def list_to_minimal(self, expanded):
if isinstance(expanded, (list, tuple)):
assert len(expanded) == 512
minimal = ffi.new("Solution*")
tmp = self.expanded_tmp_
for i, idx in enumerate(expanded):
tmp.data[i] = idx
expanded = tmp

res = library.ExpandedToMinimal(minimal, expanded)
assert res
return minimal

def minimal_to_list(self, minimal):
tmp = self.expanded_tmp_
res = library.MinimalToExpanded(tmp, minimal)
assert res
result = [tmp.data[i] for i in range(512)]
return result

__all__ = ['Solver', 'load_library']
import logging

log = logging.getLogger('{0}'.format(__name__))

def get_library_filename(system_name):
library_name_map_fmt = {
'Linux': 'lib{}.so',
'Windows': '{}.dll',
}
try:
library_filename = library_name_map_fmt[system_name].format(
'zceqsolver')
except KeyError as e:
msg = 'Unsupported system: {0}, cannot provide system specific ' \
'library name'.format(system_name)
raise Exception(msg)
return library_filename
1 change: 0 additions & 1 deletion pyzceqsolver/libzceq_solver_sh.so

This file was deleted.

Binary file added pyzceqsolver/libzceqsolver.so
Binary file not shown.
125 changes: 125 additions & 0 deletions pyzceqsolver/solver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from cffi import FFI
import pkg_resources
import platform
import pyzceqsolver

ffi = None
library = None

library_header = """
typedef struct {
char data[1344];
} Solution;

typedef struct {
unsigned int data[512];
} ExpandedSolution;

typedef struct HeaderAndNonce {
char data[140];
} HeaderAndNonce;

typedef struct ZcEquihashSolverT ZcEquihashSolver;

ZcEquihashSolver* CreateSolver(void);

void DestroySolver(ZcEquihashSolver* solver);

int FindSolutions(ZcEquihashSolver* solver, HeaderAndNonce* inputs,
Solution solutions[], int max_solutions);

int ValidateSolution(ZcEquihashSolver* solver, HeaderAndNonce* inputs, Solution* solutions);

bool ExpandedToMinimal(Solution* minimal, ExpandedSolution* expanded);

bool MinimalToExpanded(ExpandedSolution* expanded, Solution* minimal);

"""
import logging

log = logging.getLogger('{0}'.format(__name__))

def load_library():
global library, ffi
assert library is None
ffi = FFI()
ffi.cdef(library_header)
try:
library_filename = pyzceqsolver.get_library_filename(platform.system())
except Exception as e:
log.error('Failed to get library filename: {}'.format(e))
else:
library_pathname = pkg_resources.resource_filename(__name__,
library_filename)
library = ffi.dlopen(library_pathname)
assert library is not None
log.info('Loaded shared library: {0}'.format(library_filename))


class Solver:
def __init__(self, verbose=True):
self.solver_ = self.header_ = self.solutions_ = self.solution_to_check_ = None
self._ensure_library()
assert library and ffi
self.solver_ = library.CreateSolver()
self.header_ = ffi.new("HeaderAndNonce*")
self.solutions_ = ffi.new("Solution[16]")
self.minimal_tmp_ = ffi.new("Solution*")
self.expanded_tmp_ = ffi.new("ExpandedSolution*")

def __del__(self):
# Free the underlying resources on destruction
library.DestroySolver(self.solver_);
self.solver_ = None
# cffi's cdata are collected automatically
self.header_ = self.solutions_ = self.minimal_tmp_ = self.expanded_tmp_ = None

def _ensure_library(self):
# Try to load library from standard
if (library is None):
load_library()

def find_solutions(self, block_header):
assert len(block_header) == 140
self.header_.data = block_header
return library.FindSolutions(self.solver_, self.header_, self.solutions_, 16);

def get_solution(self, num):
assert(num >= 0 and num < 16)
return bytes(ffi.buffer(self.solutions_[num].data))

def validate_solution(self, block_header, solution):
assert len(block_header) == 140
assert len(solution) == 1344
self.header_.data = block_header
self.minimal_tmp_.data = solution
return library.ValidateSolution(self.solver_, self.header_, self.minimal_tmp_);

def list_to_minimal(self, solution):
assert isinstance(solution, (list, tuple))
assert len(solution) == 512

# Convert a list/tuple to an ExpandedSolution instance
data = self.expanded_tmp_.data
for i, idx in enumerate(solution):
data[i] = idx
# Convert expanded to minimal
res = library.ExpandedToMinimal(self.minimal_tmp_, self.expanded_tmp_)
assert res
# Return the relevant bytes
return bytes(ffi.buffer(self.minimal_tmp_))

def minimal_to_list(self, minimal):
assert len(minimal) == 1344
# Convert bytes into a minimal solution
self.minimal_tmp_.data = minimal
# Convert minimal to expanded solution
res = library.MinimalToExpanded(self.expanded_tmp_, self.minimal_tmp_)
assert res
# Convert expanded solution to a result python list
data = self.expanded_tmp_.data
result = [data[i] for i in range(512)]
return result


__all__ = ['Solver', 'load_library']
Loading