From 2db080ff96b2d0cb1833ff8bcdd3be445525dd45 Mon Sep 17 00:00:00 2001 From: algoidan <79864820+algoidan@users.noreply.github.com> Date: Wed, 3 Aug 2022 23:17:39 +0300 Subject: [PATCH] StateProofs: fix fast catchup bug (#4346) * fix fast catchup bug * fix partition issue * fix CR comments. --- catchup/catchpointService.go | 27 +++++++++++++++++++++++++ catchup/service_test.go | 39 +++++++++++++++++++++++++++++++++++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/catchup/catchpointService.go b/catchup/catchpointService.go index 3b9aa19872..f4d753243f 100644 --- a/catchup/catchpointService.go +++ b/catchup/catchpointService.go @@ -31,6 +31,7 @@ import ( "github.com/algorand/go-algorand/ledger/ledgercore" "github.com/algorand/go-algorand/logging" "github.com/algorand/go-algorand/network" + "github.com/algorand/go-algorand/protocol" ) const ( @@ -464,6 +465,27 @@ func (cs *CatchpointCatchupService) processStageLastestBlockDownload() (err erro return nil } +// lookbackForStateproofSupport calculates the lookback (from topblock round) needed to be downloaded +// in order to support state proofs verification. +func lookbackForStateproofsSupport(topBlock *bookkeeping.Block) uint64 { + proto := config.Consensus[topBlock.CurrentProtocol] + if proto.StateProofInterval == 0 { + return 0 + } + //calculate the lowest round needed for verify the upcoming state proof txn. + expectedStateProofRound := topBlock.StateProofTracking[protocol.StateProofBasic].StateProofNextRound + stateproofVerificationRound := expectedStateProofRound.SubSaturate(basics.Round(proto.StateProofInterval)) + + lowestRecoveryRound := topBlock.Round().SubSaturate(topBlock.Round() % basics.Round(proto.StateProofInterval)) + lowestRecoveryRound = lowestRecoveryRound.SubSaturate(basics.Round(proto.StateProofInterval * (proto.StateProofMaxRecoveryIntervals + 1))) + + if lowestRecoveryRound > stateproofVerificationRound { + return uint64(topBlock.Round().SubSaturate(lowestRecoveryRound)) + } + + return uint64(topBlock.Round().SubSaturate(stateproofVerificationRound)) +} + // processStageBlocksDownload is the fourth catchpoint catchup stage. It downloads all the reminder of the blocks, verifying each one of them against it's predecessor. func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { topBlock, err := cs.ledgerAccessor.EnsureFirstBlock(cs.ctx) @@ -478,6 +500,11 @@ func (cs *CatchpointCatchupService) processStageBlocksDownload() (err error) { if lookback < proto.MaxBalLookback { lookback = proto.MaxBalLookback } + + lookbackForStateProofSupport := lookbackForStateproofsSupport(&topBlock) + if lookback < lookbackForStateProofSupport { + lookback = lookbackForStateProofSupport + } // in case the effective lookback is going before our rounds count, trim it there. // ( a catchpoint is generated starting round MaxBalLookback, and this is a possible in any round in the range of MaxBalLookback..MaxTxnLife) if lookback >= uint64(topBlock.Round()) { diff --git a/catchup/service_test.go b/catchup/service_test.go index c52a59d1cd..30f883cbb2 100644 --- a/catchup/service_test.go +++ b/catchup/service_test.go @@ -26,6 +26,7 @@ import ( "time" "github.com/algorand/go-deadlock" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/algorand/go-algorand/agreement" @@ -956,7 +957,7 @@ func TestServiceStartStop(t *testing.T) { s := MakeService(logging.Base(), cfg, &httpTestPeerSource{}, ledger, &mockedAuthenticator{errorRound: int(0 + 1)}, nil, nil) s.Start() s.Stop() - _, ok := (<-s.done) + _, ok := <-s.done require.False(t, ok) } @@ -972,3 +973,39 @@ func TestSynchronizingTime(t *testing.T) { atomic.StoreInt64(&s.syncStartNS, 1000000) require.NotEqual(t, time.Duration(0), s.SynchronizingTime()) } + +func TestDownloadBlocksToSupportStateProofs(t *testing.T) { + partitiontest.PartitionTest(t) + // make sure we download enough blocks to verify state proof 512 + topBlk := bookkeeping.Block{} + topBlk.BlockHeader.Round = 1500 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + trackingData := bookkeeping.StateProofTrackingData{StateProofNextRound: 512} + topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData + + lookback := lookbackForStateproofsSupport(&topBlk) + oldestRound := topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) + assert.Equal(t, uint64(oldestRound), 512-config.Consensus[protocol.ConsensusFuture].StateProofInterval) + + // the network has made progress and now it is on round 8000. in this case we would not download blocks to cover 512. + // instead, we will download blocks to confirm only the recovery period lookback. + topBlk = bookkeeping.Block{} + topBlk.BlockHeader.Round = 8000 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusFuture + trackingData = bookkeeping.StateProofTrackingData{StateProofNextRound: 512} + topBlk.BlockHeader.StateProofTracking = make(map[protocol.StateProofType]bookkeeping.StateProofTrackingData) + topBlk.BlockHeader.StateProofTracking[protocol.StateProofBasic] = trackingData + + lookback = lookbackForStateproofsSupport(&topBlk) + oldestRound = topBlk.BlockHeader.Round.SubSaturate(basics.Round(lookback)) + + lowestRoundToRetain := 8000 - (8000 % 256) - (config.Consensus[protocol.ConsensusFuture].StateProofInterval * (config.Consensus[protocol.ConsensusFuture].StateProofMaxRecoveryIntervals + 1)) + assert.Equal(t, uint64(oldestRound), lowestRoundToRetain) + + topBlk = bookkeeping.Block{} + topBlk.BlockHeader.Round = 8000 + topBlk.BlockHeader.CurrentProtocol = protocol.ConsensusV32 + lookback = lookbackForStateproofsSupport(&topBlk) + assert.Equal(t, uint64(0), lookback) +}