diff options
author | John Lee <64482439+algojohnlee@users.noreply.github.com> | 2023-04-11 11:17:20 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-11 11:17:20 -0400 |
commit | 747553df032ee8af2638eaa3c9af37efb458ddaa (patch) | |
tree | a33643031325ca5827c76662dab2b79749a5a8c2 | |
parent | 0192a92e79a7b1c3d34349d4705b36d2231589aa (diff) | |
parent | c691c46259dd8df36ea10e092db54ee7ac0a37eb (diff) |
Merge pull request #5275 from onetechnical/relbeta3.15.1v3.15.1-beta
-rw-r--r-- | buildnumber.dat | 2 | ||||
-rw-r--r-- | ledger/ledger.go | 5 | ||||
-rw-r--r-- | ledger/tracker.go | 8 | ||||
-rw-r--r-- | node/follower_node.go | 6 | ||||
-rw-r--r-- | node/follower_node_test.go | 95 | ||||
-rw-r--r-- | test/e2e-go/features/followerNode/syncRestart_test.go | 126 | ||||
-rw-r--r-- | test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json | 28 |
7 files changed, 261 insertions, 9 deletions
diff --git a/buildnumber.dat b/buildnumber.dat index 573541ac9..d00491fd7 100644 --- a/buildnumber.dat +++ b/buildnumber.dat @@ -1 +1 @@ -0 +1 diff --git a/ledger/ledger.go b/ledger/ledger.go index 50222d2f9..21547e30b 100644 --- a/ledger/ledger.go +++ b/ledger/ledger.go @@ -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 diff --git a/ledger/tracker.go b/ledger/tracker.go index 26f7f25b2..5be0acd29 100644 --- a/ledger/tracker.go +++ b/ledger/tracker.go @@ -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 +} diff --git a/node/follower_node.go b/node/follower_node.go index 34dc48c24..ac8cad28e 100644 --- a/node/follower_node.go +++ b/node/follower_node.go @@ -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 } diff --git a/node/follower_node_test.go b/node/follower_node_test.go index 4389ed3e0..33a004acd 100644 --- a/node/follower_node_test.go +++ b/node/follower_node_test.go @@ -48,6 +48,12 @@ func followNodeDefaultGenesis() bookkeeping.Genesis { MicroAlgos: basics.MicroAlgos{Raw: 1000000000}, }, }, + { + Address: sinkAddr.String(), + State: basics.AccountData{ + MicroAlgos: basics.MicroAlgos{Raw: 1000000}, + }, + }, }, } } @@ -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() @@ -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()) @@ -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) +} diff --git a/test/e2e-go/features/followerNode/syncRestart_test.go b/test/e2e-go/features/followerNode/syncRestart_test.go new file mode 100644 index 000000000..8137f9e24 --- /dev/null +++ b/test/e2e-go/features/followerNode/syncRestart_test.go @@ -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()) +} diff --git a/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json b/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json new file mode 100644 index 000000000..bae2201d1 --- /dev/null +++ b/test/testdata/nettemplates/TwoNodesFollower100SecondMaxAccountLookback2.json @@ -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 } + ] + } + ] +} |