Skip to content

Commit

Permalink
dapp: Improve responsiveness of the stake info in the staking dapp.
Browse files Browse the repository at this point in the history
  • Loading branch information
patniemeyer committed Sep 25, 2024
1 parent 65a77f8 commit 9fdb5a1
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 55 deletions.
1 change: 1 addition & 0 deletions gui-orchid/lib/util/listenable_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'package:flutter/material.dart';

// TODO: This is no longer needed in later versions of Flutter. Remove when redundant.

/// Buildier that can listen to any Listenable including ChangeNotifier, ValueNotifier, etc.
/// This is a trivial subclass of AnimatedBuilder that serves to rename
/// it more appropriately for use as a plain listenable builder.
/// (This really should be the name of the base class in Flutter core.)
Expand Down
17 changes: 12 additions & 5 deletions gui-orchid/lib/util/poller.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../api/orchid_log.dart';

/*
Poller.call(foo).every(seconds: 5).dispose(disposal);
Expand All @@ -21,10 +22,12 @@ class Poller {
Poller every({int? seconds, int? minutes, int? hours}) {
assert(seconds != null || minutes != null || hours != null);
_timer?.cancel();
_timer = Timer.periodic(
Duration(
seconds: seconds ?? 0, minutes: minutes ?? 0, hours: hours ?? 0),
_poll);
var duration = Duration(
seconds: seconds ?? 0, minutes: minutes ?? 0, hours: hours ?? 0);
if (duration.inMilliseconds <= 0) {
throw Exception("invalid duration: $duration");
}
_timer = Timer.periodic(duration, _poll);
return this;
}

Expand All @@ -35,7 +38,11 @@ class Poller {
}

void _poll(_) {
func();
try {
func();
} catch (e) {
log("Poller error: $e");
}
}

Poller dispose(List disposal) {
Expand Down
2 changes: 1 addition & 1 deletion web-ethereum/account_dapp/lib/pages/dapp_home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,12 @@ class DappHomeState extends DappHomeStateBase<DappHome> {
children: [
DappHomeHeader(
web3Context: web3Context,
setNewContext: setNewContext,
contractVersionsAvailable: contractVersionsAvailable,
contractVersionSelected: contractVersionSelected,
selectContractVersion: selectContractVersion,
deployContract: deployContract,
connectEthereum: connectEthereum,
connectWalletConnect: connectWalletConnect,
disconnect: disconnect,
).padx(24).top(30).bottom(24),
_buildMainColumn(),
Expand Down
90 changes: 42 additions & 48 deletions web-ethereum/stake_dapp/lib/pages/stake_dapp_home.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import 'package:orchid/common/app_dialogs.dart';
import 'package:orchid/orchid/field/orchid_labeled_address_field.dart';
import 'package:orchid/pages/tabs/location_panel.dart';
import 'package:orchid/pages/tabs/stake_tabs.dart';
import 'package:orchid/stake_dapp/orchid_web3_stake_v0.dart';
import 'package:orchid/stake_dapp/orchid_web3_location_v0.dart';
import 'package:orchid/stake_dapp/orchid_web3_stake_v0.dart';
import 'package:orchid/stake_dapp/stake_detail.dart';
import 'dapp_home_base.dart';
import 'dapp_home_header.dart';

Expand All @@ -37,16 +38,20 @@ class _StakeDappHomeState extends DappHomeStateBase<StakeDappHome> {
final _stakeeField = AddressValueFieldController();
final _scrollController = ScrollController();

Location? _currentLocation;

// The current stake details for the staker/stakee pair.
StakeDetailPoller? _stakeDetail;

// The total stake staked for the stakee by all stakers.
Token? _currentStakeTotal;
Token? get _currentStakeTotal => _stakeDetail?.currentStakeTotal;

// The amount and delay staked for the stakee by the current staker (wallet).
StakeResult? _currentStakeStaker;
StakeResult? get _currentStakeStaker => _stakeDetail?.currentStakeStaker;

// The amount and expiration of the pulled stake pending withdrawal for the first n indexes.
List<StakePendingResult>? _currentStakePendingStaker;

Location? _currentLocation;
List<StakePendingResult>? get _currentStakePendingStaker =>
_stakeDetail?.currentStakePendingStaker;

@override
void initState() {
Expand Down Expand Up @@ -176,8 +181,8 @@ class _StakeDappHomeState extends DappHomeStateBase<StakeDappHome> {
StakeTabs(
web3Context: web3Context,
stakee: _stakee,
currentStake: _currentStakeStaker?.amount,
price: price,
currentStake: _currentStakeStaker?.amount,
currentStakeDelay: _currentStakeStaker?.delay,
).top(40),
],
Expand Down Expand Up @@ -339,55 +344,42 @@ class _StakeDappHomeState extends DappHomeStateBase<StakeDappHome> {
}

// Start polling the correct account
// TODO: Poll this
void _selectedStakeeChanged() async {
if (isStakerView) {
_updateStake();
_updateStakePoller();
} else {
_updateLocation();
}
}

void _updateStake() async {
if (_stakee != null && web3Context != null) {
// Get the total stake for all stakers (heft)
final orchidWeb3 = OrchidWeb3StakeV0(web3Context!);
_currentStakeTotal = await orchidWeb3.orchidGetTotalStake(_stakee!);
log("XXX: heft = $_currentStakeTotal");

// Get the stake for this staker (wallet)
try {
_currentStakeStaker = await orchidWeb3.orchidGetStakeForStaker(
staker: web3Context!.walletAddress!,
stakee: _stakee!,
);
log("XXX: staker stake = $_currentStakeStaker");
} catch (err, stack) {
log("Error getting stake for staker: $err");
log(stack.toString());
}
// Create a new poller for the current staker/stakee pair
void _updateStakePoller() {
// Cancel any existing poller
_clearStakePoller();

// Get the pending stake withdrawals for this staker (wallet)
try {
List<StakePendingResult> pendingList = [];
for (var i = 0; i < 3; i++) {
final pending = await orchidWeb3.orchidGetPendingWithdrawal(
staker: web3Context!.walletAddress!,
index: i,
);
pendingList.add(pending);
}
_currentStakePendingStaker = pendingList;
log("XXX: pending = $_currentStakePendingStaker");
} catch (err, stack) {
log("Error getting stake for staker: $err");
log(stack.toString());
}
} else {
_currentStakeTotal = null;
_currentStakeStaker = null;
_currentStakePendingStaker = null;
// Start a new poller if we have context, staker, and stakee
final staker = web3Context?.walletAddress;
final stakee = _stakee;
if (stakee != null && staker != null) {
_stakeDetail = StakeDetailPoller(
pollingPeriod: const Duration(seconds: 10),
web3Context: web3Context!,
staker: staker,
stakee: stakee,
);
_stakeDetail?.addListener(_stakeUpdated);
_stakeDetail?.startPolling();
}
}

void _clearStakePoller() {
_stakeDetail?.cancel();
_stakeDetail?.removeListener(_stakeUpdated);
_stakeDetail = null;
}

// Called when the stake poller has an update
void _stakeUpdated() {
setState(() {});
}

Expand Down Expand Up @@ -437,12 +429,14 @@ class _StakeDappHomeState extends DappHomeStateBase<StakeDappHome> {
// setState(() {
// _clearAccountDetail();
// });
super.disconnect();
await super.disconnect();
_updateStakePoller(); // allow the poller to cancel itself
}

@override
void dispose() {
_stakeeField.removeListener(_stakeeFieldChanged);
_clearStakePoller();
super.dispose();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class _AddStakePanelState extends State<AddStakePanel>
// Delay label (if non-null)
// if (_currentStakeDelayIsZero)
// Text("Added funds will be staked with no withdrawal delay.").white.caption.top(16),
// if (_currentStakeDelayIsNonZero)
if (_currentStakeDelayIsNonZero)
Text("This UI does not support adding funds to an existing stake with a non-zero delay.")
.caption
.error
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ class _WithdrawStakePanelState extends State<WithdrawStakePanel>
)));

_withdrawStakeAmountController.clear();
_indexController.clear();
_targetController.clear();
setState(() {});
} catch (err) {
log('Error on withdraw funds: $err');
Expand Down
139 changes: 139 additions & 0 deletions web-ethereum/stake_dapp/lib/stake_dapp/stake_detail.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:orchid/api/orchid_crypto.dart';
import 'package:orchid/api/orchid_eth/token_type.dart';
import 'package:orchid/api/orchid_log.dart';
import 'package:orchid/dapp/orchid_web3/orchid_web3_context.dart';
import 'orchid_web3_stake_v0.dart';

class StakeDetailPoller extends ChangeNotifier {
final OrchidWeb3Context web3Context;
final EthereumAddress stakee;
final EthereumAddress staker; // wallet

// The total stake staked for the stakee by all stakers.
Token? currentStakeTotal;

// The amount and delay staked for the stakee by the current staker (wallet).
StakeResult? currentStakeStaker;

// The amount and expiration of the pulled stake pending withdrawal for the first n indexes.
List<StakePendingResult>? currentStakePendingStaker;

// Manage polling state
static int nextId = 0;
final int id;
final Duration pollingPeriod;
Timer? _timer;
bool _pollInProgress = false;
bool _isCancelled = false;
DateTime? lastUpdate;

StakeDetailPoller({
required this.web3Context,
required this.staker,
required this.stakee,
this.pollingPeriod = const Duration(seconds: 30),
}) : this.id = nextId++ {
log("XXX: StakeDetailPoller $id created.");
}

/// Start periodic polling
Future<void> startPolling() async {
_timer = Timer.periodic(pollingPeriod, (_) {
_pollStake();
});
return _pollStake(); // kick one off immediately
}

/// Load data once
Future<void> pollOnce() async {
return _pollStake();
}

/// Load data updating caches
Future<void> refresh() async {
return _pollStake(refresh: true);
}

Future<void> _pollStake({bool refresh = false}) async {
if (_isCancelled || _pollInProgress) {
log("XXX: call to _pollStake with cancelled timer or poll in progress, pollInProgress=$_pollInProgress");
return;
}
_pollInProgress = true;
try {
await _pollStakeImpl(refresh);
} catch (err) {
log("Error polling stake details: $err");
} finally {
_pollInProgress = false;
lastUpdate = DateTime.now();
}
}

// 'refresh' can be used to defeat caching, if any.
Future<void> _pollStakeImpl(bool refresh) async {
final orchidWeb3 = OrchidWeb3StakeV0(web3Context);

// Get the total stake for all stakers (heft)
try {
currentStakeTotal = await orchidWeb3.orchidGetTotalStake(stakee);
log("XXX: heft = $currentStakeTotal");
} catch (err) {
log("Error getting heft for stakee: $err");
currentStakeTotal = null;
}
this.notifyListeners();

// Get the stake for this staker (wallet)
try {
currentStakeStaker = await orchidWeb3.orchidGetStakeForStaker(
staker: staker,
stakee: stakee,
);
log("XXX: staker stake = $currentStakeStaker");
} catch (err, stack) {
log("Error getting stake for staker: $err");
log(stack.toString());
currentStakeStaker = null;
}
this.notifyListeners();

// Get the pending stake withdrawals for this staker (wallet)
try {
List<StakePendingResult> pendingList = [];
for (var i = 0; i < 3; i++) {
final pending = await orchidWeb3.orchidGetPendingWithdrawal(
staker: staker,
index: i,
);
pendingList.add(pending);
}
currentStakePendingStaker = pendingList;
log("XXX: pending = $currentStakePendingStaker");
} catch (err, stack) {
log("Error getting stake for staker: $err");
log(stack.toString());
currentStakePendingStaker = null;
}
this.notifyListeners();
}

void cancel() {
_isCancelled = true;
_timer?.cancel();
log("XXX: stake detail $id poller cancelled");
}

void dispose() {
cancel();
super.dispose();
}

@override
String toString() {
return 'StakeDetailPoller{id: $id, stakee: $stakee, staker: $staker, currentStakeTotal: $currentStakeTotal, currentStakeStaker: $currentStakeStaker, currentStakePendingStaker: $currentStakePendingStaker}';
}
}

0 comments on commit 9fdb5a1

Please sign in to comment.