Skip to content

Commit

Permalink
StateProofs: fix fast catchup bug (#4346)
Browse files Browse the repository at this point in the history
* fix fast catchup bug

* fix partition issue

* fix CR comments.
  • Loading branch information
id-ms authored Aug 3, 2022
1 parent 883bbbd commit 2db080f
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 1 deletion.
27 changes: 27 additions & 0 deletions catchup/catchpointService.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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)
Expand All @@ -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()) {
Expand Down
39 changes: 38 additions & 1 deletion catchup/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
}

Expand All @@ -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)
}

0 comments on commit 2db080f

Please sign in to comment.