Skip to content

Commit 1f93f49

Browse files
committed
Test heartbeat service, restrict free heartbeats
1 parent 5191eb5 commit 1f93f49

36 files changed

+675
-540
lines changed

agreement/gossip/networkFull_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ func spinNetwork(t *testing.T, nodesCount int, cfg config.Local) ([]*networkImpl
103103
break
104104
}
105105
}
106-
log.Infof("network established, %d nodes connected in %s", nodesCount, time.Now().Sub(start).String())
106+
log.Infof("network established, %d nodes connected in %s", nodesCount, time.Since(start).String())
107107
return networkImpls, msgCounters
108108
}
109109

catchup/universalFetcher.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ func (uf *universalBlockFetcher) fetchBlock(ctx context.Context, round basics.Ro
8888
} else {
8989
return nil, nil, time.Duration(0), fmt.Errorf("fetchBlock: UniversalFetcher only supports HTTPPeer and UnicastPeer")
9090
}
91-
downloadDuration = time.Now().Sub(blockDownloadStartTime)
91+
downloadDuration = time.Since(blockDownloadStartTime)
9292
block, cert, err := processBlockBytes(fetchedBuf, round, address)
9393
if err != nil {
9494
return nil, nil, time.Duration(0), err

cmd/goal/clerk.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,7 @@ func waitForCommit(client libgoal.Client, txid string, transactionLastValidRound
221221
}
222222

223223
reportInfof(infoTxPending, txid, stat.LastRound)
224-
// WaitForRound waits until round "stat.LastRound+1" is committed
225-
stat, err = client.WaitForRound(stat.LastRound)
224+
stat, err = client.WaitForRound(stat.LastRound + 1)
226225
if err != nil {
227226
return model.PendingTransactionResponse{}, fmt.Errorf(errorRequestFail, err)
228227
}

cmd/loadgenerator/main.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -200,22 +200,23 @@ func waitForRound(restClient client.RestClient, cfg config, spendingRound bool)
200200
time.Sleep(1 * time.Second)
201201
continue
202202
}
203-
if isSpendRound(cfg, nodeStatus.LastRound) == spendingRound {
203+
lastRound := nodeStatus.LastRound
204+
if isSpendRound(cfg, lastRound) == spendingRound {
204205
// time to send transactions.
205206
return
206207
}
207208
if spendingRound {
208-
fmt.Printf("Last round %d, waiting for spending round %d\n", nodeStatus.LastRound, nextSpendRound(cfg, nodeStatus.LastRound))
209+
fmt.Printf("Last round %d, waiting for spending round %d\n", lastRound, nextSpendRound(cfg, nodeStatus.LastRound))
209210
}
210211
for {
211212
// wait for the next round.
212-
nodeStatus, err = restClient.WaitForBlock(basics.Round(nodeStatus.LastRound))
213+
err = restClient.WaitForRoundWithTimeout(lastRound + 1)
213214
if err != nil {
214215
fmt.Fprintf(os.Stderr, "unable to wait for next round node status : %v", err)
215-
time.Sleep(1 * time.Second)
216216
break
217217
}
218-
if isSpendRound(cfg, nodeStatus.LastRound) == spendingRound {
218+
lastRound++
219+
if isSpendRound(cfg, lastRound) == spendingRound {
219220
// time to send transactions.
220221
return
221222
}

daemon/algod/api/client/restClient.go

Lines changed: 80 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"net/http"
2727
"net/url"
2828
"strings"
29+
"time"
2930

3031
"github.com/google/go-querystring/query"
3132

@@ -39,6 +40,8 @@ import (
3940
"github.com/algorand/go-algorand/ledger/eval"
4041
"github.com/algorand/go-algorand/ledger/ledgercore"
4142
"github.com/algorand/go-algorand/protocol"
43+
"github.com/algorand/go-algorand/rpcs"
44+
"github.com/algorand/go-algorand/test/e2e-go/globals"
4245
)
4346

4447
const (
@@ -283,12 +286,77 @@ func (client RestClient) Status() (response model.NodeStatusResponse, err error)
283286
return
284287
}
285288

286-
// WaitForBlock returns the node status after waiting for the given round.
287-
func (client RestClient) WaitForBlock(round basics.Round) (response model.NodeStatusResponse, err error) {
289+
// WaitForBlockAfter returns the node status after trying to wait for the given
290+
// round+1. This REST API has the documented misfeatures of returning after 1
291+
// minute, regardless of whether the given block has been reached.
292+
func (client RestClient) WaitForBlockAfter(round basics.Round) (response model.NodeStatusResponse, err error) {
288293
err = client.get(&response, fmt.Sprintf("/v2/status/wait-for-block-after/%d/", round), nil)
289294
return
290295
}
291296

297+
// WaitForRound returns the node status after waiting for the given round.
298+
func (client RestClient) WaitForRound(round uint64, waitTime time.Duration) (status model.NodeStatusResponse, err error) {
299+
timeout := time.NewTimer(waitTime)
300+
for {
301+
status, err = client.Status()
302+
if err != nil {
303+
return
304+
}
305+
306+
if status.LastRound >= round {
307+
return
308+
}
309+
select {
310+
case <-timeout.C:
311+
return model.NodeStatusResponse{}, fmt.Errorf("timeout waiting for round %v with last round = %v", round, status.LastRound)
312+
case <-time.After(200 * time.Millisecond):
313+
}
314+
}
315+
}
316+
317+
const singleRoundMaxTime = globals.MaxTimePerRound * 40
318+
319+
// WaitForRoundWithTimeout waits for a given round to be reached. As it
320+
// waits, it returns early with an error if the wait time for any round exceeds
321+
// globals.MaxTimePerRound so we can alert when we're getting "hung" waiting.
322+
func (client RestClient) WaitForRoundWithTimeout(roundToWaitFor uint64) error {
323+
status, err := client.Status()
324+
if err != nil {
325+
return err
326+
}
327+
lastRound := status.LastRound
328+
329+
// If node is already at or past target round, we're done
330+
if lastRound >= roundToWaitFor {
331+
return nil
332+
}
333+
334+
roundComplete := make(chan error, 2)
335+
336+
for nextRound := lastRound + 1; lastRound < roundToWaitFor; nextRound++ {
337+
roundStarted := time.Now()
338+
339+
go func(done chan error) {
340+
stat, err := client.WaitForRound(nextRound, singleRoundMaxTime)
341+
lastRound = stat.LastRound
342+
done <- err
343+
}(roundComplete)
344+
345+
select {
346+
case lastError := <-roundComplete:
347+
if lastError != nil {
348+
close(roundComplete)
349+
return lastError
350+
}
351+
case <-time.After(singleRoundMaxTime):
352+
// we've timed out.
353+
time := time.Since(roundStarted)
354+
return fmt.Errorf("fixture.WaitForRound took %3.2f seconds between round %d and %d", time.Seconds(), lastRound, nextRound)
355+
}
356+
}
357+
return nil
358+
}
359+
292360
// HealthCheck does a health check on the potentially running node,
293361
// returning an error if the API is down
294362
func (client RestClient) HealthCheck() error {
@@ -301,14 +369,6 @@ func (client RestClient) ReadyCheck() error {
301369
return client.get(nil, "/ready", nil)
302370
}
303371

304-
// StatusAfterBlock waits for a block to occur then returns the StatusResponse after that block
305-
// blocks on the node end
306-
// Not supported
307-
func (client RestClient) StatusAfterBlock(blockNum uint64) (response model.NodeStatusResponse, err error) {
308-
err = client.get(&response, fmt.Sprintf("/v2/status/wait-for-block-after/%d", blockNum), nil)
309-
return
310-
}
311-
312372
type pendingTransactionsParams struct {
313373
Max uint64 `url:"max"`
314374
Format string `url:"format"`
@@ -557,6 +617,16 @@ func (client RestClient) RawBlock(round uint64) (response []byte, err error) {
557617
return
558618
}
559619

620+
// EncodedBlockCert takes a round and returns its parsed block and certificate
621+
func (client RestClient) EncodedBlockCert(round uint64) (blockCert rpcs.EncodedBlockCert, err error) {
622+
resp, err := client.RawBlock(round)
623+
if err != nil {
624+
return
625+
}
626+
err = protocol.Decode(resp, &blockCert)
627+
return
628+
}
629+
560630
// Shutdown requests the node to shut itself down
561631
func (client RestClient) Shutdown() (err error) {
562632
response := 1

data/transactions/verify/txn.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ func txnGroupBatchPrep(stxs []transactions.SignedTxn, contextHdr *bookkeeping.Bl
224224
if stxn.Txn.Type == protocol.StateProofTx {
225225
continue
226226
}
227-
if stxn.Txn.Type == protocol.HeartbeatTx && len(stxs) == 1 {
228-
// TODO: Only allow free HB if the HbAddress is challenged
227+
if stxn.Txn.Type == protocol.HeartbeatTx && stxn.Txn.Group.IsZero() {
228+
// in apply.Heartbeat, we further confirm that the heartbeat is for a challenged node
229229
continue
230230
}
231231
minFeeCount++

data/transactions/verify/txn_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,7 +575,7 @@ func TestPaysetGroups(t *testing.T) {
575575
startPaysetGroupsTime := time.Now()
576576
err := PaysetGroups(context.Background(), txnGroups, blkHdr, verificationPool, MakeVerifiedTransactionCache(50000), nil)
577577
require.NoError(t, err)
578-
paysetGroupDuration := time.Now().Sub(startPaysetGroupsTime)
578+
paysetGroupDuration := time.Since(startPaysetGroupsTime)
579579

580580
// break the signature and see if it fails.
581581
txnGroups[0][0].Sig[0] = txnGroups[0][0].Sig[0] + 1
@@ -609,7 +609,7 @@ func TestPaysetGroups(t *testing.T) {
609609
// channel is closed without a return
610610
require.Failf(t, "Channel got closed ?!", "")
611611
} else {
612-
actualDuration := time.Now().Sub(startPaysetGroupsTime)
612+
actualDuration := time.Since(startPaysetGroupsTime)
613613
if err == nil {
614614
if actualDuration > 4*time.Second {
615615
// it took at least 2.5 seconds more than it should have had!

data/transactions/verify/verifiedTxnCache_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ func BenchmarkGetUnverifiedTransactionGroups50(b *testing.B) {
127127
for i := 0; i < measuringMultipler; i++ {
128128
impl.GetUnverifiedTransactionGroups(queryTxnGroups, spec, protocol.ConsensusCurrentVersion)
129129
}
130-
duration := time.Now().Sub(startTime)
130+
duration := time.Since(startTime)
131131
// calculate time per 10K verified entries:
132132
t := int(duration*10000) / (measuringMultipler * b.N)
133133
b.ReportMetric(float64(t)/float64(time.Millisecond), "ms/10K_cache_compares")

heartbeat/service.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ import (
2626
"github.com/algorand/go-algorand/data/bookkeeping"
2727
"github.com/algorand/go-algorand/data/transactions"
2828
"github.com/algorand/go-algorand/data/transactions/logic"
29-
"github.com/algorand/go-algorand/ledger/eval"
29+
"github.com/algorand/go-algorand/ledger/apply"
3030
"github.com/algorand/go-algorand/logging"
3131
"github.com/algorand/go-algorand/protocol"
3232
)
@@ -80,7 +80,7 @@ func (s *Service) Stop() {
8080
func (s *Service) findChallenged(rules config.ProposerPayoutRules) []account.ParticipationRecordForRound {
8181
current := s.ledger.LastRound()
8282

83-
ch := eval.FindChallenge(rules, current, s.ledger, eval.ChRisky)
83+
ch := apply.FindChallenge(rules, current, s.ledger, apply.ChRisky)
8484
if ch.IsZero() {
8585
return nil
8686
}
@@ -93,8 +93,7 @@ func (s *Service) findChallenged(rules config.ProposerPayoutRules) []account.Par
9393
continue
9494
}
9595
if acct.Status == basics.Online {
96-
lastSeen := max(acct.LastProposed, acct.LastHeartbeat)
97-
if ch.Failed(pr.Account, lastSeen) {
96+
if ch.Failed(pr.Account, acct.LastSeen()) {
9897
s.log.Infof(" %v needs a heartbeat\n", pr.Account)
9998
found = append(found, pr)
10099
}
@@ -135,6 +134,7 @@ func (s *Service) loop() {
135134

136135
for _, pr := range s.findChallenged(proto.Payouts) {
137136
stxn := s.prepareHeartbeat(pr, lastHdr)
137+
s.log.Infof("sending heartbeat %v for %v\n", stxn.Txn.HeartbeatTxnFields, pr.Account)
138138
err = s.bcast.BroadcastInternalSignedTxGroup([]transactions.SignedTxn{stxn})
139139
if err != nil {
140140
s.log.Errorf("error broadcasting heartbeat %v for %v: %v", stxn, pr.Account, err)

heartbeat/service_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ func makeBlock(r basics.Round) bookkeeping.Block {
210210
}
211211
}
212212

213-
func TestHeartBeatOnlyWhenChallenged(t *testing.T) {
213+
func TestHeartbeatOnlyWhenChallenged(t *testing.T) {
214214
partitiontest.PartitionTest(t)
215215
t.Parallel()
216216

@@ -234,9 +234,9 @@ func TestHeartBeatOnlyWhenChallenged(t *testing.T) {
234234
// now they are online, but not challenged, so no heartbeat
235235
acct.Status = basics.Online
236236
acct.VoteKeyDilution = 100
237-
otss := crypto.GenerateOneTimeSignatureSecrets(
238-
basics.OneTimeIDForRound(ledger.LastRound(), acct.VoteKeyDilution).Batch,
239-
5)
237+
startBatch := basics.OneTimeIDForRound(ledger.LastRound(), acct.VoteKeyDilution).Batch
238+
const batches = 50 // gives 50 * kd rounds = 5000
239+
otss := crypto.GenerateOneTimeSignatureSecrets(startBatch, batches)
240240
acct.VoteID = otss.OneTimeSignatureVerifier
241241
ledger.addParticipant(joe, otss)
242242
ledger.addParticipant(mary, otss)

0 commit comments

Comments
 (0)