Files
peyawallet/lib/services/linux_p2pool_service.dart
T
Codex Bot 36306238f8
build / Build Windows wallet (push) Failing after 1m39s
build / Build Linux wallet (push) Successful in 2m20s
Normalize peyawallet data paths and CI staging
2026-04-09 18:25:08 +02:00

263 lines
6.8 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:logger/logger.dart';
import 'package:path/path.dart' as p;
import 'app_paths.dart';
import 'p2pool_service.dart';
class LinuxP2poolService implements P2poolService {
LinuxP2poolService({required Logger logger}) : _logger = logger;
final Logger _logger;
Future<bool>? _startFuture;
P2poolConfig? _lastConfig;
@override
Future<bool> isRunning({P2poolConfig? config}) async {
final resolved = _resolveConfig(config);
final pid = await _readPid();
if (pid != null && _isProcessAlive(pid)) {
return true;
}
return _isStratumAvailable(resolved);
}
@override
Future<bool> start({required P2poolConfig config}) async {
return _startP2pool(config);
}
@override
Future<bool> stop() async {
final pidFile = File(await _pidFilePath());
if (!await pidFile.exists()) {
_logger.w('P2Pool pidfile not found at ${pidFile.path}');
return false;
}
final rawPid = await pidFile.readAsString();
final pid = int.tryParse(rawPid.trim());
if (pid == null) {
_logger.w('P2Pool pidfile invalid: $rawPid');
return false;
}
if (!_isProcessAlive(pid)) {
try {
await pidFile.delete();
} catch (_) {}
return true;
}
var stopped = Process.killPid(pid, ProcessSignal.sigterm);
if (!stopped) {
_logger.w('Failed to terminate P2Pool process ($pid)');
return false;
}
if (!await _waitForExit(pid)) {
Process.killPid(pid, ProcessSignal.sigkill);
}
stopped = await _waitForExit(pid);
if (stopped) {
try {
await pidFile.delete();
} catch (_) {}
}
return stopped;
}
@override
Future<bool> restart({required P2poolConfig config}) async {
await stop();
return _startP2pool(config);
}
Future<bool> _startP2pool(P2poolConfig config) async {
if (_startFuture != null) {
return _startFuture!;
}
_startFuture = _startP2poolInternal(config);
try {
return await _startFuture!;
} finally {
_startFuture = null;
}
}
Future<bool> _startP2poolInternal(P2poolConfig config) async {
if (config.walletAddress.trim().isEmpty) {
_logger.w('P2Pool wallet address missing.');
return false;
}
if (await _isStratumAvailable(config)) {
return true;
}
_lastConfig = config;
final binary = await _locateBinary();
if (binary == null) {
_logger.w(
'P2Pool binary not found. Expected external/p2pool/p2pool-salvium');
return false;
}
final pidFile = await _pidFilePath();
final dataDir = await _dataDirPath();
final args = <String>[
'--wallet',
config.walletAddress,
'--host',
config.host,
'--rpc-port',
config.rpcPort.toString(),
'--zmq-port',
config.zmqPort.toString(),
'--data-dir',
dataDir,
'--data-api',
dataDir,
'--local-api',
];
if (config.stratum.trim().isNotEmpty) {
args.addAll(['--stratum', config.stratum.trim()]);
}
if (config.p2p.trim().isNotEmpty) {
args.addAll(['--p2p', config.p2p.trim()]);
}
if (config.startMining) {
final threads = config.miningThreads.clamp(1, 64).toInt();
args.addAll(['--start-mining', threads.toString()]);
}
try {
final process = await Process.start(
binary,
args,
workingDirectory: Directory.current.path,
mode: ProcessStartMode.detached,
);
await File(pidFile).writeAsString(process.pid.toString());
return _waitForStratum(config, process.pid);
} catch (error) {
_logger.w('Failed to start P2Pool: $error');
return false;
}
}
Future<bool> _waitForStratum(P2poolConfig config, int pid) async {
const attempts = 20;
for (var i = 0; i < attempts; i++) {
if (!_isProcessAlive(pid)) {
_logger.w('P2Pool process exited before it became ready.');
return false;
}
if (await _isStratumAvailable(config)) {
return true;
}
await Future.delayed(const Duration(seconds: 1));
}
_logger.w('P2Pool did not become available after ${attempts}s.');
return _isProcessAlive(pid);
}
Future<bool> _waitForStop(P2poolConfig config) async {
const attempts = 10;
for (var i = 0; i < attempts; i++) {
if (!await _isStratumAvailable(config)) {
return true;
}
await Future.delayed(const Duration(seconds: 1));
}
return false;
}
Future<bool> _waitForExit(int pid) async {
const attempts = 10;
for (var i = 0; i < attempts; i++) {
if (!_isProcessAlive(pid)) {
return true;
}
await Future.delayed(const Duration(milliseconds: 500));
}
return !_isProcessAlive(pid);
}
Future<bool> _isStratumAvailable(P2poolConfig config) async {
final target = _resolveStratumTarget(config.stratum);
if (target == null) {
return false;
}
try {
final socket = await Socket.connect(
target.host,
target.port,
timeout: const Duration(seconds: 1),
);
socket.destroy();
return true;
} catch (_) {
return false;
}
}
P2poolConfig _resolveConfig(P2poolConfig? config) {
return config ?? _lastConfig ?? const P2poolConfig(walletAddress: '');
}
Future<int?> _readPid() async {
final pidFile = File(await _pidFilePath());
if (!await pidFile.exists()) {
return null;
}
final rawPid = await pidFile.readAsString();
return int.tryParse(rawPid.trim());
}
bool _isProcessAlive(int pid) {
final proc = Directory('/proc/$pid');
return proc.existsSync();
}
({String host, int port})? _resolveStratumTarget(String raw) {
final trimmed = raw.trim();
if (trimmed.isEmpty) {
return null;
}
final first = trimmed.split(',').first.trim();
final match = RegExp(r'^(.*):(\d+)$').firstMatch(first);
if (match == null) {
return null;
}
var host = match.group(1) ?? '';
final port = int.tryParse(match.group(2) ?? '') ?? 0;
if (port <= 0) {
return null;
}
if (host == '0.0.0.0' || host == '*') {
host = '127.0.0.1';
} else if (host == '::' || host == '[::]') {
host = '::1';
}
return (host: host, port: port);
}
Future<String?> _locateBinary() async {
final cwd = Directory.current.path;
final candidates = [
p.join(cwd, 'external', 'p2pool', 'p2pool-salvium'),
p.join(cwd, '..', 'external', 'p2pool', 'p2pool-salvium'),
];
for (final candidate in candidates) {
final file = File(candidate);
if (await file.exists()) {
return file.path;
}
}
return null;
}
Future<String> _dataDirPath() async {
return (await AppPaths.p2poolDir()).path;
}
Future<String> _pidFilePath() async {
return (await AppPaths.p2poolPidFile()).path;
}
}