33package e2e_test
44
55import (
6+ "fmt"
67 "log/slog"
78 "os"
89 "path/filepath"
@@ -59,6 +60,24 @@ func TestE2E_Funder(t *testing.T) {
5960 require .NoError (t , err )
6061 funderPK := funderPrivateKey .PublicKey ()
6162
63+ // Check that the errors metric only contains "funder_account_balance_below_minimum" errors,
64+ // which occur on startup while waiting for the manager/funder account to be funded.
65+ metricsClient := dn .Funder .GetMetricsClient ()
66+ require .NoError (t , metricsClient .Fetch (ctx ))
67+ errors := metricsClient .GetCounterValues ("doublezero_funder_errors_total" )
68+ require .NotNil (t , errors )
69+ require .Len (t , errors , 1 )
70+ require .Equal (t , "funder_account_balance_below_minimum" , errors [0 ].Labels ["error_type" ])
71+ prevFunderAccountBalanceBelowMinimumCount := int (errors [0 ].Value )
72+
73+ // Check the funder account balance metric.
74+ require .NoError (t , metricsClient .Fetch (ctx ))
75+ funderBalance := metricsClient .GetGaugeValues ("doublezero_funder_account_balance_sol" )
76+ require .NotNil (t , funderBalance )
77+ // The funder account is the manager account, which we fund with 100 SOL during devnet setup.
78+ require .Greater (t , funderBalance [0 ].Value , 50.0 )
79+ prevFunderBalance := funderBalance [0 ].Value
80+
6281 // Add a device onchain with metrics publisher pubkey.
6382 log .Debug ("==> Creating LA device onchain" )
6483 laDeviceMetricsPublisherWallet := solana .NewWallet ()
@@ -84,20 +103,78 @@ func TestE2E_Funder(t *testing.T) {
84103 // Check that the metrics publisher pubkey is eventually funded.
85104 requireEventuallyFunded (t , log , rpcClient , nyDeviceMetricsPublisherWallet .PublicKey (), minBalanceSOL , "NY device metrics publisher" )
86105
106+ // Check that the funder account balance is now lower.
107+ require .NoError (t , metricsClient .Fetch (ctx ))
108+ funderBalance = metricsClient .GetGaugeValues ("doublezero_funder_account_balance_sol" )
109+ require .NotNil (t , funderBalance )
110+ require .Less (t , funderBalance [0 ].Value , prevFunderBalance )
111+
87112 // Drain current balance from the devices onchain.
88- drainFunds (t , rpcClient , laDeviceMetricsPublisherWallet .PrivateKey , funderPK , 0.01 )
89- drainFunds (t , rpcClient , nyDeviceMetricsPublisherWallet .PrivateKey , funderPK , 0.01 )
113+ drainWallet := solana .NewWallet ()
114+ log .Debug ("--> Draining LA device balance" , "account" , laDeviceMetricsPublisherWallet .PublicKey ())
115+ drainFunds (t , rpcClient , laDeviceMetricsPublisherWallet .PrivateKey , drainWallet .PublicKey (), 0.01 )
116+ log .Debug ("--> Draining NY device balance" , "account" , nyDeviceMetricsPublisherWallet .PublicKey ())
117+ drainFunds (t , rpcClient , nyDeviceMetricsPublisherWallet .PrivateKey , drainWallet .PublicKey (), 0.01 )
90118
91119 // Check that the devices are eventually funded again.
120+ beforeFunderBalance := getBalance (t , rpcClient , funderPK )
92121 requireEventuallyFunded (t , log , rpcClient , laDeviceMetricsPublisherWallet .PublicKey (), minBalanceSOL , "LA device metrics publisher" )
93122 requireEventuallyFunded (t , log , rpcClient , nyDeviceMetricsPublisherWallet .PublicKey (), minBalanceSOL , "NY device metrics publisher" )
123+
124+ // Wait for the funder account balance to show the top up.
125+ require .Eventually (t , func () bool {
126+ funderBalance := getBalance (t , rpcClient , funderPK )
127+ return funderBalance <= beforeFunderBalance - 2 * topUpSOL
128+ }, 60 * time .Second , 5 * time .Second )
129+
130+ // Drain the funder account balance to near 0.
131+ log .Debug ("--> Draining funder account balance" , "account" , funderPK )
132+ drainFunds (t , rpcClient , funderPrivateKey , drainWallet .PublicKey (), 0.01 )
133+
134+ // Check that the errors metric for "funder_account_balance_below_minimum" eventually increases,
135+ // which occurs when the funder account balance is drained to below the minimum.
136+ require .Eventually (t , func () bool {
137+ require .NoError (t , metricsClient .Fetch (ctx ))
138+ errors = metricsClient .GetCounterValues ("doublezero_funder_errors_total" )
139+ require .NotNil (t , errors )
140+ require .Len (t , errors , 1 )
141+ require .Equal (t , "funder_account_balance_below_minimum" , errors [0 ].Labels ["error_type" ])
142+ if int (errors [0 ].Value ) > prevFunderAccountBalanceBelowMinimumCount {
143+ return true
144+ }
145+ log .Debug ("--> Waiting for funder account balance below minimum error to increase" , "account" , funderPK , "prevCount" , prevFunderAccountBalanceBelowMinimumCount , "currentCount" , int (errors [0 ].Value ))
146+ return false
147+ }, 60 * time .Second , 5 * time .Second )
148+
149+ // Check that the funder account balance gauge metric is now near 0.
150+ require .NoError (t , metricsClient .Fetch (ctx ))
151+ funderBalance = metricsClient .GetGaugeValues ("doublezero_funder_account_balance_sol" )
152+ require .NotNil (t , funderBalance )
153+ require .LessOrEqual (t , funderBalance [0 ].Value , 0.01 )
154+
155+ // Transfer the drained funds back to the funder account.
156+ expectedFunderBalance := drainFunds (t , rpcClient , drainWallet .PrivateKey , funderPrivateKey .PublicKey (), 0.01 )
157+
158+ // Check that the funder account balance is eventually back near the previous value.
159+ require .Eventually (t , func () bool {
160+ require .NoError (t , metricsClient .Fetch (ctx ))
161+ funderBalance = metricsClient .GetGaugeValues ("doublezero_funder_account_balance_sol" )
162+ require .NotNil (t , funderBalance )
163+ if funderBalance [0 ].Value > expectedFunderBalance - 0.01 && funderBalance [0 ].Value < expectedFunderBalance + 0.01 {
164+ return true
165+ }
166+ log .Debug ("--> Waiting for funder account balance to be back near previous value" , "account" , funderPK , "expectedBalance" , expectedFunderBalance , "currentBalance" , funderBalance [0 ].Value )
167+ return false
168+ }, 60 * time .Second , 5 * time .Second )
94169}
95170
96- func drainFunds (t * testing.T , client * solanarpc.Client , from solana.PrivateKey , to solana.PublicKey , amount float64 ) {
171+ func drainFunds (t * testing.T , client * solanarpc.Client , from solana.PrivateKey , to solana.PublicKey , remainingBalanceSOL float64 ) float64 {
97172 t .Helper ()
98173
99174 balanceSOL := getBalance (t , client , from .PublicKey ())
100- transferFunds (t , client , from , to , balanceSOL - amount , nil )
175+ transferFunds (t , client , from , to , balanceSOL - remainingBalanceSOL , nil )
176+
177+ return balanceSOL - remainingBalanceSOL
101178}
102179
103180func requireEventuallyFunded (t * testing.T , log * slog.Logger , client * solanarpc.Client , account solana.PublicKey , minBalanceSOL float64 , name string ) {
@@ -108,7 +185,7 @@ func requireEventuallyFunded(t *testing.T, log *slog.Logger, client *solanarpc.C
108185 require .NoError (t , err )
109186 balanceSOL := float64 (balance .Value ) / float64 (solana .LAMPORTS_PER_SOL )
110187 if balanceSOL < minBalanceSOL {
111- log .Debug ("--> Waiting for %s to be funded" , " name" , name )
188+ log .Debug (fmt . Sprintf ( "--> Waiting for %s to be funded" , name ), "account" , account , "minBalance" , minBalanceSOL , "balance" , balanceSOL )
112189 return false
113190 }
114191 return true
0 commit comments