Skip to content

Commit

Permalink
Merge branch 'mainsail-develop' into refactor/schedule-cache-blocks-c…
Browse files Browse the repository at this point in the history
…ommand
  • Loading branch information
alfonsobries authored Jan 22, 2025
2 parents 5e27d49 + f819816 commit 9ca9ef8
Show file tree
Hide file tree
Showing 66 changed files with 936 additions and 186 deletions.
23 changes: 22 additions & 1 deletion app/Console/Commands/CacheKnownWallets.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
namespace App\Console\Commands;

use App\Facades\Network;
use App\Facades\Wallets;
use App\Models\Scopes\UsernameResignationScope;
use App\Models\Transaction;
use App\Models\Wallet;
use App\Services\Cache\WalletCache;
use Illuminate\Console\Command;
Expand Down Expand Up @@ -35,7 +38,23 @@ public function handle(): void

$knownWallets = collect(Network::knownWallets());

Wallet::whereIn('address', $knownWallets->pluck('address'))
/** @var string[] $resignedAddresses */
$resignedAddresses = Transaction::withScope(UsernameResignationScope::class)
->select('sender_address')
->groupBy('sender_address')
->get()
->pluck('sender_address');

$resignedWallets = Wallet::whereIn('address', $resignedAddresses)
->where('attributes->username', null)
->get();

foreach ($resignedWallets as $wallet) {
$cache->forgetWalletNameByAddress($wallet->address);
}

Wallets::allWithUsername()
->orWhereIn('address', $knownWallets->pluck('address'))
->select([
'address',
'attributes',
Expand All @@ -48,6 +67,8 @@ public function handle(): void
$username = null;
if (! is_null($knownWallet)) {
$username = $knownWallet['name'];
} else {
$username = $wallet->username();
}

if (! is_null($username)) {
Expand Down
4 changes: 4 additions & 0 deletions app/Contracts/WalletRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

interface WalletRepository
{
public function allWithUsername(): Builder;

public function allWithValidatorPublicKey(): Builder;

public function allWithVote(): Builder;
Expand All @@ -22,5 +24,7 @@ public function findByPublicKey(string $publicKey): Wallet;

public function findByPublicKeys(array $publicKey): Collection;

public function findByUsername(string $address, bool $caseSensitive = true): Wallet;

public function findByIdentifier(string $identifier): Wallet;
}
7 changes: 6 additions & 1 deletion app/DTO/MemoryWallet.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,12 @@ public function publicKey(): ?string
return $this->publicKey;
}

public function walletName(): ?string
public function hasUsername(): bool
{
return $this->username() !== null;
}

public function username(): ?string
{
return (new WalletCache())->getWalletNameByAddress($this->address);
}
Expand Down
14 changes: 12 additions & 2 deletions app/DTO/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,35 @@

namespace App\DTO;

use App\Facades\Wallets;
use App\Services\ExchangeRate;
use Illuminate\Support\Arr;

final class Payment
{
private float $amount;

private string $address;

private ?string $username = null;

public function __construct(private int $timestamp, array $payment)
{
$this->amount = $payment['amount'] / config('currencies.notation.crypto', 1e18);
$this->address = $payment['recipientId'];
$this->amount = $payment['amount'] / config('currencies.notation.crypto', 1e18);
$this->address = $payment['recipientId'];
$this->username = Arr::get(Wallets::findByAddress($payment['recipientId']), 'attributes.username');
}

public function amount(): float
{
return $this->amount;
}

public function username(): ?string
{
return $this->username;
}

public function amountFiat(): string
{
return ExchangeRate::convert($this->amount, $this->timestamp);
Expand Down
2 changes: 2 additions & 0 deletions app/Facades/Wallets.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@
use Illuminate\Support\Facades\Facade;

/**
* @method static Builder allWithUsername()
* @method static Builder allWithValidatorPublicKey()
* @method static Builder allWithVote()
* @method static Builder allWithPublicKey()
* @method static Wallet findByAddress(string $address)
* @method static Wallet findByPublicKey(string $publicKey)
* @method static Collection findByPublicKeys(array $publicKey)
* @method static Wallet findByUsername(string $username)
* @method static Wallet findByIdentifier(string $identifier)
*/
final class Wallets extends Facade
Expand Down
6 changes: 6 additions & 0 deletions app/Http/Livewire/Modals/ExportBlocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ final class ExportBlocks extends Component

public string $address;

public ?string $username = null;

public bool $hasForgedBlocks = false;

public function mount(WalletViewModel $wallet): void
{
$this->address = $wallet->address();

if ($wallet->hasUsername()) {
$this->username = $wallet->username();
}
}

public function render(): View
Expand Down
1 change: 1 addition & 0 deletions app/Http/Livewire/Validators/MissedBlocks.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ private function getMissedBlocksQuery(): Builder
return ForgingStats::query()
->when($this->sortKey === 'height', fn ($query) => $query->sortByHeight($sortDirection))
->when($this->sortKey === 'age', fn ($query) => $query->sortByAge($sortDirection))
->when($this->sortKey === 'name', fn ($query) => $query->sortByUsername($sortDirection))
->when($this->sortKey === 'votes' || $this->sortKey === 'percentage_votes', fn ($query) => $query->sortByVoteCount($sortDirection))
->when($this->sortKey === 'no_of_voters', fn ($query) => $query->sortByNumberOfVoters($sortDirection))
->whereNotNull('missed_height');
Expand Down
6 changes: 4 additions & 2 deletions app/Http/Livewire/Validators/RecentVotes.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,9 @@ private function getRecentVotesQuery(): Builder
$sortDirection = SortDirection::DESC;
}

// TODO: fetch only vote transaction types - https://app.clickup.com/t/86dv8nz3e
return Transaction::query()
->join('receipts', 'transactions.id', '=', 'receipts.id')
->where('receipts.success', true)
->where('timestamp', '>=', Timestamp::now()->subDays(30)->unix() * 1000)
->where(function ($query) {
$query->where(fn ($query) => $query->when($this->filter['vote'], function ($query) {
Expand All @@ -119,6 +120,7 @@ private function getRecentVotesQuery(): Builder
})
->when($this->sortKey === 'age', fn ($query) => $query->sortByAge($sortDirection))
->when($this->sortKey === 'address', fn ($query) => $query->sortByAddress($sortDirection))
->when($this->sortKey === 'type', fn ($query) => $query->sortByType($sortDirection));
->when($this->sortKey === 'type', fn ($query) => $query->sortByType($sortDirection))
->when($this->sortKey === 'name', fn ($query) => $query->sortByUsername($sortDirection));
}
}
1 change: 1 addition & 0 deletions app/Http/Livewire/Validators/Validators.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ private function getValidatorsQuery(): Builder
->orWhere(fn ($query) => $query->when($this->filter['resigned'] === true, fn ($query) => $query->where('attributes->validatorResigned', true)));
}))
->when($this->sortKey === 'rank', fn ($query) => $query->sortByRank($sortDirection))
->when($this->sortKey === 'name', fn ($query) => $query->sortByUsername($sortDirection))
->when($this->sortKey === 'votes' || $this->sortKey === 'percentage_votes', fn ($query) => $query->sortByVoteCount($sortDirection))
->when($this->sortKey === 'no_of_voters', fn ($query) => $query->sortByNumberOfVoters($sortDirection))
->when($this->sortKey === 'missed_blocks', fn ($query) => $query->sortByMissedBlocks($sortDirection));
Expand Down
23 changes: 23 additions & 0 deletions app/Models/Concerns/ForgingStats/CanBeSorted.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,29 @@ public function scopeSortByAge(mixed $query, SortDirection $sortDirection): Buil
return $query->orderByRaw('timestamp '.$sortDirection->value);
}

public function scopeSortByUsername(mixed $query, SortDirection $sortDirection): Builder
{
$missedBlockPublicKeys = ForgingStats::groupBy('address')->pluck('address');

$validatorNames = Wallet::whereIn('address', $missedBlockPublicKeys)
->get()
->pluck('attributes.username', 'address');

if (count($validatorNames) === 0) {
return $query->selectRaw('NULL AS validator_name')
->selectRaw('forging_stats.*');
}

return $query->selectRaw('wallets.name AS validator_name')
->selectRaw('forging_stats.*')
->join(DB::raw(sprintf(
'(values %s) as wallets (address, name)',
$validatorNames->map(fn ($name, $publicKey) => sprintf('(\'%s\',\'%s\')', $publicKey, $name))
->join(','),
)), 'forging_stats.address', '=', 'wallets.address', 'left outer')
->orderByRaw('validator_name '.$sortDirection->value.', timestamp DESC');
}

public function scopeSortByVoteCount(mixed $query, SortDirection $sortDirection): Builder
{
$missedBlockAddresses = ForgingStats::groupBy('address')->pluck('address');
Expand Down
33 changes: 33 additions & 0 deletions app/Models/Concerns/Transaction/CanBeSorted.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Enums\SortDirection;
use App\Facades\Network;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Facades\DB;

trait CanBeSorted
{
Expand Down Expand Up @@ -39,4 +40,36 @@ public function scopeSortByType(mixed $query, SortDirection $sortDirection): Bui
->selectRaw('transactions.*')
->orderBy('transaction_type', $sortDirection->value);
}

public function scopeSortByUsername(mixed $query, SortDirection $sortDirection): Builder
{
return $query
->select([
'validator_name' => fn ($query) => $query
->selectRaw('coalesce((wallets.attributes->\'username\')::text, wallets.address)')
->from(function ($query) {
$query
->selectRaw('CONCAT(\'0x\', RIGHT(SUBSTRING(encode(data, \'hex\'), 9), 40)) as vote')
->where('recipient_address', Network::knownContract('consensus'))
->whereRaw('SUBSTRING(encode(data, \'hex\'), 1, 8) = ?', [ContractMethod::vote()])
->whereColumn('transactions.id', 'validator_transaction.id')
->from('transactions', 'validator_transaction');
}, 'validator_vote')
->join('wallets', DB::raw('LOWER(wallets.address)'), '=', DB::raw('LOWER(validator_vote.vote)')),

'transaction_type' => fn ($query) => $query
->selectRaw('coalesce(validator_vote.vote, validator_vote.unvote)')
->from(function ($query) {
$query
->selectRaw('case when (SUBSTRING(encode(data, \'hex\'), 1, 8) = ?) then 1 end as unvote', [ContractMethod::unvote()])
->selectRaw('case when (SUBSTRING(encode(data, \'hex\'), 1, 8) = ?) then 0 end as vote', [ContractMethod::vote()])
->where('recipient_address', Network::knownContract('consensus'))
->whereColumn('transactions.id', 'validator_transaction.id')
->from('transactions', 'validator_transaction');
}, 'validator_vote'),
])
->selectRaw('transactions.*')
->orderBy('transaction_type', 'asc')
->orderBy('validator_name', $sortDirection->value);
}
}
5 changes: 5 additions & 0 deletions app/Models/Concerns/Wallet/CanBeSorted.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@

trait CanBeSorted
{
public function scopeSortByUsername(mixed $query, SortDirection $sortDirection): Builder
{
return $query->orderByRaw("(\"attributes\"->>'username')::text ".$sortDirection->value.', ("attributes"->>\'validatorRank\')::numeric ASC');
}

public function scopeSortByRank(mixed $query, SortDirection $sortDirection): Builder
{
return $query->orderByRaw("(\"attributes\"->>'validatorRank')::numeric ".$sortDirection->value);
Expand Down
11 changes: 11 additions & 0 deletions app/Models/Wallet.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Laravel\Scout\Searchable;

Expand All @@ -23,6 +24,7 @@
* @property BigNumber $nonce
* @property array $attributes
* @property int $updated_at
* @property string $validator_username (only available when indexed by scout)
* @property string $timestamp (only available when indexed by scout)
* @property int $missed_blocks (only available when sorting validators by missed blocks)
* @method static \Illuminate\Database\Eloquent\Builder withScope(string $scope)
Expand Down Expand Up @@ -81,6 +83,13 @@ final class Wallet extends Model
'attributes' => 'array',
];

public function username(): ?string
{
$attributes = json_decode($this->attributes['attributes'], true);

return Arr::get($attributes, 'username', null);
}

/**
* Get the value used to index the model.
*/
Expand All @@ -106,6 +115,7 @@ public function toSearchableArray(): array
{
return [
'address' => $this->address,
'username' => $this->username(),
'balance' => $this->balance->__toString(),
'timestamp' => $this->timestamp,
];
Expand All @@ -120,6 +130,7 @@ public static function getSearchableQuery(): Builder

return $self->newQuery()
->select([
DB::raw("wallets.attributes->>'username' AS validator_username"),
'wallets.address',
'wallets.attributes',
'wallets.balance',
Expand Down
4 changes: 3 additions & 1 deletion app/Providers/RouteServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public function boot()
}

try {
return Wallets::findByAddress($walletID);
return strlen($walletID) === Constants::ADDRESS_LENGTH
? Wallets::findByAddress($walletID)
: Wallets::findByUsername($walletID);
} catch (Throwable) {
throw (new WalletNotFoundException())->setModel(Wallet::class, [$walletID]);
}
Expand Down
21 changes: 21 additions & 0 deletions app/Repositories/WalletRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,17 @@
use App\Services\Search\Traits\ValidatesTerm;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

final class WalletRepository implements Contract
{
use ValidatesTerm;

public function allWithUsername(): Builder
{
return Wallet::whereNotNull('wallets.attributes->username');
}

public function allWithValidatorPublicKey(): Builder
{
return Wallet::whereNotNull('wallets.attributes->validatorPublicKey');
Expand Down Expand Up @@ -44,6 +50,18 @@ public function findByPublicKeys(array $publicKeys): Collection
return Wallet::whereIn('public_key', $publicKeys)->get();
}

public function findByUsername(string $username, bool $caseSensitive = true): Wallet
{
if ($caseSensitive === false) {
$username = substr(DB::getPdo()->quote($username), 1, -1);

return Wallet::whereRaw('lower(attributes::text)::jsonb @> lower(\'{"username":"'.$username.'"}\')::jsonb')
->firstOrFail();
}

return Wallet::where('attributes->username', $username)->firstOrFail();
}

public function findByIdentifier(string $identifier): Wallet
{
$query = Wallet::query();
Expand All @@ -52,6 +70,9 @@ public function findByIdentifier(string $identifier): Wallet
$query->whereLower('address', $identifier);
} elseif ($this->couldBePublicKey($identifier)) {
$query->whereLower('public_key', $identifier);
} elseif ($this->couldBeUsername($identifier)) {
$username = substr(DB::getPdo()->quote($identifier), 1, -1);
$query->orWhereRaw('lower(attributes::text)::jsonb @> lower(\'{"username":"'.$username.'"}\')::jsonb');
} else {
$query->empty();
}
Expand Down
10 changes: 10 additions & 0 deletions app/Repositories/WalletRepositoryWithCache.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ public function __construct(private WalletRepository $wallets)
{
}

public function allWithUsername(): Builder
{
return $this->wallets->allWithUsername();
}

public function allWithValidatorPublicKey(): Builder
{
return $this->wallets->allWithValidatorPublicKey();
Expand Down Expand Up @@ -50,6 +55,11 @@ public function findByPublicKeys(array $publicKeys): Collection
return $this->remember(fn () => $this->wallets->findByPublicKeys($publicKeys));
}

public function findByUsername(string $username, bool $caseSensitive = true): Wallet
{
return $this->remember(fn () => $this->wallets->findByUsername($username, $caseSensitive));
}

public function findByIdentifier(string $identifier): Wallet
{
return $this->remember(fn () => $this->wallets->findByIdentifier($identifier));
Expand Down
Loading

0 comments on commit 9ca9ef8

Please sign in to comment.