diff --git a/.env.example b/.env.example index e2a2f2131f128feed8da526bb363b140fe4e66a9..1c73d5da14dcab7061b51bf2eea1103962a5a378 100644 --- a/.env.example +++ b/.env.example @@ -38,10 +38,10 @@ PUSHER_APP_CLUSTER=mt1 MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" -MONERO_USERNAME= -MONERO_PASSWORD= +COIN=monero RPC_URL=http://127.0.0.1:28080/json_rpc +RPC_USER= +RPC_PASSWORD= -WALLET_ADDRESS= - -GITLAB_URL=https://repo.getmonero.org/api/v4/projects/54 +REPOSITORY_URL=https://repo.getmonero.org/api/v4/projects/54 +GITHUB_ACCESS_TOKEN= diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index dfee7a22ff93bdca1470207b879ce6ed60bf8229..0000000000000000000000000000000000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "storage/app/proposals"] - path = storage/app/proposals - url = https://repo.getmonero.org/monero-project/ccs-proposals.git - branch = master diff --git a/app/Coin/Coin.php b/app/Coin/Coin.php new file mode 100644 index 0000000000000000000000000000000000000000..2a8d60151ba5f16d19a603cd2c24f7ed06f4c9dc --- /dev/null +++ b/app/Coin/Coin.php @@ -0,0 +1,30 @@ +argument('height') ?? Deposit::max('block_received'); + return $wallet->scanIncomingTransfers(max($min_height, 50) - 50); + } + + public function subaddrIndex($addressDetails, $project) + { + return $addressDetails['subaddr_index']; + } +} diff --git a/app/Coin/CoinZcoin.php b/app/Coin/CoinZcoin.php new file mode 100644 index 0000000000000000000000000000000000000000..2ad6cf611ad883d68dcded8b39a492caced74182 --- /dev/null +++ b/app/Coin/CoinZcoin.php @@ -0,0 +1,33 @@ +scanIncomingTransfers()->each(function ($tx) { + $project = Project::where('address', $tx->address)->first(); + if ($project) { + $tx->subaddr_index = $project->subaddr_index; + } + }); + } + + public function subaddrIndex($addressDetails, $project) + { + return $project->id; + } +} diff --git a/app/Console/Commands/GenerateAddresses.php b/app/Console/Commands/GenerateAddresses.php index 9f86d4471ef93fdb9492cf8f0d88843af2ee02ad..f27555a020d25046274e60998753cb1206ff57dc 100644 --- a/app/Console/Commands/GenerateAddresses.php +++ b/app/Console/Commands/GenerateAddresses.php @@ -2,10 +2,10 @@ namespace App\Console\Commands; +use App\Coin\CoinAuto; use App\Project; use Illuminate\Console\Command; use Illuminate\Support\Facades\Storage; -use Monero\WalletOld; class GenerateAddresses extends Command { @@ -40,17 +40,30 @@ class GenerateAddresses extends Command */ public function handle() { + $coin = CoinAuto::newCoin(); + $wallet = $coin->newWallet(); + $projects = Project::whereNotNull('filename')->whereNull('address')->where('state', 'FUNDING-REQUIRED')->get(); - $wallet = new WalletOld(); foreach ($projects as $project) { $addressDetails = $wallet->getPaymentAddress(); + + $address = $addressDetails['address']; + $subaddr_index = $coin->subaddrIndex($addressDetails, $project); + if (Project::where('address', $address)->orWhere('subaddr_index', $subaddr_index)->first()) + { + $this->error('Skipping already used address ' . $address . ' or subaddr_index ' . $subaddr_index); + continue; + } + $project->address_uri = $wallet->createQrCodeString($addressDetails['address']); - $project->address = $addressDetails['address']; - $project->subaddr_index = $addressDetails['subaddr_index']; + $project->address = $address; + $project->subaddr_index = $subaddr_index; Storage::disk('public')->put("/img/qrcodes/{$project->subaddr_index}.png", $project->generateQrcode()); $project->qr_code = "img/qrcodes/{$project->subaddr_index}.png"; $project->raised_amount = 0; $project->save(); + + $this->info('Project: ' . $project->filename . ', address: ' . $project->address); } } diff --git a/app/Console/Commands/MoneroNotify.php b/app/Console/Commands/MoneroNotify.php new file mode 100644 index 0000000000000000000000000000000000000000..ab1f152e254e8e34511f87eb65906a30fd3e62ac --- /dev/null +++ b/app/Console/Commands/MoneroNotify.php @@ -0,0 +1,24 @@ +mergeRequests('merged'); + $merged = $connection->mergeRequests(State::Merged); foreach ($merged as $request) { - $newFiles = $connection->getNewFiles($request->iid); - if (sizeof($newFiles) != 1) { + $newFiles = $connection->getNewFiles($request); + if ($newFiles->count() != 1) { continue; } - $filename = $newFiles[0]; + $filename = $newFiles->first(); if (!preg_match('/.+\.md$/', $filename)) { continue; } if (basename($filename) != $filename) { continue; } - $result[$filename] = $request->web_url; + $result[$filename] = $request->url(); } return $result; diff --git a/app/Console/Commands/UpdateSiteProposals.php b/app/Console/Commands/UpdateSiteProposals.php index f86af2987b7f72e4608062ad2006798a91e25540..a2d90028d2725357d071a2a94b5eac441d3e688a 100644 --- a/app/Console/Commands/UpdateSiteProposals.php +++ b/app/Console/Commands/UpdateSiteProposals.php @@ -3,8 +3,9 @@ namespace App\Console\Commands; use App\Project; +use App\Repository\State; +use App\Repository\Connection; use Illuminate\Console\Command; -use GitLab\Connection; use GuzzleHttp\Client; use stdClass; use Symfony\Component\Yaml\Yaml; @@ -76,38 +77,38 @@ class UpdateSiteProposals extends Command $ideas = []; $connection = new Connection(new Client()); - $mergeRequests = $connection->mergeRequests('opened'); + $mergeRequests = $connection->mergeRequests(State::Opened); foreach ($mergeRequests as $mergeRequest) { - $newFiles = $connection->getNewFiles($mergeRequest->iid); - if (sizeof($newFiles) != 1) { - $this->error ("Skipping MR #$mergeRequest->id '$mergeRequest->title': contains multiple files"); + $newFiles = $connection->getNewFiles($mergeRequest); + if ($newFiles->count() != 1) { + $this->error ("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': contains multiple files"); continue; } - $filename = $newFiles[0]; + $filename = $newFiles->first(); if (!preg_match('/.+\.md$/', $filename)) { - $this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': doesn't contain any .md file"); + $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': doesn't contain any .md file"); continue; } if (basename($filename) != $filename) { - $this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': $filename must be in the root folder"); + $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': $filename must be in the root folder"); continue; } if (in_array($filename, $ideas)) { - $this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': duplicated $filename, another MR #$ideas[$filename]->id"); + $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': duplicated $filename, another MR #$ideas[$filename]->id"); continue; } $project = Project::where('filename', $filename)->first(); if ($project && $this->proposalFileExists($filename)) { - $this->error("Skipping MR #$mergeRequest->id '$mergeRequest->title': already have a project $filename"); + $this->error("Skipping MR #{$mergeRequest->id()} '{$mergeRequest->title()}': already have a project $filename"); continue; } - $this->info("Idea MR #$mergeRequest->id '$mergeRequest->title': $filename"); + $this->info("Idea MR #{$mergeRequest->id()} '{$mergeRequest->title()}': $filename"); $prop = new stdClass(); - $prop->name = htmlspecialchars(trim($mergeRequest->title), ENT_QUOTES); - $prop->{'gitlab-url'} = htmlspecialchars($mergeRequest->web_url, ENT_QUOTES); - $prop->author = htmlspecialchars($mergeRequest->author->username, ENT_QUOTES); - $prop->date = date('F j, Y', strtotime($mergeRequest->created_at)); + $prop->name = htmlspecialchars(trim($mergeRequest->title()), ENT_QUOTES); + $prop->{'gitlab-url'} = htmlspecialchars($mergeRequest->url(), ENT_QUOTES); + $prop->author = htmlspecialchars($mergeRequest->author(), ENT_QUOTES); + $prop->date = date('F j, Y', $mergeRequest->created_at()); $responseProposals[] = $prop; } @@ -119,7 +120,8 @@ class UpdateSiteProposals extends Command { $prop = new stdClass(); $prop->name = $proposal->title; - $prop->{'donate-url'} = url("projects/{$proposal->subaddr_index}/donate"); + $prop->{'donate-address'} = $proposal->address; + $prop->{'donate-qr-code'} = $proposal->address_uri ? $proposal->getQrCodeSrcAttribute() : null; $prop->{'gitlab-url'} = $proposal->gitlab_url; $prop->{'local-url'} = '/proposals/'. pathinfo($proposal->filename, PATHINFO_FILENAME) . '.html'; $prop->contributions = $proposal->contributions; diff --git a/app/Console/Commands/WalletNotify.php b/app/Console/Commands/WalletNotify.php index 890544e6772615f061c574d8c2ab7a97fbd43e1e..1cdd1f397eaf2da999347cb89ed9c7374c2d9030 100644 --- a/app/Console/Commands/WalletNotify.php +++ b/app/Console/Commands/WalletNotify.php @@ -2,28 +2,30 @@ namespace App\Console\Commands; +use App\Coin\CoinAuto; use App\Deposit; use App\Project; use Illuminate\Console\Command; use Monero\Transaction; -use Monero\WalletOld; class walletNotify extends Command { + private $coin; + private $wallet; + /** * The name and signature of the console command. * * @var string */ - protected $signature = 'monero:notify - {height? : Scan wallet transactions starting from the specified height}'; + protected $signature = 'wallet:notify'; /** * The console command description. * * @var string */ - protected $description = 'Checks the monero blockchain for transactions'; + protected $description = 'Checks the blockchain for transactions'; /** * Create a new command instance. @@ -33,6 +35,9 @@ class walletNotify extends Command public function __construct() { parent::__construct(); + + $this->coin = CoinAuto::newCoin(); + $this->wallet = $this->coin->newWallet(); } /** @@ -42,18 +47,15 @@ class walletNotify extends Command */ public function handle() { - $wallet = new WalletOld(); - - $blockheight = $wallet->blockHeight(); + $blockheight = $this->wallet->blockHeight(); if ($blockheight < 1) { - $this->error('monero daemon down or wrong port in db ?'); + $this->error('failed to fetch blockchain height'); return; } - $min_height = $this->argument('height') ?? Deposit::max('block_received'); - $transactions = $wallet->scanIncomingTransfers(max($min_height, 50) - 50); - $transactions->each(function ($transaction) use ($wallet) { + $transactions = $this->coin->onNotifyGetTransactions($this, $this->wallet); + $transactions->each(function ($transaction) { $this->processPayment($transaction); }); @@ -67,6 +69,9 @@ class walletNotify extends Command */ public function processPayment(Transaction $transaction) { + $amountCoins = $transaction->amount / 10 ** $this->wallet->digitsAfterTheRadixPoint(); + $details = 'address: ' . $transaction->address . ' amount: '. $amountCoins . ' txid: '.$transaction->id; + $deposit = Deposit::where('tx_id', $transaction->id)->where('subaddr_index', $transaction->subaddr_index)->first(); if ($deposit) { if ($deposit->block_received == 0) { @@ -76,16 +81,15 @@ class walletNotify extends Command return null; } - $this->info('amount: '.$transaction->amount / 1000000000000 .' confirmations:'.$transaction->confirmations.' tx_hash:'.$transaction->id); - $this->info('subaddr_index: '.$transaction->subaddr_index); - $this->createDeposit($transaction); - $project = Project::where('subaddr_index', $transaction->subaddr_index)->first(); - if ($project) { + if ($project = Project::where('subaddr_index', $transaction->subaddr_index)->first()) { // update the project total - $project->raised_amount = $project->raised_amount + $transaction->amount * 1e-12; + $project->raised_amount = $project->raised_amount + $amountCoins; $project->save(); + $this->info('Donation to "' . $project->filename . '" '. $details); + } else { + $this->error('Unrecognized donation, ' . $details); } return; @@ -122,7 +126,7 @@ class walletNotify extends Command */ public function updateConfirmation($blockheight, Deposit $deposit) { - $diff = $blockheight - $deposit->block_received; + $diff = $blockheight - $deposit->block_received + 1; $deposit->confirmations = $diff; $deposit->save(); diff --git a/app/Project.php b/app/Project.php index d5c1e20f3cdbb2e714c6fc5f5a147b7f60ec4d72..d7e19bfe7c6871200fe1fb1a09980a8a1294708e 100644 --- a/app/Project.php +++ b/app/Project.php @@ -46,10 +46,6 @@ class Project extends Model return $this->hasMany(Deposit::class, 'subaddr_index', 'subaddr_index'); } - public function getAmountReceivedAttribute() { - return $this->deposits->sum('amount') * 1e-12; - } - public function getPercentageFundedAttribute() { return min(100, round($this->raised_amount / $this->target_amount * 100)); } diff --git a/app/Repository/Connection.php b/app/Repository/Connection.php new file mode 100644 index 0000000000000000000000000000000000000000..8f9d89ba68ac2529d5e2cda68a9a6b0547b5b8b7 --- /dev/null +++ b/app/Repository/Connection.php @@ -0,0 +1,28 @@ +repo = new Github($client, $url); + } else { + $this->repo = new Gitlab($client, $url); + } + } + + public function mergeRequests($state) { + return $this->repo->mergeRequests($state); + } + + public function getNewFiles($merge_request_iid) { + return $this->repo->getNewFiles($merge_request_iid); + } +} diff --git a/app/Repository/Github.php b/app/Repository/Github.php new file mode 100644 index 0000000000000000000000000000000000000000..6a77a6e60f14f7d91c94f965032c7af7c082d80c --- /dev/null +++ b/app/Repository/Github.php @@ -0,0 +1,89 @@ +pull_request = $pull_request; + } + + public function id() : int + { + return $this->pull_request->number; + } + + public function url() : string + { + return $this->pull_request->html_url; + } + + public function title() : string + { + return $this->pull_request->title; + } + + public function author() : string + { + return $this->pull_request->user->login; + } + + public function created_at() : int + { + return strtotime($this->pull_request->created_at); + } +} + +class Github implements Repository +{ + private $client; + private $options = []; + private $owner_repo; + private const stateToString = [ State::Opened => 'open' , + State::Merged => 'closed']; + + public function __construct(Client $client, string $repository_url) + { + $this->client = $client; + $this->owner_repo = parse_url($repository_url, PHP_URL_PATH); + if ($token = env('GITHUB_ACCESS_TOKEN')) { + $this->options = ['headers' => ['Authorization' => 'token ' . $token]]; + } + } + + private function getUrl($url) + { + return $this->client->request('GET', $url, $this->options); + } + + public function mergeRequests($state) + { + $url = 'https://api.github.com/repos' . $this->owner_repo . '/pulls?state=' . Self::stateToString[$state]; + $response = $this->getUrl($url); + $result = collect(json_decode($response->getBody())); + if ($state == State::Merged) { + $result = $result->filter(function ($pull_request) { + return $pull_request->merged_at !== null; + }); + } + return $result->map(function ($pull_request) { + return new PullRequest($pull_request); + }); + } + + public function getNewFiles($pull_request) + { + $url = 'https://api.github.com/repos' . $this->owner_repo . '/pulls/' . $pull_request->id() . '/files'; + $response = $this->getUrl($url); + return collect(json_decode($response->getBody()))->filter(function ($change) { + return $change->status == 'added'; + })->map(function ($change) { + return $change->filename; + }); + } +} diff --git a/app/Repository/Gitlab.php b/app/Repository/Gitlab.php new file mode 100644 index 0000000000000000000000000000000000000000..940387b2c5812faf71b30a162170228f4b03cc5e --- /dev/null +++ b/app/Repository/Gitlab.php @@ -0,0 +1,79 @@ +merge_request = $merge_request; + } + + public function iid() : int + { + return $this->merge_request->iid; + } + + public function id() : int + { + return $this->merge_request->id; + } + + public function url() : string + { + return $this->merge_request->web_url; + } + + public function title() : string + { + return $this->merge_request->title; + } + + public function author() : string + { + return $this->merge_request->author->username; + } + + public function created_at() : int + { + return strtotime($this->merge_request->created_at); + } +} + +class Gitlab implements Repository +{ + private $client; + private $base_url; + private const stateToString = [ State::Opened => 'opened', + State::Merged => 'merged']; + + public function __construct(Client $client, string $repository_url) + { + $this->client = $client; + $this->base_url = $repository_url; + } + + public function mergeRequests($state) + { + $url = $this->base_url . '/merge_requests?scope=all&per_page=50&state=' . Self::stateToString[$state]; + $response = $this->client->request('GET', $url); + return collect(json_decode($response->getBody()))->map(function ($merge_request) { + return new MergeRequest($merge_request); + }); + } + + public function getNewFiles($merge_request) + { + $url = $this->base_url . '/merge_requests/' . $merge_request->iid() . '/changes'; + $response = $this->client->request('GET', $url); + return collect(json_decode($response->getBody())->changes)->filter(function ($change) { + return $change->new_file; + })->map(function ($change) { + return $change->new_path; + }); + } +} diff --git a/app/Repository/Repository.php b/app/Repository/Repository.php new file mode 100644 index 0000000000000000000000000000000000000000..34500dda0d2381cff364b9ed8726d9d64648ebd9 --- /dev/null +++ b/app/Repository/Repository.php @@ -0,0 +1,28 @@ +client = $client; - } - - public function mergeRequests($state = 'all') { - - $url = env('GITLAB_URL') . '/merge_requests?scope=all&per_page=50&state='. $state; - - $response = $this->client->request('GET', $url); - - return collect(json_decode($response->getBody())); - } - - public function getNewFiles($merge_request_iid) { - $url = env('GITLAB_URL') . '/merge_requests/' . $merge_request_iid . '/changes'; - $response = $this->client->request('GET', $url); - $deserialized = collect(json_decode($response->getBody())); - - $result = []; - foreach ($deserialized['changes'] as $change) { - if ($change->new_file) { - $result[] = $change->new_path; - } - } - - return $result; - } - - -} diff --git a/monero/WalletCommon.php b/monero/WalletCommon.php new file mode 100644 index 0000000000000000000000000000000000000000..1e736c3b0fa1f55ca4a8cc1e4fe6cc69c6d488c3 --- /dev/null +++ b/monero/WalletCommon.php @@ -0,0 +1,12 @@ +client = $client ?: new jsonRPCClient(env('RPC_URL')); + $this->client = $client ?: new jsonRPCClient([ 'username' => env('RPC_USER'), + 'password' => env('RPC_PASSWORD'), + 'url' => env('RPC_URL')]); } /** @@ -90,7 +97,7 @@ class WalletOld * * @return int */ - public function blockHeight() + public function blockHeight() : int { return $this->client->blockHeight(); } diff --git a/monero/WalletZcoin.php b/monero/WalletZcoin.php new file mode 100644 index 0000000000000000000000000000000000000000..6d3efc4795e1368c61e3f483905ac106ca06b3dd --- /dev/null +++ b/monero/WalletZcoin.php @@ -0,0 +1,84 @@ +rpc = new jsonRpcBase([ 'auth_type' => 'basic', + 'username' => env('RPC_USER'), + 'password' => env('RPC_PASSWORD'), + 'url' => env('RPC_URL')]); + } + + public function getPaymentAddress() + { + return ['address' => $this->rpc->request('getnewaddress')]; + } + + private function decodeTxAmount(string $tx_amount) : int + { + $tx_amount = str_replace(',', '.', $tx_amount); + + $amount = explode('.', $tx_amount); + if (sizeof($amount) < 1 || sizeof($amount) > 2) { + throw new \Exception('Failed to decode tx amount ' . $tx_amount); + } + + $fraction = $amount[1] ?? ""; + if (strlen($fraction) > $this->digitsAfterTheRadixPoint()) { + throw new \Exception('Failed to decode tx amount, too many digits after the redix point ' . $tx_amount); + } + + $amount = $amount[0] . str_pad($fraction, $this->digitsAfterTheRadixPoint(), '0'); + $amount = intval($amount); + if ($amount == 0) { + throw new \Exception('Failed to convert tx amount to int ' . $tx_amount); + } + + return $amount; + } + + public function scanIncomingTransfers($skip_txes = 0) + { + return collect($this->rpc->request('listtransactions', ['', 100, $skip_txes]))->filter(function ($tx) { + return $tx['category'] == 'receive'; + })->map(function ($tx) { + return new Transaction( + $tx['txid'], + $this->decodeTxAmount($tx['amount']), + $tx['address'], + $tx['confirmations'], + 0, + Carbon::now(), + 0, + isset($tx['blockhash']) ? $this->blockHeightByHash($tx['blockhash']) : 0 + ); + }); + } + + public function blockHeight() : int + { + return $this->rpc->request('getblockcount'); + } + + public function createQrCodeString($address, $amount = null) : string + { + return 'zcoin:' . $address . ($amount ? '?amount=' . $amount : ''); + } + + private function blockHeightByHash($block_hash) : int + { + return $this->rpc->request('getblockheader', [$block_hash])['height']; + } +} diff --git a/monero/jsonRPCClient.php b/monero/jsonRPCClient.php index 836914fb1d2f8fc3415bdcccdf3eb14b79b29cc0..78208e09d9bfdcdba977b693c1f9f14fbdb7911e 100644 --- a/monero/jsonRPCClient.php +++ b/monero/jsonRPCClient.php @@ -12,18 +12,7 @@ use Illuminate\Support\Facades\Log; */ class jsonRPCClient implements Contracts\WalletManager { - - /** @var string */ - private $username = 'test2'; - - /** @var string */ - private $password = 'test2'; - - /** @var string */ - private $url = 'http://127.0.0.1:28080/json_rpc'; - - /** @var Client|null */ - private $client; + private $rpc; /** * JsonRPCClient constructor. @@ -32,20 +21,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function __construct($options, $client = null) { - $this->username = $options['username'] ?? $this->username; - $this->password = $options['password'] ?? $this->password; - $this->url = $options['url'] ?? $this->url; - - if (empty($client)) { - $client = new Client([ - 'base_uri' => $this->url, - 'headers' => [ - 'Content-Type' => 'application/json', - ] - ]); - } - - $this->client = $client; + $this->rpc = new jsonRpcBase($options, $client); } /** @@ -55,7 +31,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function balance() : int { - $response = $this->request('get_balance'); + $response = $this->rpc->request('get_balance'); return $response['balance']; } @@ -66,7 +42,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function unlockedBalance() : int { - $response = $this->request('get_balance'); + $response = $this->rpc->request('get_balance'); return $response['unlocked_balance']; } @@ -77,7 +53,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function address() : string { - $response = $this->request('get_address'); + $response = $this->rpc->request('get_address'); return $response['address']; } @@ -88,7 +64,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function blockHeight() : int { - $response = $this->request('get_height'); + $response = $this->rpc->request('get_height'); return $response['height']; } @@ -102,7 +78,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function createSubaddress($account_index = 0, $label = '') : array { - $response = $this->request('create_address', ['account_index' => $account_index, 'label' => $label]); + $response = $this->rpc->request('create_address', ['account_index' => $account_index, 'label' => $label]); return $response; } @@ -113,7 +89,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function incomingTransfers($min_height = 0) : array { - $response = $this->request('get_transfers', ['pool' => true, 'in' => true, 'min_height' => $min_height, 'filter_by_height' => $min_height > 0 ? true : false]); + $response = $this->rpc->request('get_transfers', ['pool' => true, 'in' => true, 'min_height' => $min_height, 'filter_by_height' => $min_height > 0 ? true : false]); return $response; } @@ -128,7 +104,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function payments($paymentIds, $minHeight) : array { - $response = $this->request('get_bulk_payments', ['payment_ids' => $paymentIds, 'min_block_height' => $minHeight]); + $response = $this->rpc->request('get_bulk_payments', ['payment_ids' => $paymentIds, 'min_block_height' => $minHeight]); return $response; } @@ -144,7 +120,7 @@ class jsonRPCClient implements Contracts\WalletManager */ public function createUri($address, $amount = null, $paymentId = null) : string { - $response = $this->request('make_uri', ['address' => $address, 'amount' => $amount, 'payment_id' => $paymentId]); + $response = $this->rpc->request('make_uri', ['address' => $address, 'amount' => $amount, 'payment_id' => $paymentId]); return $response['uri']; } @@ -158,60 +134,4 @@ class jsonRPCClient implements Contracts\WalletManager { return bin2hex(openssl_random_pseudo_bytes(32)); } - - /** - * Sets up the request data body - * - * @param string $method name of the rpc command - * @param array $params associative array of variables being passed to the method - * - * @return false|string will return a json string or false - */ - private function preparePayload($method, $params) - { - $payload = [ - 'jsonrpc' => '2.0', - 'id' => '0', - 'method' => $method, - 'params' => $params, - ]; - return json_encode($payload); - } - - /** - * Send off request to rpc server - * - * @param string $method name of the rpc command - * @param array $params associative array of variables being passed to the method - * - * @return mixed the rpc query result - * - * @throws \RuntimeException - */ - protected function request(string $method, array $params = []) - { - $payload = $this->preparePayload($method, $params); - - try { - $response = $this->client->request('POST', '',[ - 'auth' => [$this->username, $this->password, 'digest'], - 'body' => $payload, - 'headers' => [ - 'Content-Type' => 'application/json', - ] - ]); - - $body = $response->getBody(); - } catch (GuzzleException $exception) { - Log::error($exception); - throw new \RuntimeException('Connection to node unsuccessful'); - } - $result = json_decode((string) $body, true); - if (isset($result['error'])) { - - throw new \RuntimeException($result['error']['message']); - } - return $result['result']; - } - } diff --git a/monero/jsonRpcBase.php b/monero/jsonRpcBase.php new file mode 100644 index 0000000000000000000000000000000000000000..16a9dcdb4793dd2cce4f8772b1a96a0820f42ea6 --- /dev/null +++ b/monero/jsonRpcBase.php @@ -0,0 +1,104 @@ +username = $options['username'] ?? $this->username; + $this->password = $options['password'] ?? $this->password; + $this->url = $options['url'] ?? $this->url; + $this->auth_type = $options['auth_type'] ?? 'digest'; + + if (empty($client)) { + $client = new Client([ + 'base_uri' => $this->url, + 'headers' => [ + 'Content-Type' => 'application/json', + ] + ]); + } + + $this->client = $client; + } + + /** + * Sets up the request data body + * + * @param string $method name of the rpc command + * @param array $params associative array of variables being passed to the method + * + * @return false|string will return a json string or false + */ + private function preparePayload($method, $params) + { + $payload = [ + 'jsonrpc' => '2.0', + 'id' => '0', + 'method' => $method, + 'params' => $params, + ]; + return json_encode($payload); + } + + /** + * Send off request to rpc server + * + * @param string $method name of the rpc command + * @param array $params associative array of variables being passed to the method + * + * @return mixed the rpc query result + * + * @throws \RuntimeException + */ + public function request(string $method, array $params = []) + { + $payload = $this->preparePayload($method, $params); + + try { + $response = $this->client->request('POST', '',[ + 'auth' => [$this->username, $this->password, $this->auth_type], + 'body' => $payload, + 'headers' => [ + 'Content-Type' => 'application/json', + ] + ]); + + $body = $response->getBody(); + } catch (GuzzleException $exception) { + Log::error($exception); + error_log($exception); + throw new \RuntimeException('Connection to node ' . $this->url . ' unsuccessful'); + } + $result = json_decode((string) $body, true); + if (isset($result['error'])) { + + throw new \RuntimeException($result['error']['message']); + } + return $result['result']; + } + +} diff --git a/readme.md b/readme.md index 88c9ba12e352c31e16783d653576b71422524c84..d5036c6eddf59af3997a29a21e305a1037905af9 100644 --- a/readme.md +++ b/readme.md @@ -15,18 +15,19 @@ php >= 7.1 ``` apt update -apt install -y jekyll mysql-server nginx php php-curl php-fpm php-gd php-mbstring php-mysql php-xml unzip +apt install -y cron git jekyll mysql-server nginx php php-curl php-fpm php-gd php-mbstring php-mysql php-xml unzip ``` Install `Composer` following the instructions at https://getcomposer.org/download/ +Checkout and configure CCS backend, frontend and proposals repositories (replace ``, ``, `` with the actual URLs) ``` cd /var/www/html -git clone --recursive https://repo.getmonero.org/monero-project/ccs-back.git -git -C ccs-back/storage/app/proposals checkout master +git clone +git clone +git clone ccs-back/storage/app/proposals -git clone https://repo.getmonero.org/monero-project/ccs-front.git rm -rf ccs-front/proposals ln -s /var/www/html/ccs-back/storage/app/proposals ccs-front/proposals ln -fs /var/www/html/ccs-back/storage/app/proposals.json ccs-front/_data/proposals.json @@ -37,16 +38,13 @@ composer update cp .env.example .env ``` -Run Monero RPC wallet in background with `--disable-rpc-login` option -Example: -``` -./monero-wallet-rpc --wallet-file=wallet --password=secret --disable-rpc-login --rpc-bind-port=28080 -``` - Spin up MYSQL server, create new database, user and grant user access to it -Open `.env` in editor of choice and edit the following lines: +Open `.env` in editor of choice and edit the following lines: +> `COIN` - choose one of supported coins: `monero` or `zcoin` +> `REPOSITORY_URL` - CCS proposals Github URL or GitLab API endpoint (e.g. https://\/api/v4/projects/\)> +> `GITHUB_ACCESS_TOKEN` - leave empty if you are not using Github or visit https://github.com/settings/tokens to generate new `public_repo` token ``` -APP_URL=http:// +APP_URL=http:// DB_CONNECTION=mysql DB_HOST=127.0.0.1 @@ -56,8 +54,13 @@ DB_USERNAME= DB_PASSWORD= RPC_URL=http://127.0.0.1:28080/json_rpc +RPC_USER= +RPC_PASSWORD= -GITLAB_URL=https://repo.getmonero.org/api/v4/projects/54 +COIN= + +REPOSITORY_URL= +GITHUB_ACCESS_TOKEN= ``` Initialize the system @@ -76,11 +79,11 @@ chown -R www-data ccs-back/ chown -R www-data ccs-front/ ``` -Remove Nginx example config +Remove Nginx example config ``` rm /etc/nginx/sites-enabled/default ``` -Create new file `/etc/nginx/sites-enabled/ccs` in editor of choice and paste the following lines replacing `` and `` with appropriate values +Create new file `/etc/nginx/sites-enabled/ccs` in editor of choice and paste the following lines replacing `` and `` with appropriate values ``` server { @@ -88,15 +91,15 @@ server { listen [::]:80 default_server; root /var/www/html/ccs-front/_site/; index index.php index.html; - server_name ; - + server_name ; + location / { try_files $uri $uri/ /index.php?$query_string; } - + # pass the PHP scripts to FastCGI server # - + location ~ \.php$ { root /var/www/html/ccs-back/public/; include snippets/fastcgi-php.conf; @@ -120,7 +123,7 @@ Instead of scheduling a cron job you can run the following commands in no partic ``` php /var/www/html/ccs-back/artisan proposal:process php /var/www/html/ccs-back/artisan generate:addresses - php /var/www/html/ccs-back/artisan monero:notify + php /var/www/html/ccs-back/artisan wallet:notify php /var/www/html/ccs-back/artisan proposal:update ``` 2. Process incoming donations @@ -128,11 +131,11 @@ Instead of scheduling a cron job you can run the following commands in no partic ``` php /var/www/html/ccs-back/artisan monero:notify ``` -3. Generate static HTML files +1. Generate static HTML files ``` jekyll build --source /var/www/html/ccs-front --destination /var/www/html/ccs-front/_site ``` -4. Get the full list of processed transactions in JSON format +2. Get the full list of processed transactions in JSON format ``` php /var/www/html/ccs-back/artisan deposit:list ``` diff --git a/storage/app/proposals b/storage/app/proposals deleted file mode 160000 index 4459b497ee672bcc7e0848f2da7eed93152e4e06..0000000000000000000000000000000000000000 --- a/storage/app/proposals +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 4459b497ee672bcc7e0848f2da7eed93152e4e06