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
1 change: 1 addition & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
</testsuites>
<php>
<env name="WALLET_RPC_URL" value="http://127.0.0.1:18083/json_rpc"/>
<env name="WALLET_RPC_URL_2" value="http://127.0.0.1:18086/json_rpc"/>
<env name="WALLET_RPC_URL_AUTH" value="http://127.0.0.1:18084/json_rpc"/>
<env name="DAEMON_RPC_URL" value="http://127.0.0.1:18081/json_rpc"/>
<env name="DAEMON_RPC_URL_AUTH" value="http://127.0.0.1:18085/json_rpc"/>
Expand Down
3 changes: 3 additions & 0 deletions src/WalletRpc/IsMultisigResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class IsMultisigResponse implements JsonDataSerializable
#[Json]
public bool $ready;

#[Json('kex_is_done')]
public bool $kexIsDone;

/**
* Amount of signature needed to sign a transfer.
*/
Expand Down
37 changes: 37 additions & 0 deletions src/WalletRpc/SetSubaddressLookaheadRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace RefRing\MoneroRpcPhp\WalletRpc;

use RefRing\MoneroRpcPhp\Request\ParameterInterface;
use RefRing\MoneroRpcPhp\Request\RpcRequest;
use RefRing\MoneroRpcPhp\Trait\JsonSerializeBigInt;
use Square\Pjson\Json;
use Square\Pjson\JsonDataSerializable;

/**
* Set the subaddress lookahead depth for scanning.
*/
class SetSubaddressLookaheadRequest implements ParameterInterface, JsonDataSerializable
{
use JsonSerializeBigInt;

#[Json('password', omit_empty: true)]
public ?string $password;

#[Json('major_idx')]
public int $major;

#[Json('minor_idx')]
public int $minor;

public static function create(int $major, int $minor, ?string $password = null): RpcRequest
{
$self = new self();
$self->password = $password;
$self->major = $major;
$self->minor = $minor;
return new RpcRequest('set_subaddress_lookahead', $self);
}
}
16 changes: 16 additions & 0 deletions src/WalletRpc/SetSubaddressLookaheadResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace RefRing\MoneroRpcPhp\WalletRpc;

use RefRing\MoneroRpcPhp\Trait\JsonSerializeBigInt;
use Square\Pjson\JsonDataSerializable;

/**
* Set the subaddress lookahead depth for scanning.
*/
class SetSubaddressLookaheadResponse implements JsonDataSerializable
{
use JsonSerializeBigInt;
}
15 changes: 15 additions & 0 deletions src/WalletRpcClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@
use RefRing\MoneroRpcPhp\WalletRpc\SetAccountTagDescriptionResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetAttributeRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetAttributeResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetSubaddressLookaheadRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetSubaddressLookaheadResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetDaemonRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetDaemonResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetTxNotesRequest;
Expand Down Expand Up @@ -1547,4 +1549,17 @@ public function getDefaultFeePriority(): GetDefaultFeePriorityResponse
{
return $this->handleRequest(GetDefaultFeePriorityRequest::create(), GetDefaultFeePriorityResponse::class);
}

/**
* Set the subaddress lookahead depth for scanning.
*
* @param int $major The number of accounts to look ahead.
* @param int $minor The number of subaddresses to look ahead per account.
* @param ?string $password Wallet password.
* @throws MoneroRpcException
*/
public function setSubaddressLookahead(int $major, int $minor, ?string $password = null): SetSubaddressLookaheadResponse
{
return $this->handleRequest(SetSubaddressLookaheadRequest::create($major, $minor, $password), SetSubaddressLookaheadResponse::class);
}
}
6 changes: 6 additions & 0 deletions tests/TestHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ class TestHelper
final public const WALLET_PWD_1 = 'b4r0l0';
final public const WALLET_PWD_2 = 'b4r0l0-2';

/**
* Subaddress at index (0, 999) for WALLET_1_MNEMONIC
* Used to test set_subaddress_lookahead functionality
*/
final public const MAINNET_SUBADDRESS_0_999 = '8A6tQYX2JX2CSvEZAEtPpMa3NwFNDLtRFCaSEcigzNQn53H8H5dkPFHHYwaTxjVw4NFV9xuahPqjmDbnVfhbFb3T1pKv6xH';

public static function getRandomWalletName(): string
{
return hash('sha256', (string) random_int(0, PHP_INT_MAX));
Expand Down
11 changes: 11 additions & 0 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ services:
ports:
- "18083:18083"

monero-wallet-rpc-2:
image: ghcr.io/refring/monero-containers/monero-wallet-rpc:${MONERO_VERSION:-0.18.4.4}
environment:
MONERO_DAEMON_ADDRESS: monerod:18081
MONERO_DAEMON_HOST: monerod:18081
WALLET_RPC_PORT: 18086
depends_on:
- "monerod"
ports:
- "18086:18086"

monerod-rpc-authentication:
image: ghcr.io/refring/monero-containers/monerod:${MONERO_VERSION:-0.18.4.4}
depends_on:
Expand Down
41 changes: 41 additions & 0 deletions tests/integration/BasicWalletTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
use RefRing\MoneroRpcPhp\Exception\TagNotFoundException;
use RefRing\MoneroRpcPhp\Exception\WalletExistsException;
use RefRing\MoneroRpcPhp\Model\Address;
use RefRing\MoneroRpcPhp\Tests\Attribute\RequiresMoneroVersion;
use RefRing\MoneroRpcPhp\Tests\Trait\RequiresMoneroVersionTrait;
use RefRing\MoneroRpcPhp\Model\Amount;
use RefRing\MoneroRpcPhp\Tests\KeyPairHelper;
use RefRing\MoneroRpcPhp\Tests\TestHelper;
Expand All @@ -36,6 +38,8 @@

class BasicWalletTest extends TestCase
{
use RequiresMoneroVersionTrait;

private static WalletRpcClient $rpcClient;
private static DaemonRpcClient $daemonRpcClient;

Expand All @@ -57,6 +61,16 @@ public static function setUpBeforeClass(): void
self::$daemonRpcClient = (new ClientBuilder(getenv('DAEMON_RPC_URL')))->buildDaemonClient();
}

protected function setUp(): void
{
$this->checkMoneroVersionRequirements();
}

protected static function getDaemonRpcClient(): DaemonRpcClient
{
return self::$daemonRpcClient;
}

public function testGetVersion(): void
{
$version = self::$rpcClient->getVersion();
Expand Down Expand Up @@ -557,4 +571,31 @@ public function testQueryKey(string $seed): void
$result = self::$rpcClient->queryKey(QueryKeyType::MNEMONIC);
$this->assertSame($seed, $result->key);
}

#[RequiresMoneroVersion('0.18.4.1')]
public function testUpdateLookahead(): void
{
// Restore the test wallet
self::$rpcClient->restoreDeterministicWallet('', '', TestHelper::WALLET_1_MNEMONIC);

// The subaddress at index (0, 999) should not be accessible with default lookahead
$address0_999 = new Address(TestHelper::MAINNET_SUBADDRESS_0_999);

// Verify the address is not in the current lookahead table
$addressNotFound = false;
try {
self::$rpcClient->getAddressIndex($address0_999);
} catch (AddressNotInWalletException) {
$addressNotFound = true;
}
$this->assertTrue($addressNotFound, 'Address at index (0, 999) should not be in the lookahead table before extending it');

// Update the lookahead and verify the high index address is now in the table
self::$rpcClient->setSubaddressLookahead(50, 1000);

$result = self::$rpcClient->getAddressIndex($address0_999);
$this->assertSame(0, $result->index->major);
$this->assertSame(999, $result->index->minor);
}

}
84 changes: 81 additions & 3 deletions tests/integration/TransferTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,29 @@
use PHPUnit\Framework\TestCase;
use RefRing\MoneroRpcPhp\ClientBuilder;
use RefRing\MoneroRpcPhp\DaemonRpcClient;
use RefRing\MoneroRpcPhp\Exception\AddressNotInWalletException;
use RefRing\MoneroRpcPhp\Exception\InvalidDestinationException;
use RefRing\MoneroRpcPhp\Model\Address;
use RefRing\MoneroRpcPhp\Model\Amount;
use RefRing\MoneroRpcPhp\Tests\Attribute\RequiresMoneroVersion;
use RefRing\MoneroRpcPhp\Tests\TestHelper;
use RefRing\MoneroRpcPhp\Tests\Trait\RequiresMoneroVersionTrait;
use RefRing\MoneroRpcPhp\WalletRpc\Model\Destination;
use RefRing\MoneroRpcPhp\WalletRpc\Model\TransferType;
use RefRing\MoneroRpcPhp\WalletRpc\TransferResponse;
use RefRing\MoneroRpcPhp\WalletRpcClient;

final class TransferTest extends TestCase
{
use RequiresMoneroVersionTrait;

public static array $seeds = [TestHelper::WALLET_1_MNEMONIC, TestHelper::WALLET_2_MNEMONIC];

public static array $wallets = [];

private static DaemonRpcClient $daemonRpcClient;
private static WalletRpcClient $walletRpcClient;
private static WalletRpcClient $walletRpcClient2;

public static int $runningBalance = 0;

Expand All @@ -44,11 +50,24 @@ public static function setUpBeforeClass(): void
->buildDaemonClient();
self::$walletRpcClient = (new ClientBuilder(getenv('WALLET_RPC_URL')))
->buildWalletClient();
self::$walletRpcClient2 = (new ClientBuilder(getenv('WALLET_RPC_URL_2')))
->buildWalletClient();

self::$walletRpcClient->restoreDeterministicWallet('', '', self::$seeds[0]);
self::$walletRpcClient2->restoreDeterministicWallet('', '', self::$seeds[1]);
self::$daemonRpcClient->generateBlocks(100, TestHelper::MAINNET_ADDRESS_1);
}

protected function setUp(): void
{
$this->checkMoneroVersionRequirements();
}

protected static function getDaemonRpcClient(): DaemonRpcClient
{
return self::$daemonRpcClient;
}

public function testWallet(): void
{
self::$walletRpcClient->refresh();
Expand Down Expand Up @@ -181,10 +200,9 @@ public function testRawTransactions(): void
$this->assertFalse($result->overspend);
$this->assertFalse($result->feeTooLow);

self::$walletRpcClient->restoreDeterministicWallet('', '', self::$seeds[1]);
self::$walletRpcClient->refresh();
self::$walletRpcClient2->refresh();

$result = self::$walletRpcClient->getTransfers(true, true, true, true, true);
$result = self::$walletRpcClient2->getTransfers(true, true, true, true, true);
$this->assertCount(0, $result->in);
$this->assertCount(0, $result->out);
$this->assertCount(0, $result->pending);
Expand All @@ -198,4 +216,64 @@ public function testScanTx(TransferResponse $transferResponse): void
$this->expectNotToPerformAssertions();
$result = self::$walletRpcClient->scanTx([$transferResponse->txHash]);
}

#[RequiresMoneroVersion('0.18.4.1')]
public function testSubaddressLookahead(): void
{
// The subaddress at index (0, 999) should not be accessible with default lookahead
$address0_999 = new Address(TestHelper::MAINNET_SUBADDRESS_0_999);

// Verify the address is not in the current lookahead table
$addressNotFound = false;
try {
self::$walletRpcClient->getAddressIndex($address0_999);
} catch (AddressNotInWalletException) {
$addressNotFound = true;
}
$this->assertTrue($addressNotFound, 'Address at index (0, 999) should not be in the lookahead table before extending it');

// Extend the lookahead to include index 999
self::$walletRpcClient->setSubaddressLookahead(50, 1000, "");

// Now the address should be accessible
$result = self::$walletRpcClient->getAddressIndex($address0_999);
$this->assertSame(0, $result->index->major);
$this->assertSame(999, $result->index->minor);

// Transfer to the subaddress at index (0, 999)
$transferAmount = 454545454545;
$destination = new Destination($address0_999, new Amount($transferAmount));

// Use wallet 2 to transfer (restore it first)
self::$daemonRpcClient->generateBlocks(100, TestHelper::MAINNET_ADDRESS_2);

self::$walletRpcClient2->refresh();
$balance = self::$walletRpcClient2->getBalance();
$this->assertGreaterThan($transferAmount, $balance->balance, 'Wallet 2 should have enough balance for transfer');

self::$walletRpcClient2->transfer($destination);

// Mine a block to confirm the transaction
self::$daemonRpcClient->generateBlocks(5, TestHelper::MAINNET_ADDRESS_1);

self::$walletRpcClient->refresh();

$balanceResult = self::$walletRpcClient->getBalance(0);
$this->assertNotEmpty($balanceResult->perSubaddress, 'Balance info for subaddress should exist');

$balanceInfo0_999 = null;
foreach ($balanceResult->perSubaddress as $balanceInfo) {
if ($balanceInfo->accountIndex === 0 && $balanceInfo->addressIndex === 999) {
$balanceInfo0_999 = $balanceInfo;
break;
}
}

$this->assertNotNull($balanceInfo0_999, 'Balance info for address (0, 999) not found');
$this->assertSame(TestHelper::MAINNET_SUBADDRESS_0_999, (string) $balanceInfo0_999->address);
$this->assertSame($transferAmount, $balanceInfo0_999->balance);
$this->assertSame(0, $balanceInfo0_999->unlockedBalance);
$this->assertSame('', $balanceInfo0_999->label);
$this->assertSame(1, $balanceInfo0_999->numUnspentOutputs);
}
}
9 changes: 9 additions & 0 deletions tests/unit/WalletRpcDeserializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
use RefRing\MoneroRpcPhp\WalletRpc\SetAccountTagDescriptionResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetAttributeResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetDaemonResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetSubaddressLookaheadResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SetTxNotesResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SignMultisigResponse;
use RefRing\MoneroRpcPhp\WalletRpc\SignResponse;
Expand Down Expand Up @@ -878,6 +879,14 @@ public function testGetDefaultFeePriority()
$this->assertSame($responseFlat, $response->toJson());
}

public function testSetSubaddressLookahead()
{
$jsonResponse = '{"id":"0","jsonrpc":"2.0","result":{}}';
$response = SetSubaddressLookaheadResponse::fromJsonString($jsonResponse, "result");
$responseFlat = $this->comparableJson($jsonResponse);
$this->assertSame($responseFlat, $response->toJson(JSON_FORCE_OBJECT));
}

public static function comparableJson(string $json): string
{
return json_encode(json_decode($json, flags: JSON_BIGINT_AS_STRING)->result);
Expand Down
15 changes: 15 additions & 0 deletions tests/unit/WalletRpcSerializationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
use RefRing\MoneroRpcPhp\WalletRpc\SetAccountTagDescriptionRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetAttributeRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetDaemonRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetSubaddressLookaheadRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SetTxNotesRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SignMultisigRequest;
use RefRing\MoneroRpcPhp\WalletRpc\SignRequest;
Expand Down Expand Up @@ -898,4 +899,18 @@ public function testGetDefaultFeePriority()
$request = GetDefaultFeePriorityRequest::create();
$this->assertSame($expected, $request->toJson());
}

public function testSetSubaddressLookahead()
{
$expected = '{"jsonrpc":"2.0","id":"0","method":"set_subaddress_lookahead","params":{"password":"testpassword","major_idx":50,"minor_idx":200}}';
$request = SetSubaddressLookaheadRequest::create(50, 200, 'testpassword');
$this->assertSame($expected, $request->toJson());
}

public function testSetSubaddressLookaheadWithoutPassword()
{
$expected = '{"jsonrpc":"2.0","id":"0","method":"set_subaddress_lookahead","params":{"major_idx":50,"minor_idx":200}}';
$request = SetSubaddressLookaheadRequest::create(50, 200);
$this->assertSame($expected, $request->toJson());
}
}