Skip to content

Commit

Permalink
Merge pull request #5299 from Algo-devops-service/relstable3.15.1
Browse files Browse the repository at this point in the history
  • Loading branch information
algojohnlee authored Apr 12, 2023
2 parents 814bc0b + 6453881 commit 5f2d5ed
Show file tree
Hide file tree
Showing 7 changed files with 261 additions and 9 deletions.
2 changes: 1 addition & 1 deletion buildnumber.dat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0
1
5 changes: 5 additions & 0 deletions ledger/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,11 @@ func (l *Ledger) Validate(ctx context.Context, blk bookkeeping.Block, executionP
return &vb, nil
}

// LatestTrackerCommitted returns the trackers' dbRound which "is always exactly accountsRound()"
func (l *Ledger) LatestTrackerCommitted() basics.Round {
return l.trackers.getDbRound()
}

// DebuggerLedger defines the minimal set of method required for creating a debug balances.
type DebuggerLedger = internal.LedgerForCowBase

Expand Down
8 changes: 8 additions & 0 deletions ledger/tracker.go
Original file line number Diff line number Diff line change
Expand Up @@ -728,3 +728,11 @@ func (tr *trackerRegistry) replay(l ledgerForTracker) (err error) {
}
return
}

// getDbRound accesses dbRound with protection by the trackerRegistry's mutex.
func (tr *trackerRegistry) getDbRound() basics.Round {
tr.mu.RLock()
dbRound := tr.dbRound
tr.mu.RUnlock()
return dbRound
}
6 changes: 3 additions & 3 deletions node/follower_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,10 @@ func MakeFollower(log logging.Logger, rootDir string, cfg config.Local, phoneboo
node.catchupBlockAuth = blockAuthenticatorImpl{Ledger: node.ledger, AsyncVoteVerifier: agreement.MakeAsyncVoteVerifier(node.lowPriorityCryptoVerificationPool)}
node.catchupService = catchup.MakeService(node.log, node.config, p2pNode, node.ledger, node.catchupBlockAuth, make(chan catchup.PendingUnmatchedCertificate), node.lowPriorityCryptoVerificationPool)

// Initialize sync round to the next round so that nothing falls out of the cache on Start
err = node.SetSyncRound(uint64(node.Ledger().NextRound()))
// Initialize sync round to the latest db round + 1 so that nothing falls out of the cache on Start
err = node.SetSyncRound(uint64(node.Ledger().LatestTrackerCommitted() + 1))
if err != nil {
log.Errorf("unable to set sync round to Ledger.NextRound %v", err)
log.Errorf("unable to set sync round to Ledger.DBRound %v", err)
return nil, err
}

Expand Down
95 changes: 90 additions & 5 deletions node/follower_node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ func followNodeDefaultGenesis() bookkeeping.Genesis {
MicroAlgos: basics.MicroAlgos{Raw: 1000000000},
},
},
{
Address: sinkAddr.String(),
State: basics.AccountData{
MicroAlgos: basics.MicroAlgos{Raw: 1000000},
},
},
},
}
}
Expand All @@ -61,6 +67,20 @@ func setupFollowNode(t *testing.T) *AlgorandFollowerNode {
return node
}

func remakeableFollowNode(t *testing.T, tempDir string, maxAcctLookback uint64) (*AlgorandFollowerNode, string) {
cfg := config.GetDefaultLocal()
cfg.EnableFollowMode = true
cfg.DisableNetworking = true
cfg.MaxAcctLookback = maxAcctLookback
genesis := followNodeDefaultGenesis()
if tempDir == "" {
tempDir = t.TempDir()
}
followNode, err := MakeFollower(logging.Base(), tempDir, cfg, []string{}, genesis)
require.NoError(t, err)
return followNode, tempDir
}

func TestSyncRound(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()
Expand All @@ -73,13 +93,13 @@ func TestSyncRound(t *testing.T) {
b.CurrentProtocol = protocol.ConsensusCurrentVersion
err := node.Ledger().AddBlock(b, agreement.Certificate{})
require.NoError(t, err)
latestRound := uint64(node.Ledger().Latest())
// Sync Round should be initialized to the ledger's latest round
require.Equal(t, latestRound, node.GetSyncRound())
dbRound := uint64(node.Ledger().LatestTrackerCommitted())
// Sync Round should be initialized to the ledger's dbRound + 1
require.Equal(t, dbRound+1, node.GetSyncRound())
// Set a new sync round
require.NoError(t, node.SetSyncRound(latestRound+10))
require.NoError(t, node.SetSyncRound(dbRound+11))
// Ensure it is persisted
require.Equal(t, latestRound+10, node.GetSyncRound())
require.Equal(t, dbRound+11, node.GetSyncRound())
// Unset the sync round and make sure get returns 0
node.UnsetSyncRound()
require.Equal(t, uint64(0), node.GetSyncRound())
Expand Down Expand Up @@ -127,3 +147,68 @@ func TestDevModeWarning(t *testing.T) {
require.NotNil(t, foundEntry)
require.Contains(t, foundEntry.Message, "Follower running on a devMode network. Must submit txns to a different node.")
}

// TestSyncRoundWithRemake extends TestSyncRound to simulate starting and stopping the network
func TestSyncRoundWithRemake(t *testing.T) {
partitiontest.PartitionTest(t)
t.Parallel()

maxAcctLookback := uint64(100)

followNode, tempDir := remakeableFollowNode(t, "", maxAcctLookback)
addBlock := func(round basics.Round) {
b := bookkeeping.Block{
BlockHeader: bookkeeping.BlockHeader{
GenesisHash: followNode.ledger.GenesisHash(),
Round: round,
RewardsState: bookkeeping.RewardsState{
RewardsRate: 0,
RewardsPool: poolAddr,
FeeSink: sinkAddr,
},
},
}
b.CurrentProtocol = protocol.ConsensusCurrentVersion
err := followNode.Ledger().AddBlock(b, agreement.Certificate{})
require.NoError(t, err)

status, err := followNode.Status()
require.NoError(t, err)
require.Equal(t, round, status.LastRound)
}

// Part I. redo TestSyncRound
// main differences are:
// * cfg.DisableNetworking = true
// * cfg.MaxAcctLookback = 100 (instead of 4)

addBlock(basics.Round(1))

dbRound := uint64(followNode.Ledger().LatestTrackerCommitted())
// Sync Round should be initialized to the ledger's dbRound + 1
require.Equal(t, dbRound+1, followNode.GetSyncRound())
// Set a new sync round
require.NoError(t, followNode.SetSyncRound(dbRound+11))
// Ensure it is persisted
require.Equal(t, dbRound+11, followNode.GetSyncRound())
// Unset the sync round and make sure get returns 0
followNode.UnsetSyncRound()
require.Equal(t, uint64(0), followNode.GetSyncRound())

// Part II. fast forward and then remake the node

newRound := basics.Round(2 * maxAcctLookback)
for i := basics.Round(2); i <= newRound; i++ {
addBlock(i)
}

followNode, _ = remakeableFollowNode(t, tempDir, maxAcctLookback)
status, err := followNode.Status()
require.NoError(t, err)
require.Equal(t, newRound, status.LastRound)

// syncRound should be at
// newRound - maxAcctLookback + 1 = maxAcctLookback + 1
syncRound := followNode.GetSyncRound()
require.Equal(t, uint64(maxAcctLookback+1), syncRound)
}
126 changes: 126 additions & 0 deletions test/e2e-go/features/followerNode/syncRestart_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Copyright (C) 2019-2023 Algorand, Inc.
// This file is part of go-algorand
//
// go-algorand is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// go-algorand is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with go-algorand. If not, see <https://www.gnu.org/licenses/>.

package followerNode

import (
"path/filepath"
"testing"

"github.com/stretchr/testify/require"

"github.com/algorand/go-algorand/config"
"github.com/algorand/go-algorand/daemon/algod/api/client"
"github.com/algorand/go-algorand/test/framework/fixtures"
"github.com/algorand/go-algorand/test/partitiontest"
)

// Overview of this test:
// Start a two-node network--one in follower mode (follower has 0%, secondary has 100%)
// with the nodes having a max account lookback of 2.
// Advance the primary node to particular rounds, set the follower's sync round
// and then advance the follower node as much as possible.
// Restart the network and verify that the sync round hasn't advanced.
//
// NOTE: with a max account lookback of MAL, and the follower's sync round at SR:
// the follower cannot advance past round SR - 1 + MAL
func TestSyncRestart(t *testing.T) {
partitiontest.PartitionTest(t)
defer fixtures.ShutdownSynchronizedTest(t)

if testing.Short() {
t.Skip()
}
t.Parallel()
a := require.New(fixtures.SynchronizedTest(t))

var fixture fixtures.RestClientFixture
fixture.Setup(t, filepath.Join("nettemplates", "TwoNodesFollower100SecondMaxAccountLookback2.json"))

defer fixture.Shutdown()

// sanity check that the follower has the expected max account lookback of 2:
followerCtrl, err := fixture.GetNodeController("Follower")
a.NoError(err)
cfg, err := config.LoadConfigFromDisk(followerCtrl.GetDataDir())
a.NoError(err)
a.Equal(uint64(2), cfg.MaxAcctLookback)

waitTill := func(node string, round uint64) {
controller, err := fixture.GetNodeController(node)
a.NoError(err)
err = fixture.ClientWaitForRoundWithTimeout(fixture.GetAlgodClientForController(controller), round)
a.NoError(err)
}

getAlgod := func(node string) client.RestClient {
controller, err := fixture.GetNodeController(node)
a.NoError(err)
algod := fixture.GetAlgodClientForController(controller)
return algod
}

getRound := func(node string) uint64 {
algod := getAlgod(node)
status, err := algod.Status()
a.NoError(err)
return status.LastRound
}

getSyncRound := func() uint64 {
followClient := getAlgod("Follower")
rResp, err := followClient.GetSyncRound()
a.NoError(err)
return rResp.Round
}

a.Equal(uint64(1), getSyncRound())

waitTill("Primary", 3)
// with a max account lookback of 2, and the sync round at 1,
// the follower cannot advance past round 2 = 1 - 1 + 2
waitTill("Follower", 2)
a.LessOrEqual(uint64(3), getRound("Primary"))
a.Equal(uint64(2), getRound("Follower"))
a.Equal(uint64(1), getSyncRound())

/** restart the network **/
fixture.ShutdownImpl(true)
fixture.Start()

a.LessOrEqual(uint64(3), getRound("Primary"))
a.Equal(uint64(1), getSyncRound())
a.Equal(uint64(2), getRound("Follower"))

waitTill("Primary", 6)
followerClient := getAlgod("Follower")
err = followerClient.SetSyncRound(uint64(3))
a.NoError(err)
a.Equal(uint64(3), getSyncRound())
// with a max account lookback of 2, and the sync round at 3,
// the follower cannot advance past round 4 = 3 - 1 + 2
waitTill("Follower", 4)
a.LessOrEqual(uint64(6), getRound("Primary"))
a.Equal(uint64(4), getRound("Follower"))
a.Equal(uint64(3), getSyncRound())

fixture.ShutdownImpl(true)
fixture.Start()

a.LessOrEqual(uint64(6), getRound("Primary"))
a.Equal(uint64(4), getRound("Follower"))
a.Equal(uint64(3), getSyncRound())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"Genesis": {
"LastPartKeyRound": 3000,
"NetworkName": "tbd",
"Wallets": [
{
"Name": "Wallet1",
"Stake": 100,
"Online": true
}
]
},
"Nodes": [
{
"Name": "Follower",
"Wallets": [],
"ConfigJSONOverride": "{\"EnableFollowMode\":true, \"MaxAcctLookback\":2}"
},
{
"Name": "Primary",
"IsRelay": true,
"Wallets": [
{ "Name": "Wallet1",
"ParticipationOnly": false }
]
}
]
}

0 comments on commit 5f2d5ed

Please sign in to comment.