summaryrefslogtreecommitdiff
path: root/test/e2e-go/cli/goal/expect/goalExpectCommon.exp
blob: 1d8f9d0ed60d5a2cf938560fcca15ec66e1f4654 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
# Algorand Goal Expect Utility Package
namespace eval ::AlgorandGoal {

  # Export Procedures
  namespace export Info
  namespace export Abort
  namespace export StartNetwork
  namespace export StopNetwork
  namespace export CreateWallet
  namespace export VerifyWallet
  namespace export RecoverWallet
  namespace export CreateAccountForWallet
  namespace export GetAccountAddress
  namespace export GetAccountBalance
  namespace export GetAccountRewards
  namespace export AccountTransfer
  namespace export WaitForAccountBalance
  namespace export GetHighestFundedAccountForWallet
  namespace export GetAlgodNetworkAddress
  namespace export GetKMDNetworkAddress
  namespace export GetLedgerSupply
  namespace export WaitForRound
  namespace export Report
  namespace export ListParticipationKeys
  namespace export AddParticipationKey
  namespace export TakeAccountOnline

  # My Variables
   set version 1.0
   set description "Algorand Goal Expect Package"

  # Variable for the path of the script
   variable home [file join [pwd] [file dirname [info script]]]
}

# Definition of the procedure MyProcedure
proc ::AlgorandGoal::Info {} {
   puts AlgorandGoal::description
}

package provide AlgorandGoal $AlgorandGoal::version
package require Tcl 8.0


# Utility method to abort out of this script
proc ::AlgorandGoal::Abort { ERROR } {
    puts "Aborting with Error: $ERROR"

    if { [info exists ::GLOBAL_TEST_ROOT_DIR] } {
        # terminate child algod processes, if there are active child processes the test will hang on a test failure
        puts "GLOBAL_TEST_ROOT_DIR $::GLOBAL_TEST_ROOT_DIR"
        puts "GLOBAL_NETWORK_NAME $::GLOBAL_NETWORK_NAME"
        ::AlgorandGoal::StopNetwork $::GLOBAL_NETWORK_NAME $::GLOBAL_TEST_ROOT_DIR
    }

    if { [info exists ::GLOBAL_TEST_ALGO_DIR] } {
        puts "GLOBAL_TEST_ALGO_DIR $::GLOBAL_TEST_ALGO_DIR"
        ::AlgorandGoal::StopNode $::GLOBAL_TEST_ALGO_DIR
    }

    exit 1
}

# Utility method to test the process returned value
# Returns 0 when no error code is detected
# When an error code is detected:
#    If ABORT = 1 Calls AlgorandGoal::Abort 
#    if ABORT = 0 Returns 1 OS_CODE ERR_CODE KILLED KILL_SIGNAL EXP
# If SIGHUP is detected, it ignores it.
proc ::AlgorandGoal::CheckProcessReturnedCode {ABORT} {
    upvar spawn_id spawn_id
    lassign [wait -i $spawn_id] PID SPAWNID OS_CODE ERR_CODE KILLED KILL_SIGNAL EXP

    if {$KILLED == "CHILDKILLED"} {
	if {$KILL_SIGNAL == "SIGHUP" && $EXP == "hangup"} {
	    # this is caused by expect close. Will ignore.
	    return 0
	}
	if {$ABORT} {
	    ::AlgorandGoal::Abort "process killed: $KILL_SIGNAL $EXP"
	}
	return [list 1 $OS_CODE $ERR_CODE $KILLED $KILL_SIGNAL $EXP]
    }

    if {$OS_CODE == -1} {
	if {$ABORT} {
	    ::AlgorandGoal::Abort "OS error code: $ERR_CODE"
	}
	return [list 1 $OS_CODE $ERR_CODE $KILLED $KILL_SIGNAL $EXP]
    } else {
	if {$ERR_CODE != 0} {
	    if {$ABORT} {
		::AlgorandGoal::Abort "porcess returned non-zero value: $ERR_CODE"
	    }
	    return [list 1 $OS_CODE $ERR_CODE $KILLED $KILL_SIGNAL $EXP]
	}
    }
    return 0
}

# Start the node
proc ::AlgorandGoal::StartNode { TEST_ALGO_DIR {SYSTEMD_MANAGED "False"} {PEER_ADDRESS ""} } {
    set ::GLOBAL_TEST_ALGO_DIR $TEST_ALGO_DIR
    set timeout 15
    set GOAL_PARAMS "node start -d $TEST_ALGO_DIR"
    if { $PEER_ADDRESS != "" } {
        set GOAL_PARAMS "$GOAL_PARAMS -p $PEER_ADDRESS"
    }
    if { [catch {
        puts "node start with $TEST_ALGO_DIR"
        spawn goal {*}$GOAL_PARAMS
        if { $SYSTEMD_MANAGED eq "True" } {
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node start" }
                "^This node is managed by systemd, you must run the following command to make your desired state change to your node:*" { puts "Goal showed correct error message for systemd" ; close}
                eof { ::AlgorandGoal::CheckEOF "Unable to start network" }
            }
        } else {
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node start" }
                "^Algorand node successfully started!*" {puts "Node started successfully"; close}
                eof { ::AlgorandGoal::CheckEOF "Unable to start network" }
            }
        }
    } EXCEPTION] } {
        puts "ERROR in StartNode: $EXCEPTION"
        exit 1
    }
}

# Stop the node
proc ::AlgorandGoal::StopNode { TEST_ALGO_DIR {SYSTEMD_MANAGED ""} } {
    set ::GLOBAL_TEST_ALGO_DIR $TEST_ALGO_DIR
    set timeout 15

    if { [catch {
        puts "node stop with $TEST_ALGO_DIR"
        if { $SYSTEMD_MANAGED eq "" } {
            spawn goal node stop -d $TEST_ALGO_DIR
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node stop" }
                "*The node was successfully stopped.*" {puts "Node stopped successfully"; close}
                eof { close; ::AlgorandGoal::Abort "Did not receive appropriate message before goal command completion" }
            }
        } else {
            spawn goal node stop -d $TEST_ALGO_DIR
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node stop" }
                "*This node is managed by systemd, you must run the following command to make your desired state change to your node:*" { puts "Goal showed correct error message for systemd" ; close}
                eof { close; ::AlgorandGoal::Abort "Did not receive appropriate message before goal command completion" }
            }
        }
    } EXCEPTION] } {
        puts "ERROR in StopNode: $EXCEPTION"
        exit 1
    }
}

# Restart the node
proc ::AlgorandGoal::RestartNode { TEST_ALGO_DIR {SYSTEMD_MANAGED ""} } {
    set ::GLOBAL_TEST_ALGO_DIR $TEST_ALGO_DIR
    set timeout 30

    if { [catch {
        puts "node restart with $TEST_ALGO_DIR"
        if { $SYSTEMD_MANAGED eq "" } {
            spawn goal node restart -d $TEST_ALGO_DIR
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node restart" }
                "^The node was successfully stopped.*Algorand node successfully started!*" {puts "Node restarted successfully"; close}
            }
        } else {
            spawn goal node restart -d $TEST_ALGO_DIR
            expect {
                timeout { close; ::AlgorandGoal::Abort "Did not receive appropriate message during node restart" }
                "^This node is managed by systemd, you must run the following command to make your desired state change to your node:*" { puts "Goal showed correct error message for systemd" ; close}
            }
        }
    } EXCEPTION] } {
        puts "ERROR in RestartNode: $EXCEPTION"
        exit 1
    }
}

# Create the network
proc ::AlgorandGoal::CreateNetwork { NETWORK_NAME NETWORK_TEMPLATE TEST_ALGO_DIR TEST_ROOT_DIR } {
    # Running on ARM64, it seems that network creation is pretty slow.
    # 30 second won't be enough here, so I'm changing this to 120 seconds.
    set timeout 120

    if { [catch {
        # Create network
        puts "network create $NETWORK_NAME"
        spawn goal network create --network $NETWORK_NAME --template $NETWORK_TEMPLATE --datadir $TEST_ALGO_DIR --rootdir $TEST_ROOT_DIR
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timed out creating network" }
            "^Network $NETWORK_NAME created under.*" { puts "Network $NETWORK_NAME created" ; exp_continue }
            eof { ::AlgorandGoal::CheckEOF "Unable to create network" }
        }
    } EXCEPTION ] } {
       puts "ERROR in CreateNetwork: $EXCEPTION"
       exit 1
    }
}

# Start the network
proc ::AlgorandGoal::StartNetwork { NETWORK_NAME NETWORK_TEMPLATE TEST_ROOT_DIR } {
    set timeout 120
    set ::GLOBAL_TEST_ROOT_DIR $TEST_ROOT_DIR
    set ::GLOBAL_NETWORK_NAME $NETWORK_NAME

    if { [catch {
        # Start network
        puts "network start $NETWORK_NAME"
        spawn goal network start -r $TEST_ROOT_DIR
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timed out starting network"  }
            -re "^Network started under.*" { puts "Network $NETWORK_NAME started"; exp_continue; }
            eof { ::AlgorandGoal::CheckEOF "Failed to start network $NETWORK_NAME" }
        }
    } EXCEPTION ] } {
       puts "ERROR in StartNetwork: $EXCEPTION"
       exit 1
    }

    if { [catch {
        # Check network status
        puts "network status $NETWORK_NAME"
        spawn goal network status -r $TEST_ROOT_DIR
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timed out retrieving network status"  }
            -re ".*Error getting status.*" { close; ::AlgorandGoal::Abort "error getting network status: $expect_out(buffer)"}
            eof { ::AlgorandGoal::CheckEOF "Failed to retrieve network status $NETWORK_NAME" }
        }
        puts "StartNetwork complete"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in StartNetwork: $EXCEPTION"
    }
}

# Stop the network
proc ::AlgorandGoal::StopNetwork { NETWORK_NAME TEST_ROOT_DIR } {
    set timeout 60
    set NETWORK_STOP_MESSAGE ""
    puts "Stopping network: $NETWORK_NAME"
    spawn goal network stop -r $TEST_ROOT_DIR
    expect {
        timeout {
	      close
	      puts "Timed out shutting down network"
	      puts "GLOBAL_TEST_ROOT_DIR $::GLOBAL_TEST_ROOT_DIR"
	      puts "GLOBAL_NETWORK_NAME $::GLOBAL_NETWORK_NAME"
	      exit 1
	    }
        -re "^Network Stopped under.*" {set NETWORK_STOP_MESSAGE $expect_out(buffer); exp_continue; }
        eof { ::AlgorandGoal::CheckEOF "Failed to stop network $NETWORK_NAME" }
    }
    puts $NETWORK_STOP_MESSAGE
}

# Create a new wallet
proc ::AlgorandGoal::CreateWallet { WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        set WALLET_PASS_PHRASE "NOT SET"
        spawn goal wallet new $WALLET_NAME -d $TEST_PRIMARY_NODE_DIR

        expect {
            timeout {::AlgorandGoal::Abort "Timed out CreateWallet password"  }
            "Please choose a password for wallet*" { send "$WALLET_PASSWORD\r" }
        }
        expect {
            timeout {::AlgorandGoal::Abort "Timed out CreateWallet confirmation"  }
            "Please confirm*" { send "$WALLET_PASSWORD\r"}
        }
        expect {
             timeout {::AlgorandGoal::Abort "Timed out CreateWallet see it now"  }
             "Would you like to see it now? (Y/n):" { send "y\r" }
        }
        expect {
              timeout {::AlgorandGoal::Abort "Timed out CreateWallet keep info safe"  }
              "Keep this information safe -- never share it with anyone!" {}
        }
        expect {
             timeout {::AlgorandGoal::Abort "Timed out CreateWallet pass phrase"  }
             -re {([a-z ]+)} {set WALLET_PASS_PHRASE $expect_out(1,string); close;}
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in CreateWallet: $EXCEPTION"
    }
    return $WALLET_PASS_PHRASE
}

# Verify that the wallet exists
proc ::AlgorandGoal::VerifyWallet { WALLET_NAME TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal wallet list -d $TEST_PRIMARY_NODE_DIR
        expect {
             timeout { ::AlgorandGoal::Abort "Timed out seeing expected input for spawn goal wallet list" }
             "*$WALLET_NAME*" {close}
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in VerifyWallet: $EXCEPTION"
    }
}

proc ::AlgorandGoal::RecoverWallet { NEW_WALLET_NAME WALLET_PASSPHRASE NEW_WALLET_PASSWORD TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal wallet new -r $NEW_WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
           expect {
               timeout { puts "TIMEOUT"    }
               {Please type your recovery mnemonic below, and hit return when you are done:*} { send "$WALLET_PASSPHRASE\r" }
            }
        for { set index 1}  {$index <= 5} {incr index} {
            expect {
               timeout { puts "TIMEOUT"  }
               {Please choose a password for wallet* } { send "$NEW_WALLET_PASSWORD\r"}
               {Please confirm the password:*} { send "$NEW_WALLET_PASSWORD\r"}
               {Creating wallet...*} {puts $expect_out(buffer) }
               -re {Created wallet '([-a-zA-Z0-9_]+)'} {set RECOVERED_WALLET_NAME $expect_out(1,string) }
            }
        }
        puts "Recovered wallet: $RECOVERED_WALLET_NAME"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in RecoverWallet: $EXCEPTION"
    }
    return $RECOVERED_WALLET_NAME
}

# Associate a new account with a specific wallet
proc ::AlgorandGoal::CreateAccountForWallet { WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal account new -w $WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
        while 1 {
            expect {
                timeout { break; ::AlgorandGoal::Abort "Timed out seeing new account created for wallet $WALLET_NAME" }
                "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" }
                 -re {Created new account with address ([a-zA-Z0-9]+)} {set ACCOUNT_ADDRESS $expect_out(1,string) ;close; break }
            }
        }
        puts "Account Address: $ACCOUNT_ADDRESS"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in VerifyAccount: $EXCEPTION"
    }
    return $ACCOUNT_ADDRESS
}

# Verify that account exists
proc ::AlgorandGoal::VerifyAccount { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal account list -w $WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
        while 1 {
            expect {
                timeout {break; ::AlgorandGoal::Abort "Timed out seeing expected account: $ACCOUNT_ADDRESS"}
                "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" }
                -re {\t([A-Z0-9]+)\t([A-Z0-9]+)} {set RETURN_ACCOUNT_ADDRESS $expect_out(1,string); break  }
            }
        }
        puts "Account Address: $RETURN_ACCOUNT_ADDRESS "
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in VerifyAccount: $EXCEPTION"
    }
}

# Delete an account
proc ::AlgorandGoal::DeleteAccount { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal account delete -d $TEST_PRIMARY_NODE_DIR --wallet $WALLET_NAME --address $ACCOUNT_ADDRESS
        expect {
            timeout { ::AlgorandGoal::Abort "Failed to delete account: $WALLET_NAME $ACCOUNT_ADDRESS"  }
            "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r"; exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed deleting account : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in DeleteAccount: $EXCEPTION"
    }
}

#Select an account from the Wallet
proc ::AlgorandGoal::GetAccountAddress { WALLET_NAME TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
        spawn goal account list -w $WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "Failed to find primary wallet: $WALLET_NAME"  }
            -re {\[online\]\t([a-zA-Z0-9]+)\t([a-zA-Z0-9]+)\t([0-9]+)} {set ACCOUNT_ADDRESS $expect_out(2,string); set ACCOUNT_BALANCE $expect_out(3,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed retrieving account address : error code [lindex $result 3]"} }
        }
        puts "Primary Account Address: $ACCOUNT_ADDRESS   Balance: $ACCOUNT_BALANCE"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetAccountAddress: $EXCEPTION"
    }
    return $ACCOUNT_ADDRESS
}

# Return the Account Balance
proc ::AlgorandGoal::GetAccountBalance { WALLET_NAME ACCOUNT_ADDRESS TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [ catch {
        spawn goal account balance -w $WALLET_NAME -a $ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out retrieving account balance for wallet $WALLET_NAME and account $ACCOUNT_ADDRESS"  }
            -re {\d+} {set ACCOUNT_BALANCE  $expect_out(0,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed retrieving account balance : error code [lindex $result 3]"} }
        }
        puts "Wallet: $WALLET_NAME, Account: $ACCOUNT_ADDRESS, Balance: $ACCOUNT_BALANCE"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetAccountBalance: $EXCEPTION"
    }
    return $ACCOUNT_BALANCE
}

# Return the Account Rewards
proc ::AlgorandGoal::GetAccountRewards { WALLET_NAME ACCOUNT_ADDRESS TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    spawn goal account rewards -w $WALLET_NAME -a $ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR
    expect {
        timeout { ::AlgorandGoal::Abort "Timed out retrieving account rewards for wallet $WALLET_NAME and account $ACCOUNT_ADDRESS"  }
        -re {\d+} {set ACCOUNT_EARNINGS  $expect_out(0,string)}
    }
    puts "Wallet: $WALLET_NAME, Account: $ACCOUNT_ADDRESS, Rewards: $ACCOUNT_EARNINGS"
    return $ACCOUNT_EARNINGS
}

# Account Transfer
proc ::AlgorandGoal::AccountTransfer { FROM_WALLET_NAME FROM_WALLET_PASSWORD FROM_ACCOUNT_ADDRESS TRANSFER_AMOUNT TO_ACCOUNT_ADDRESS FEE_AMOUNT TEST_PRIMARY_NODE_DIR OUT_FILE } {
    set TRANSACTION_ID [::AlgorandGoal::AccountTransferWait $FROM_WALLET_NAME $FROM_WALLET_PASSWORD $FROM_ACCOUNT_ADDRESS $TRANSFER_AMOUNT $TO_ACCOUNT_ADDRESS $FEE_AMOUNT $TEST_PRIMARY_NODE_DIR $OUT_FILE "true"]
    return $TRANSACTION_ID
}

# Account Transfer ( with optional wait flag)
proc ::AlgorandGoal::AccountTransferWait { FROM_WALLET_NAME FROM_WALLET_PASSWORD FROM_ACCOUNT_ADDRESS TRANSFER_AMOUNT TO_ACCOUNT_ADDRESS FEE_AMOUNT TEST_PRIMARY_NODE_DIR OUT_FILE WAIT} {
    set timeout 60
    if { [ catch {
        set TRANSACTION_ID "NOT SET"
        if { $OUT_FILE == "" } {
             if { $WAIT == "" } {
                 spawn goal clerk send --fee $FEE_AMOUNT --wallet $FROM_WALLET_NAME --amount $TRANSFER_AMOUNT --from $FROM_ACCOUNT_ADDRESS --to $TO_ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR --no-wait
             } else {
                 spawn goal clerk send --fee $FEE_AMOUNT --wallet $FROM_WALLET_NAME --amount $TRANSFER_AMOUNT --from $FROM_ACCOUNT_ADDRESS --to $TO_ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR
             }
        } else {
            spawn goal clerk send --fee $FEE_AMOUNT --wallet $FROM_WALLET_NAME --amount $TRANSFER_AMOUNT --from $FROM_ACCOUNT_ADDRESS --to $TO_ACCOUNT_ADDRESS -d $TEST_PRIMARY_NODE_DIR --out $OUT_FILE
        }
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timed out transferring funds"  }
            "Please enter the password for wallet '$FROM_WALLET_NAME':" { send "$FROM_WALLET_PASSWORD\r"; exp_continue }
            -re {transaction ID: ([A-Z0-9]{52})} {set TRANSACTION_ID $expect_out(1,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed account transfer : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AccountTransfer: $EXCEPTION"
    }
    return $TRANSACTION_ID
}

# Wait for Account to achieve given balance
proc ::AlgorandGoal::WaitForAccountBalance { WALLET_NAME ACCOUNT_ADDRESS EXPECTED_BALANCE TEST_PRIMARY_NODE_DIR } {
    set timeout 60
    if { [catch {
    set i 0
        while 1 {
            incr i
            exec sleep 2

            # Retrieve the target account balance
            spawn goal account balance -a $ACCOUNT_ADDRESS -w $WALLET_NAME -d $TEST_PRIMARY_NODE_DIR
            expect {
                timeout { ::AlgorandGoal::Abort "Timed out retrieving account balance"  }
                -re {(\d+)} {set ACCOUNT_BALANCE $expect_out(0,string); exp_continue }
                eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to read account balance : error code [lindex $result 3]"} }
            }
            puts "Account Balance: $ACCOUNT_BALANCE"

            # Validate the new account balance
            if { $ACCOUNT_BALANCE == $EXPECTED_BALANCE } {
                puts "Account balance OK: $ACCOUNT_BALANCE"; break
            } else {
               puts "Account balance: '$ACCOUNT_BALANCE' does not match expected balance: '$EXPECTED_BALANCE', still waiting..."
               if { $i >= 20 } then { ::AlgorandGoal::Abort "Account balance '$ACCOUNT_BALANCE' does not match expected amount: '$EXPECTED_BALANCE', waited too long, FAIL"; break;}
            }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in WaitForAccountBalance: $EXCEPTION"
    }
    return $ACCOUNT_BALANCE
}

# Create an asset
proc ::AlgorandGoal::AssetCreate { CREATOR WALLET_NAME WALLET_PASSWORD TOTAL_SUPPLY DECIMALS ASSET_NAME UNIT_NAME TEST_PRIMARY_NODE_DIR } {
    set timeout 40
    if { [ catch {
        spawn goal asset create -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME --creator $CREATOR --total $TOTAL_SUPPLY --unitname $UNIT_NAME --name $ASSET_NAME --decimals $DECIMALS
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out create asset"  }
	        "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r"; exp_continue }
	        eof
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AssetCreate: $EXCEPTION"
    }
}

# Transfer asset
proc ::AlgorandGoal::AssetTransfer { WALLET_NAME WALLET_PASSWORD FROM_ADDR TO_ADDR ASSET_ID ASSET_AMOUNT TEST_PRIMARY_NODE_DIR} {
    # The asset transfer is being sent, and the goal command would wait until the created transaction included in a block.
    # if a round is 4 second, we're going to wait 5 times that to allow enough time for the transaction to get propageted,
    # proposed and included in a block.
    set timeout 20
    if { [ catch {
        spawn goal asset send -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME --from $FROM_ADDR --to $TO_ADDR --assetid $ASSET_ID --amount $ASSET_AMOUNT
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out asset transfer"  }
            "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r"; exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to transfer asset : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AssetTransfer: $EXCEPTION"
    }
}

# write asset transfer to a txn file
proc ::AlgorandGoal::CreateAssetTransfer { FROM_ADDR TO_ADDR ASSET_ID ASSET_AMOUNT TEST_PRIMARY_NODE_DIR TXN_OUTPUT} {
    if { [ catch {
        spawn goal asset send -d $TEST_PRIMARY_NODE_DIR --from $FROM_ADDR --to $TO_ADDR --assetid $ASSET_ID --amount $ASSET_AMOUNT -o $TXN_OUTPUT
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out creating asset transfer transaction"  }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to create asset transfer transaction : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in CreateAssetTransfer: $EXCEPTION"
    }
}

# Freeze asset
proc ::AlgorandGoal::AssetFreeze { WALLET_NAME WALLET_PASSWORD FREEZE_ADDR ACCOUNT_ADDR ASSET_ID FREEZE_VALUE TEST_PRIMARY_NODE_DIR} {
    if { [ catch {
        spawn goal asset freeze -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME --freezer $FREEZE_ADDR --account $ACCOUNT_ADDR --assetid $ASSET_ID --freeze=$FREEZE_VALUE
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out asset freeze"  }
	        "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r"; exp_continue }
	        eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to freeze asset : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AssetTransfer: $EXCEPTION"
    }
}

# Get asset id
proc ::AlgorandGoal::AssetLookup { CREATOR UNIT_NAME TEST_PRIMARY_NODE_DIR } {
    set timeout 10
    if { [ catch {
        set ASSET_ID "NOT SET"
        spawn goal asset info -d $TEST_PRIMARY_NODE_DIR --creator $CREATOR  --unitname $UNIT_NAME
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out asset lookup"  }
            -re {Asset ID:\s+([0-9]+)} {set ASSET_ID $expect_out(1,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to get asset info : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AssetLookup: $EXCEPTION"
    }
    return $ASSET_ID
}

# Assemble group txn
proc ::AlgorandGoal::AssembleGroup { INPUT_GROUP OUTPUT_GROUP } {
    if { [ catch {
        spawn goal clerk group -i $INPUT_GROUP -o $OUTPUT_GROUP
	    expect {
            timeout { ::AlgorandGoal::Abort "Timed out assembling group transaction"  }
	        eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to assemble group : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AssembleGroup: $EXCEPTION"
    }
}

# Split group txn
proc ::AlgorandGoal::SplitGroup { INPUT_GROUP OUTPUT_GROUP } {
    if { [ catch {
        spawn goal clerk split -i $INPUT_GROUP -o $OUTPUT_GROUP
	    expect {
            timeout { ::AlgorandGoal::Abort "Timed out splitting group transaction"  }
	        eof
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in Split Group: $EXCEPTION"
    }
}

# Generate limit order code from teal template
proc ::AlgorandGoal::LimitOrder {TEAL_DRIVER SWAP_N SWAP_D MIN_TRD OWNER FEE TIME_OUT ASSET_ID TEAL_OUTPUT}  {
    if { [ catch {
	set limitf [open $TEAL_OUTPUT w]
        spawn python $TEAL_DRIVER "limit-order" --swapn $SWAP_N --swapd $SWAP_D --mintrd $MIN_TRD --own $OWNER  --fee $FEE --timeout $TIME_OUT --asset $ASSET_ID
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out limit order"  }
            -re {^.+$} { puts $limitf $expect_out(buffer); close $limitf; close }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in LimitOrder: $EXCEPTION"
    }
}

# Compile
proc ::AlgorandGoal::TealCompile { TEAL_SOURCE } {
    if { [ catch {
        set TEAL_HASH "NOT SET"
        spawn goal clerk compile $TEAL_SOURCE
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out compiling $TEAL_SOURCE"  }
            -re {[A-Z2-9]{58}} {set TEAL_HASH $expect_out(0,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to compile teal : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in TealCompile: $EXCEPTION"
    }
    return $TEAL_HASH
}

# Compile and sign
proc ::AlgorandGoal::TealCompileSign { TEAL_SOURCE TEAL_BYTES ACCOUNT_ADDRESS } {
    if { [ catch {
        set TEAL_HASH "NOT SET"
        spawn goal clerk compile $TEAL_SOURCE -o $TEAL_BYTES -s -a $ACCOUNT_ADDRESS
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out compiling and signing $TEAL_SOURCE using $ACCOUNT_ADDRESS"  }
            -re {[A-Z2-9]{58}} {set TEAL_HASH $expect_out(0,string); exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to compile teal and sign: error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in TealCompileSign: $EXCEPTION"
    }
    return $TEAL_HASH
}

# create txn with logic sig
proc ::AlgorandGoal::TealTxnCreate { TEAL_SOURCE TO_ADDR CLOSE_TO SEND_AMOUNT NODE_DIR OUTPUT_TXN } {
    if { [ catch {
        spawn goal clerk send --from-program $TEAL_SOURCE --to $TO_ADDR -c $CLOSE_TO --amount $SEND_AMOUNT -d $NODE_DIR -o $OUTPUT_TXN
    expect {
            timeout { ::AlgorandGoal::Abort "Timed out Teal transaction create"  }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to create teal transaction: error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in TealTxnCreate: $EXCEPTION"
    }
}

# Sign Transaction
proc ::AlgorandGoal::SignTransaction { WALLET_NAME WALLET_PASSWORD INPUT_TXN OUTPUT_TXN TEST_PRIMARY_NODE_DIR} {
    if { [ catch {
        spawn goal clerk sign -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME -i $INPUT_TXN -o $OUTPUT_TXN
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out signing transaction"  }
	        "Please enter the password for wallet '$WALLET_NAME':" { send "$WALLET_PASSWORD\r" ; exp_continue}
	        eof
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in SignTransaction: $EXCEPTION"
    }
}

# Raw send
proc ::AlgorandGoal::RawSend { TXN_FILE TEST_PRIMARY_NODE_DIR } {
    set timeout 30
    if { [ catch {
        set TRANSACTION_ID "NOT SET"
        spawn goal clerk rawsend -f $TXN_FILE -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timed out rawsend $TXN_FILE"  }
            -re {Transaction ([A-Z0-9]{52}) committed} {set TRANSACTION_ID $expect_out(1,string); close }
	        -re {Rejected transactions written to (.+rej)} {::AlgorandGoal::Abort "RawSend rejected."}
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in RawSend: $EXCEPTION"
    }
    return $TRANSACTION_ID
}

# Return the highest funded account for the given Wallet
proc ::AlgorandGoal::GetHighestFundedAccountForWallet { WALLET_NAME NODE_DIR } {
    if { [catch {
        spawn goal account list -w $WALLET_NAME -d $NODE_DIR
        set ACCOUNT_BALANCE 0
        set ACCOUNT_ADDRESS "NONE"
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out finding primary wallet: $WALLET_NAME"  }
            -re {\[n/a\]\t([a-zA-Z0-9]+)\t([a-zA-Z0-9]+)\t\[n/a\]} {
                    set CURRENT_ACCOUNT_ADDRESS $expect_out(2,string);
                    if { $ACCOUNT_BALANCE == 0 } then { set ACCOUNT_ADDRESS $CURRENT_ACCOUNT_ADDRESS };
                    exp_continue;
                }
            -re {\[online\]\t([a-zA-Z0-9]+)\t([a-zA-Z0-9]+)\t([0-9]+)} {
                    set CURRENT_ACCOUNT_ADDRESS $expect_out(2,string);
                    set  CURRENT_ACCOUNT_BALANCE $expect_out(3,string);
                    if { $CURRENT_ACCOUNT_BALANCE > $ACCOUNT_BALANCE } then { set ACCOUNT_BALANCE $CURRENT_ACCOUNT_BALANCE; set ACCOUNT_ADDRESS $CURRENT_ACCOUNT_ADDRESS };
                    exp_continue;
                }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to retrieve highest funded account : error code [lindex $result 3]"} }
        }
        puts "Account Address: $ACCOUNT_ADDRESS   Balance: $ACCOUNT_BALANCE"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetHighestFundedAccountForWallet: $EXCEPTION"
    }
    return $ACCOUNT_ADDRESS
}

# Return the Algod Network Address for the node directory
proc ::AlgorandGoal::GetAlgodNetworkAddress { TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        set NET_FILE [open $TEST_PRIMARY_NODE_DIR/algod.net]
        set PRIMARY_NODE_ADDRESS [string trim [read $NET_FILE ]]
        puts "Primary node address is: $PRIMARY_NODE_ADDRESS"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetAlgodNetworkAddress: $EXCEPTION"
    }
    return $PRIMARY_NODE_ADDRESS
}

# Return the KMD Network Address
proc ::AlgorandGoal::GetKMDNetworkAddress { TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        set KMD_DIR [glob -type d -directory $TEST_PRIMARY_NODE_DIR kmd-v*]
        set NET_FILE [open $KMD_DIR/kmd.net]
        set KMD_NET_ADDRESS [string trim [read $NET_FILE ]]
        puts "KMD network address is: $KMD_NET_ADDRESS"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetKMDNetworkAddress: $EXCEPTION"
    }
    return $KMD_NET_ADDRESS
}

# Use curl to check if a network address supports CORS
proc ::AlgorandGoal::CheckNetworkAddressForCors { NET_ADDRESS } {
    if { [ catch {
        spawn curl -X OPTIONS -H "Origin: http://algorand.com" --head $NET_ADDRESS
        expect {
            timeout { close; ::AlgorandGoal::Abort "Timeout failure in CheckNetworkAddressForCors" }
            "Access-Control-Allow-Origin" { puts "success" ; close  }
            close
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in CheckNetworkAddressForCors: $EXCEPTION"
    }
}

# Show the Ledger Supply
proc ::AlgorandGoal::GetLedgerSupply { TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        spawn goal ledger supply -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "Get Ledger Supply timed out"  }
            -re {Round: (\d+)} {set ROUND $expect_out(1,string); exp_continue }
            -re {Total Money: (\d+)} {set TOTAL_MONEY $expect_out(1,string); exp_continue }
            -re {Online Money: (\d+)} {set ONLINE_MONEY $expect_out(1,string) }
        }
        puts "Round: $ROUND"
        puts "Total Money: $TOTAL_MONEY"
        puts "Online Money: $ONLINE_MONEY"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetLedgerSupply: $EXCEPTION"
    }
}

# Create a multisig address from two accounts
proc ::AlgorandGoal::CreateOneOfTwoMultisigForWallet { ADDRESS_1 ADDRESS_2 WALLET_NAME WALLET_PASSWORD TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        spawn goal account multisig new $ADDRESS_1 $ADDRESS_2 -T 1 -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out creating a multisig account from $ADDRESS_1 and $ADDRESS_2"  }
            "Please enter the password for wallet*" { send "$WALLET_PASSWORD\r" }
            -re {Created new account with address ([a-zA-Z0-9]+)} {
                    set MULTISIG_ADDRESS $expect_out(1,string);
                    close;
            }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in CreateOneOfTwoMultisigForWallet: $EXCEPTION"
    }
    return $MULTISIG_ADDRESS
}

# Query info for a 1-of-2 multisig account, and verify
proc ::AlgorandGoal::VerifyMultisigInfoForOneOfTwoMultisig { MULTISIG_ADDRESS ADDRESS_1 ADDRESS_2 WALLET_NAME TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        spawn goal account multisig info --address $MULTISIG_ADDRESS -d $TEST_PRIMARY_NODE_DIR -w $WALLET_NAME
        expect {
            timeout { ::AlgorandGoal::Abort "Timed out querying info about multisig account $MULTISIG_ADDRESS"  }
            -re {Version: (\d+)\r\nThreshold: (\d+)\r\nPublic keys:\r\n  ([a-zA-Z0-9]+)\r\n  ([a-zA-Z0-9]+)\r\n} {
                set VERSION $expect_out(1,string);
                set THRESHOLD $expect_out(2,string);
                set ADDRESS_RESPONSE_1 $expect_out(3,string);
                set ADDRESS_RESPONSE_2 $expect_out(4,string);
                close;
            }
        }
        if { $THRESHOLD != "1" } then { ::AlgorandGoal::Abort "Key threshold $THRESHOLD is not the expected threshold of 1"  }
        if { $ADDRESS_RESPONSE_1 != $ADDRESS_1 } then { ::AlgorandGoal::Abort "Responded address $ADDRESS_RESPONSE_1 is not the expected address $ADDRESS_1"  }
        if { $ADDRESS_RESPONSE_2 != $ADDRESS_2 } then { ::AlgorandGoal::Abort "Responded address $ADDRESS_RESPONSE_2 is not the expected address $ADDRESS_2"  }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in VerifyMultisigInfoForOneOfTwoMultisig: $EXCEPTION"
    }
}

# Delete a multisig address
proc ::AlgorandGoal::DeleteMultisigAccount { MULTISIG_ADDRESS TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        spawn goal account multisig delete --address $MULTISIG_ADDRESS -d $TEST_PRIMARY_NODE_DIR
        expect {*}
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in DeleteMultisigAccount: $EXCEPTION"
    }
}

# Retrieve the node last catchpoint
proc ::AlgorandGoal::GetNodeLastCatchpoint { NODE_DATA_DIR } {
    set CATCHPOINT ""
    if { [catch {
        # Check node status
        puts "spawn node status"
        spawn goal node status -d $NODE_DATA_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal node status timed out" }
            -re {Last Catchpoint: ([0-9]*#[A-Z2-7]*)} {regexp -- {[0-9]*#[A-Z2-7]*} $expect_out(0,string) CATCHPOINT; exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to perform goal node status : error code [lindex $result 3]"} }
        }
        if { $CATCHPOINT == "" } {
            ::AlgorandGoal::Abort "Last Catchpoint entry was missing from goal node status"
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetNodeLastCatchpoint: $EXCEPTION"
    }
    return $CATCHPOINT
}


# Get node's last reached round
proc ::AlgorandGoal::GetNodeLastCommittedBlock { NODE_DATA_DIR } {
    set COMMITTEDROUND ""
    if { [catch {
        # Check node status
        puts "spawn node status"
        spawn goal node status -d $NODE_DATA_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal node status timed out" }
            -re {Last committed block: ([0-9]+)} {regexp -- {[0-9]+} $expect_out(0,string) COMMITTEDROUND; exp_continue }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to perform goal node status : error code [lindex $result 3]"} }
        }
        if { $COMMITTEDROUND == "" } {
            ::AlgorandGoal::Abort "Last committed block entry was missing from goal node status"
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in GetNodeLastCommittedBlock: $EXCEPTION"
    }
    return $COMMITTEDROUND
}

# Start catching up to a specific catchpoint
proc ::AlgorandGoal::StartCatchup { NODE_DATA_DIR CATCHPOINT } {
    if { [catch {
        # start catchup
        puts "spawn node catchup $CATCHPOINT"
        spawn goal node catchup $CATCHPOINT -d $NODE_DATA_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal node catchup timed out" }
            eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to start catching up : error code [lindex $result 3]"} }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in StartCatchup: $EXCEPTION"
    }
    return $CATCHPOINT
}

# Wait for node to get into catchup mode
proc ::AlgorandGoal::WaitCatchup { TEST_PRIMARY_NODE_DIR WAIT_DURATION_SEC } {
    if { [catch {
        set i 0
        while { $i < $WAIT_DURATION_SEC } {
            # Check node status
            puts "spawn node status  "
            spawn goal node status -d $TEST_PRIMARY_NODE_DIR
            expect {
                timeout { ::AlgorandGoal::Abort "goal node status timed out"  }
                -re {Catchpoint: ([0-9]*#[A-Z2-7]*)} { set CATCHPOINT $expect_out(1,string); exp_continue }
                eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to wait for catchup mode : error code [lindex $result 3]"} }
            }
            if { [info exists CATCHPOINT] } {
                break
            }

            incr i
            exec sleep 1
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in WaitCatchup: $EXCEPTION"
    }
    if { [info exists CATCHPOINT] == 0 } {
        ::AlgorandGoal::Abort "Node failed to start catching up within $WAIT_DURATION_SEC seconds"
    }
}

# Wait for node to reach a specific round
proc ::AlgorandGoal::WaitForRound { WAIT_FOR_ROUND_NUMBER NODE_DATA_DIR } {
    puts "node status waiting for Round $WAIT_FOR_ROUND_NUMBER "
    set LAST_ROUND -1
    set SLEEP_TIME 1
    if { [catch {
        set i 0
        while 1 {
            incr i

            # Check node status
            puts "spawn node status"
            log_user 0
            set BLOCK -1
            spawn goal node status -d $NODE_DATA_DIR
            expect {
                timeout { ::AlgorandGoal::Abort "goal node status timed out"  }
                -re {Cannot contact Algorand node: (\d+)} {set BLOCK -1; close }
                -re {Last committed block: (\d+)} {set BLOCK $expect_out(1,string); exp_continue }
                -re {Time since last block: ([0-9]*\.?[0-9]*)s} {set TIME_SINCE_LAST_BLOCK $expect_out(1,string); exp_continue }
                -re {Sync Time: ([0-9]*\.?[0-9]*)s} {set SYNC_TIME $expect_out(1,string); exp_continue }
                -re {Last consensus protocol: ([-+=.:/_a-zA-Z0-9]+)} {set LAST_CONSENSUS_PROTOCOL $expect_out(1,string); exp_continue }
                -re {Next consensus protocol: ([-+=.:/_a-zA-Z0-9]+)} {set NEXT_CONSENSUS_PROTOCOL $expect_out(1,string); exp_continue }
                -re {Round for next consensus protocol: (\d+)} {set ROUND_FOR_NEXT_CONSENSUS_PROTOCOL $expect_out(1,string); exp_continue }
                -re {Next consensus protocol supported: (\w+)} {set NEXT_CONSENSUS_PROTOCOL_SUPPORTED $expect_out(1,string); exp_continue }
                -re {Genesis ID: (\w+)} {set GENESIS_ID $expect_out(1,string); exp_continue }
                -re {Genesis hash: ([A-Za-z0-9+/]+={0,2})} {set GENESIS_HASH $expect_out(1,string); exp_continue }
                -re {Catchpoint: ([0-9]*#[A-Z2-7]*)} { set CATCHPOINT $expect_out(1,string); exp_continue }
                eof { catch wait result; if { [lindex $result 3] != 0 } { ::AlgorandGoal::Abort "failed to wait for round : error code [lindex $result 3]"} }
            }
            log_user 1
            if { $BLOCK > -1 } {
                puts "node status check complete, current round is $BLOCK"
            } else {
                ::AlgorandGoal::Abort "failed to retrieve block round number"
            }

            # Check if the round number is reached
            if { $BLOCK >= $WAIT_FOR_ROUND_NUMBER } {
                puts "Reached Round number: $WAIT_FOR_ROUND_NUMBER"; break
            } else {
               puts "Current Round: '$BLOCK' is less than wait for round: '$WAIT_FOR_ROUND_NUMBER'"
               if { $LAST_ROUND >= $BLOCK } {
                   # no progress was made since last time we checked.
                   incr SLEEP_TIME
               } else {
                   # we've made progress since last time we checked.
                   if { $SLEEP_TIME > 0 } { incr SLEEP_TIME -1 }
                   set i 0
                   set LAST_ROUND $BLOCK
               }
               if { $i >= 10 } then { ::AlgorandGoal::Abort " Current Round $BLOCK did not reach $WAIT_FOR_ROUND_NUMBER   "; break;}
            }

            puts "sleep time $SLEEP_TIME"
            after [expr {int($SLEEP_TIME * 1000)}]
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in WaitForRound: $EXCEPTION"
    }
    return $BLOCK
}

# Generate report
proc ::AlgorandGoal::Report { TEST_PRIMARY_NODE_DIR } {
    if { [ catch {
        spawn goal report -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal report timed out"  }
            "source code available at https://github.com/algorand/go-algorand" {puts "goal -v ok"}
            -re {Genesis ID from genesis.json: *} {puts "genesis ID from genesis.json ok"}
            -re {Last committed block: (\d+)} {puts "status check ok"}
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in Report: $EXCEPTION"
    }
}

# List Participation keys
proc ::AlgorandGoal::ListParticipationKeys { TEST_PRIMARY_NODE_DIR } {
    # the function account.RestoreParticipation.func1 seems to be running slow on
    # arm64; for now, we'll give it abit more time to run:
    set GOARCH [exec go env GOARCH]
    if { $GOARCH == "arm64" } {
        set timeout 20
    }
    if { [ catch {
        spawn goal account listpartkeys -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal ListParticipationKeys timed out"  }
            eof { ::AlgorandGoal::CheckEOF "failed to list participation keys" }
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in ListParticipationKeys: $EXCEPTION"
    }
}

# Add a participation key
proc ::AlgorandGoal::AddParticipationKey { ADDRESS FIRST_ROUND LAST_ROUND TEST_PRIMARY_NODE_DIR } {
    # we need to set the timeout to more than one round, since it might take few rounds for the transaction to be accepted, transmitted, proposed, etc.
    set timeout 20
    if { [ catch {
        spawn goal account addpartkey --address $ADDRESS --roundFirstValid $FIRST_ROUND --roundLastValid $LAST_ROUND -d $TEST_PRIMARY_NODE_DIR
        expect {
            timeout { ::AlgorandGoal::Abort "goal AddParticipationKey timed out"  }
            eof { ::AlgorandGoal::CheckEOF "failed to AddParticipationKey" }
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in AddParticipationKey: $EXCEPTION"
    }
}

# Register online participation with a given account
proc ::AlgorandGoal::TakeAccountOnline { ADDRESS FIRST_ROUND LAST_ROUND TEST_PRIMARY_NODE_DIR } {
    # we need to set the timeout to more than one round, since it might take few rounds for the transaction to be accepted, transmitted, proposed, etc.
    set timeout 20

    if { [ catch {
       spawn goal account changeonlinestatus --address $ADDRESS --firstvalid $FIRST_ROUND --lastvalid $LAST_ROUND -d $TEST_PRIMARY_NODE_DIR
       expect {
           timeout { ::AlgorandGoal::Abort "goal TakeAccountOnline timed out"  }
           eof { ::AlgorandGoal::CheckEOF "failed to change account online status" }
       }
   } EXCEPTION ] } {
   ::AlgorandGoal::Abort "ERROR in TakeAccountOnline: $EXCEPTION"
   }
}

# Stateful Teal App Procedures

# App Create with 0 arguments
proc ::AlgorandGoal::AppCreate0 { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APPROVAL_PROGRAM GLOBAL_BYTE_SLICES LOCAL_BYTE_SLICES CLEAR_PROGRAM DATA_DIR } {
    set timeout 60
    if { [ catch {
        set APP_ID "NOT SET"
        puts "calling goal app create"
        spawn goal app create --creator $ACCOUNT_ADDRESS --approval-prog $APPROVAL_PROGRAM --global-byteslices $GLOBAL_BYTE_SLICES --global-ints 0 --local-byteslices $LOCAL_BYTE_SLICES --local-ints 0 --clear-prog $CLEAR_PROGRAM -w $WALLET_NAME -d $DATA_DIR
        expect {
            timeout { abort "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue }
            -re {Created app with app index ([0-9]+)} {set APP_ID $expect_out(1,string) ;close  }
            eof {close; ::AlgorandGoal::Abort "app not created" }
        }
        puts "App ID $APP_ID"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppCreate: $EXCEPTION"
    }
    return $APP_ID
}

# App Create with 1 argument
proc ::AlgorandGoal::AppCreate { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APPROVAL_PROGRAM APP_ARG GLOBAL_BYTE_SLICES LOCAL_BYTE_SLICES CLEAR_PROGRAM DATA_DIR } {
    set timeout 60
    if { [ catch {
        set APP_ID "NOT SET"
        puts "calling goal app create"
        spawn goal app create --creator $ACCOUNT_ADDRESS --approval-prog $APPROVAL_PROGRAM --global-byteslices $GLOBAL_BYTE_SLICES --global-ints 0 --local-byteslices $LOCAL_BYTE_SLICES --local-ints 0 --app-arg $APP_ARG --clear-prog $CLEAR_PROGRAM -w $WALLET_NAME -d $DATA_DIR
        expect {
            timeout { abort "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue }
            -re {Created app with app index ([0-9]+)} {set APP_ID $expect_out(1,string) ;close  }
            eof {close; ::AlgorandGoal::Abort "app not created" }
        }
        puts "App ID $APP_ID"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppCreate: $EXCEPTION"
    }
    return $APP_ID
}

# App Create with extra pages
proc ::AlgorandGoal::AppCreateExPages { WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APPROVAL_PROGRAM APP_ARG GLOBAL_BYTE_SLICES LOCAL_BYTE_SLICES CLEAR_PROGRAM DATA_DIR EXTRA_PAGES } {
    set timeout 60
    if { [ catch {
        set APP_ID "NOT SET"
        puts "calling goal app create with extra pages"
        spawn goal app create --creator $ACCOUNT_ADDRESS --approval-prog $APPROVAL_PROGRAM --global-byteslices $GLOBAL_BYTE_SLICES --global-ints 0 --local-byteslices $LOCAL_BYTE_SLICES --local-ints 0 --app-arg $APP_ARG --clear-prog $CLEAR_PROGRAM --extra-pages $EXTRA_PAGES -w $WALLET_NAME  -d $DATA_DIR
        expect {
            timeout { abort "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue }
            -re {Created app with app index ([0-9]+)} {set APP_ID $expect_out(1,string) ;close  }
            eof {close; ::AlgorandGoal::Abort "app not created" }
        }
        puts "App ID $APP_ID"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppCreate: $EXCEPTION"
    }
    return $APP_ID
}

# App Create with onCompletion argument
proc ::AlgorandGoal::AppCreateOnCompletion { \
						 WALLET_NAME \
						 WALLET_PASSWORD \
						 ACCOUNT_ADDRESS \
						 APPROVAL_PROGRAM \
						 APP_ARG GLOBAL_BYTE_SLICES \
						 LOCAL_BYTE_SLICES \
						 CLEAR_PROGRAM \
						 DATA_DIR \
						 ON_COMPLETION} {
    set timeout 60
    if { [ catch {
        set APP_ID "NOT SET"
        puts "calling goal app create"
        spawn goal app create \
	    --creator $ACCOUNT_ADDRESS \
	    --on-completion $ON_COMPLETION \
	    --approval-prog $APPROVAL_PROGRAM \
	    --global-byteslices $GLOBAL_BYTE_SLICES \
	    --global-ints 0 \
	    --local-byteslices $LOCAL_BYTE_SLICES \
	    --local-ints 1 \
	    --app-arg $APP_ARG \
	    --clear-prog $CLEAR_PROGRAM \
	    -w $WALLET_NAME \
	    -d $DATA_DIR
        expect {
            timeout { abort "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue }
            -re {Created app with app index ([0-9]+)} {set APP_ID $expect_out(1,string) ;close  }
            eof {close; ::AlgorandGoal::Abort "app not created" }
        }
        puts "App ID $APP_ID"
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppCreate: $EXCEPTION"
    }
    return $APP_ID
}

# App OptIn with 1 argument
proc ::AlgorandGoal::AppOptIn { APP_ID WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APP_ARG1 DATA_DIR } {
    set timeout 60
    if { [ catch {
        puts "opting in account to app"
        spawn goal app optin --app-id $APP_ID --app-arg $APP_ARG1 -w $WALLET_NAME --from $ACCOUNT_ADDRESS -d $DATA_DIR
        expect {
            timeout { puts timeout; ::AlgorandGoal::Abort  "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue}
            "*committed in round*" {puts "optin successful"; close}
            eof {close; ::AlgorandGoal::Abort "optin failed" }
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppOptIn: $EXCEPTION"
    }
}

# App Call with 1 argument
proc ::AlgorandGoal::AppCall { APP_ID WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APP_ARG1 DATA_DIR } {
    set timeout 60
    if { [ catch {
        puts "app call with 1 arg: '$APP_ARG1'"
        spawn goal app call --app-id $APP_ID --app-arg $APP_ARG1 -w $WALLET_NAME --from $ACCOUNT_ADDRESS -d $DATA_DIR
        expect {
            timeout { puts timeout; ::AlgorandGoal::Abort  "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue}
            "*committed in round*" {puts "app call succeeded"; close}
            eof {close; ::AlgorandGoal::Abort "app call failed" }
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in AppCall: $EXCEPTION"
    }
}

# App Call with 2 arguments
proc ::AlgorandGoal::AppCall2 { APP_ID WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APP_ARG1 APP_ARG2 DATA_DIR } {
  set timeout 60
  if { [ catch {
      puts "app call with 2 args: '$APP_ARG1' and '$APP_ARG2'"
      spawn goal app call --app-id $APP_ID --app-arg $APP_ARG1 --app-arg $APP_ARG2 -w $WALLET_NAME --from $ACCOUNT_ADDRESS -d $DATA_DIR
      expect {
          timeout { puts timeout; ::AlgorandGoal::Abort  "\n Failed to see expected output" }
          "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue}
          "*committed in round*" {puts "app call succeeded"; close}
          eof {close; ::AlgorandGoal::Abort "app call failed" }
      }
  } EXCEPTION ] } {
     ::AlgorandGoal::Abort "ERROR in AppCall2: $EXCEPTION"
  }
}

# App Closeout with 1 argument
proc ::AlgorandGoal::AppCloseout { APP_ID WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APP_ARG1 DATA_DIR } {
    set timeout 60
    if { [ catch {
        puts "calling goal app close out with first arg '$APP_ARG1'"
        spawn goal app closeout --app-id $APP_ID --app-arg $APP_ARG1 -w $WALLET_NAME --from $ACCOUNT_ADDRESS -d $DATA_DIR
        expect {
            timeout { puts timeout; ::AlgorandGoal::Abort  "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue }
            "*committed in round*" {puts "app closeout succeeded"; close}
            eof {close; ::AlgorandGoal::Abort "app closeout failed"}
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in AppCloseout: $EXCEPTION"
    }
}

# App Delete with 1 argument
proc ::AlgorandGoal::AppDelete { APP_ID WALLET_NAME WALLET_PASSWORD ACCOUNT_ADDRESS APP_ARG1 DATA_DIR } {
    set timeout 60
    if { [ catch {
        puts "call app delete with '$APP_ARG1'"
        spawn goal app delete --app-id $APP_ID --app-arg $APP_ARG1 -w $WALLET_NAME --from $ACCOUNT_ADDRESS -d $DATA_DIR
        expect {
            timeout { puts timeout; ::AlgorandGoal::Abort  "\n Failed to see expected output" }
            "Please enter the password for wallet '$WALLET_NAME':" {send "$WALLET_PASSWORD\r" ; exp_continue}
            "*committed in round*" {puts "app delete successful"; close}
            eof {close; ::AlgorandGoal::Abort "app delete failed" }
        }
    } EXCEPTION ] } {
        ::AlgorandGoal::Abort "ERROR in AppDelete: $EXCEPTION"
    }
}

# Compile teal program
proc ::AlgorandGoal::AppCompile {  INPUT_PROGRAM COMPILED_OUTPUT DATA_DIR } {
    set timeout 60
    if { [ catch {
        puts "calling goal clerk compile for $INPUT_PROGRAM"
        exec goal clerk compile ${INPUT_PROGRAM} -o ${COMPILED_OUTPUT} -d ${DATA_DIR}
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in AppCompile: $EXCEPTION"
    }
}

# CheckEOF checks if there was an error, and aborts if there was an error
proc ::AlgorandGoal::CheckEOF { { ERROR_STRING "" } } {
    upvar spawn_id spawn_id
    catch { wait -i $spawn_id } result;
    if { [lindex $result 3] != 0 } {
        puts $ERROR_STRING
        puts "returned error code is [lindex $result 3]"
        exit 1
    }
}

# Print contents of transaction file
proc ::AlgorandGoal::InspectTransactionFile { TRX_FILE } {
    puts "\n Inspect $TRX_FILE"
    spawn goal clerk inspect $TRX_FILE
    expect {
        eof
    }
}

# Run pingpong test
proc ::AlgorandGoal::RunPingpong {DURATION PINGPONG_OPTIONS TEST_PRIMARY_NODE_DIR} {
    set timeout [expr $DURATION + 60]
    if { [ catch {
        set pingpong_base "pingpong run --duration $DURATION -d $TEST_PRIMARY_NODE_DIR --quiet "
        set pingpong_command [concat $pingpong_base $PINGPONG_OPTIONS]
        puts "starting pingpong test with command:  $pingpong_command"
        eval spawn $pingpong_command
        expect {
            timeout { puts "pingpong test interrupted by timeout, terminating after $timeout seconds" }
            -re {Sent (\d+) transactions \((\d+) attempted\).} {
                    set actual $expect_out(1,string) ;
                    set attempted $expect_out(2,string) ;
                    puts "actual: $actual, attempted: $attempted";
                    if { $actual != $attempted } then { ::AlgorandGoal::Abort "Pingpong attempted to send $attempted transactions, but actual was $actual"; break;}
                    exp_continue
                }
            "Terminating after max run time of" {puts "end of ping pong test"}
            eof {::AlgorandGoal::Abort "pingpong terminated unexpectedly: $expect_out(buffer)"}
            "Error" {::AlgorandGoal::Abort "error running pingpong: $expect_out(buffer)"}
        }
    } EXCEPTION ] } {
       ::AlgorandGoal::Abort "ERROR in RunPingpong: $EXCEPTION"
    }
}