diff --git a/cmd/goal/node.go b/cmd/goal/node.go index 50856b2212..11b95f7c61 100644 --- a/cmd/goal/node.go +++ b/cmd/goal/node.go @@ -61,6 +61,7 @@ var newNodeFullConfig bool var watchMillisecond uint64 var abortCatchup bool var fastCatchupForce bool +var initializeCatchup uint64 const catchpointURL = "https://algorand-catchpoints.s3.us-east-2.amazonaws.com/channel/%s/latest.catchpoint" @@ -116,6 +117,7 @@ func init() { catchupCmd.Flags().BoolVarP(&abortCatchup, "abort", "x", false, "Aborts the current catchup process") catchupCmd.Flags().BoolVar(&fastCatchupForce, "force", false, "Forces fast catchup with implicit catchpoint to start without a consent prompt") + catchupCmd.Flags().Uint64VarP(&initializeCatchup, "initialize", "i", 0, "Catchup only if the catchpoint would advance the node by at least the specified number of rounds") } @@ -161,22 +163,38 @@ var catchupCmd = &cobra.Command{ Example: "goal node catchup 6500000#1234567890ABCDEF01234567890ABCDEF0\tStart catching up to round 6500000 with the provided catchpoint\ngoal node catchup --abort\t\t\t\t\tAbort the current catchup", Args: catchpointCmdArgument, Run: func(cmd *cobra.Command, args []string) { + var catchpoint string + // assume first positional parameter is the catchpoint + if len(args) != 0 { + catchpoint = args[0] + } datadir.OnDataDirs(func(dataDir string) { - if !abortCatchup && len(args) == 0 { - client := ensureAlgodClient(dataDir) + client := ensureAlgodClient(dataDir) + + if abortCatchup { + err := client.AbortCatchup() + if err != nil { + reportErrorf(errorNodeStatus, err) + } + return + } + + // lookup missing catchpoint + if catchpoint == "" { vers, err := client.AlgodVersions() if err != nil { reportErrorf(errorNodeStatus, err) } genesis := strings.Split(vers.GenesisID, "-")[0] URL := fmt.Sprintf(catchpointURL, genesis) - label, err := getMissingCatchpointLabel(URL) + catchpoint, err = getMissingCatchpointLabel(URL) if err != nil { reportErrorf(errorCatchpointLabelMissing, errorUnableToLookupCatchpointLabel, err.Error()) } - args = append(args, label) + + // Prompt user to confirm using an implicit catchpoint. if !fastCatchupForce { - fmt.Printf(nodeConfirmImplicitCatchpoint, label) + fmt.Printf(nodeConfirmImplicitCatchpoint, catchpoint) reader := bufio.NewReader(os.Stdin) text, _ := reader.ReadString('\n') text = strings.Replace(text, "\n", "", -1) @@ -185,7 +203,14 @@ var catchupCmd = &cobra.Command{ } } } - catchup(dataDir, args) + + resp, err := client.Catchup(catchpoint, initializeCatchup) + if err != nil { + reportErrorf(errorNodeStatus, err) + } + if resp.CatchupMessage != catchpoint { + reportInfof("node response: %s", resp.CatchupMessage) + } }) }, } @@ -718,21 +743,6 @@ var createCmd = &cobra.Command{ }, } -func catchup(dataDir string, args []string) { - client := ensureAlgodClient(datadir.EnsureSingleDataDir()) - if abortCatchup { - err := client.AbortCatchup() - if err != nil { - reportErrorf(errorNodeStatus, err) - } - return - } - err := client.Catchup(args[0]) - if err != nil { - reportErrorf(errorNodeStatus, err) - } -} - // verifyPeerDialArg verifies that the peers provided in peerDial are valid peers. func verifyPeerDialArg() bool { if peerDial == "" { diff --git a/daemon/algod/api/client/restClient.go b/daemon/algod/api/client/restClient.go index 0448f650c4..dbdcf8a5ef 100644 --- a/daemon/algod/api/client/restClient.go +++ b/daemon/algod/api/client/restClient.go @@ -389,6 +389,10 @@ type accountInformationParams struct { Exclude string `url:"exclude"` } +type catchupParams struct { + Initialize uint64 `url:"initialize"` +} + // PendingTransactionsByAddr returns all the pending transactions for an addr. func (client RestClient) PendingTransactionsByAddr(addr string, max uint64) (response model.PendingTransactionsResponse, err error) { err = client.get(&response, fmt.Sprintf("/v2/accounts/%s/transactions/pending", addr), pendingTransactionsByAddrParams{max}) @@ -575,8 +579,8 @@ func (client RestClient) AbortCatchup(catchpointLabel string) (response model.Ca } // Catchup start catching up to the give catchpoint label -func (client RestClient) Catchup(catchpointLabel string) (response model.CatchpointStartResponse, err error) { - err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), nil, nil, "POST", false, true, false) +func (client RestClient) Catchup(catchpointLabel string, initializeRounds uint64) (response model.CatchpointStartResponse, err error) { + err = client.submitForm(&response, fmt.Sprintf("/v2/catchup/%s", catchpointLabel), catchupParams{Initialize: initializeRounds}, nil, "POST", false, true, false) return } diff --git a/libgoal/libgoal.go b/libgoal/libgoal.go index 2bc924bac9..5788d64e23 100644 --- a/libgoal/libgoal.go +++ b/libgoal/libgoal.go @@ -1131,16 +1131,12 @@ func (c *Client) AbortCatchup() error { } // Catchup start catching up to the give catchpoint label. -func (c *Client) Catchup(catchpointLabel string) error { +func (c *Client) Catchup(catchpointLabel string, initialize uint64) (model.CatchpointStartResponse, error) { algod, err := c.ensureAlgodClient() if err != nil { - return err + return model.CatchpointStartResponse{}, err } - _, err = algod.Catchup(catchpointLabel) - if err != nil { - return err - } - return nil + return algod.Catchup(catchpointLabel, initialize) } const defaultAppIdx = 1380011588 diff --git a/test/e2e-go/features/catchup/catchpointCatchup_test.go b/test/e2e-go/features/catchup/catchpointCatchup_test.go index ecbecbdb87..f9f62b3d06 100644 --- a/test/e2e-go/features/catchup/catchpointCatchup_test.go +++ b/test/e2e-go/features/catchup/catchpointCatchup_test.go @@ -316,7 +316,7 @@ func TestCatchpointCatchupFailure(t *testing.T) { err = primaryNode.StopAlgod() a.NoError(err) - _, err = usingNodeRestClient.Catchup(catchpointLabel) + _, err = usingNodeRestClient.Catchup(catchpointLabel, 0) a.ErrorContains(err, node.MakeStartCatchpointError(catchpointLabel, fmt.Errorf("")).Error()) } @@ -358,7 +358,7 @@ func TestBasicCatchpointCatchup(t *testing.T) { catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound) - _, err = usingNodeRestClient.Catchup(catchpointLabel) + _, err = usingNodeRestClient.Catchup(catchpointLabel, 0) a.NoError(err) err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound+1)) @@ -534,7 +534,7 @@ func TestNodeTxHandlerRestart(t *testing.T) { lastCatchpoint := waitForCatchpointGeneration(t, &fixture, relayClient, basics.Round(targetCatchpointRound)) // let the primary node catchup - err = client1.Catchup(lastCatchpoint) + _, err = client1.Catchup(lastCatchpoint, 0) a.NoError(err) status1, err := client1.Status() @@ -647,7 +647,7 @@ func TestReadyEndpoint(t *testing.T) { // Then when the primary node is at target round, it should satisfy ready 200 condition // let the primary node catchup - err = client1.Catchup(lastCatchpoint) + _, err = client1.Catchup(lastCatchpoint, 0) a.NoError(err) // The primary node is catching up with its previous catchpoint @@ -789,7 +789,7 @@ func TestNodeTxSyncRestart(t *testing.T) { _, err = fixture.StartNode(primaryNode.GetDataDir()) a.NoError(err) // let the primary node catchup - err = client1.Catchup(lastCatchpoint) + _, err = client1.Catchup(lastCatchpoint, 0) a.NoError(err) // the transaction should not be confirmed yet diff --git a/test/e2e-go/features/catchup/stateproofsCatchup_test.go b/test/e2e-go/features/catchup/stateproofsCatchup_test.go index 7f11ca37f5..4d3140c8d0 100644 --- a/test/e2e-go/features/catchup/stateproofsCatchup_test.go +++ b/test/e2e-go/features/catchup/stateproofsCatchup_test.go @@ -93,7 +93,7 @@ func TestStateProofInReplayCatchpoint(t *testing.T) { catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound) - _, err = usingNodeRestClient.Catchup(catchpointLabel) + _, err = usingNodeRestClient.Catchup(catchpointLabel, 0) a.NoError(err) // waiting for fastcatchup to start @@ -169,7 +169,7 @@ func TestStateProofAfterCatchpoint(t *testing.T) { catchpointLabel := waitForCatchpointGeneration(t, fixture, primaryNodeRestClient, targetCatchpointRound) - _, err = usingNodeRestClient.Catchup(catchpointLabel) + _, err = usingNodeRestClient.Catchup(catchpointLabel, 0) a.NoError(err) roundAfterSPGeneration := targetCatchpointRound.RoundUpToMultipleOf(basics.Round(consensusParams.StateProofInterval)) + @@ -258,7 +258,7 @@ func TestSendSigsAfterCatchpointCatchup(t *testing.T) { targetCatchpointRound := getFirstCatchpointRound(&consensusParams) catchpointLabel := waitForCatchpointGeneration(t, &fixture, primaryNodeRestClient, targetCatchpointRound) - _, err = usingNodeRestClient.Catchup(catchpointLabel) + _, err = usingNodeRestClient.Catchup(catchpointLabel, 0) a.NoError(err) err = fixture.ClientWaitForRoundWithTimeout(usingNodeRestClient, uint64(targetCatchpointRound)+1)