Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use separate file for address book store #33

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Conversation

gartnera
Copy link
Member

@gartnera gartnera commented Oct 23, 2024

Use separate files for the seed and saved state of the address book to allow for managing the address_book.seed under configuration control.

Both files will be loaded by RetrieveP2PAddresses() but SaveAddressBook() will only write to address_book

Summary by CodeRabbit

  • New Features

    • Enhanced address book management with improved state retrieval functionalities.
    • Introduced a new method for loading bootstrap addresses, ensuring better error handling and data integrity.
    • Updated the peer discovery process to utilize bootstrap addresses.
  • Bug Fixes

    • Improved error reporting for invalid addresses during retrieval.
  • Tests

    • Updated test cases to use a dynamic approach for file path construction related to the address book.
    • Added new methods for retrieving bootstrap addresses in mock state managers.

Copy link

coderabbitai bot commented Oct 23, 2024

📝 Walkthrough

Walkthrough

The changes in this pull request focus on enhancing the functionality of the storage/localstate_mgr.go file, particularly in address book management and state retrieval. A new constant for the address book file name is introduced, and the SaveAddressBook method is updated to improve file handling. The RetrieveP2PAddresses method is restructured to load addresses from both seed and state address books through a new helper function, loadAddressBook, which improves error handling and reduces redundancy.

Changes

File Change Summary
storage/localstate_mgr.go - Added constant addressBookName for file naming.
- Updated SaveAddressBook to use addressBookName and os.WriteFile.
- Restructured RetrieveP2PAddresses to load from both seed and state address books.
- Introduced loadAddressBook function for loading addresses with improved error handling.
- Minor adjustments to comments and formatting for clarity.
storage/localstate_mgr_test.go - Modified filePathName in TestSaveAddressBook to use addressBookName instead of hardcoded string.
keysign/ecdsa/keysign_old_test.go - Added RetrieveBootstrapAddresses method to MockLocalStateOldManager.
keysign/ecdsa/keysign_test.go - Added RetrieveBootstrapAddresses method to MockLocalStateManager.
keysign/eddsa/keysign_test.go - Added RetrieveBootstrapAddresses method to MockLocalStateManager.
storage/mock_localstatemanager.go - Added RetrieveBootstrapAddresses method to MockLocalStateManager.
tss/tss.go - Updated NewTss to use RetrieveBootstrapAddresses instead of RetrieveP2PAddresses.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LocalStateManager
    participant AddressBook

    User->>LocalStateManager: SaveAddressBook()
    LocalStateManager->>AddressBook: os.WriteFile(addressBookName)
    AddressBook-->>LocalStateManager: Confirmation

    User->>LocalStateManager: RetrieveP2PAddresses()
    LocalStateManager->>AddressBook: loadAddressBook(seedPath)
    AddressBook-->>LocalStateManager: List of addresses
    LocalStateManager->>AddressBook: loadAddressBook(statePath)
    AddressBook-->>LocalStateManager: List of addresses
    LocalStateManager-->>User: Combined list of addresses
Loading

Possibly related PRs

  • fix: replace DHT with private peer discovery #34: The changes in the main PR regarding the management of address books and local state retrieval are related to the new peer discovery mechanism introduced in this PR, which also focuses on address management in a peer-to-peer context.
  • fix infinite discovery address leak #37: The modifications in the main PR that enhance address retrieval and management are relevant to the fixes made in this PR, which addresses issues with the discovery mechanism and ensures that the number of addresses associated with peers is properly managed.

Suggested reviewers

  • lumtis
  • fbac
  • morde08

Warning

There were issues while running some tools. Please review the errors and either fix the tool’s configuration or disable the tool if it’s a critical failure.

🔧 golangci-lint (1.61.0)

level=warning msg="[config_reader] The configuration option linters.errcheck.ignore is deprecated, please use linters.errcheck.exclude-functions."
level=warning msg="The linter 'deadcode' is deprecated (since v1.49.0) due to: The owner seems to have abandoned the linter. Replaced by unused."
level=warning msg="The linter 'golint' is deprecated (since v1.41.0) due to: The repository of the linter has been archived by the owner. Replaced by revive."
level=error msg="[linters_context] deadcode: This linter is fully inactivated: it will not produce any reports."
level=error msg="[linters_context] golint: This linter is fully inactivated: it will not produce any reports."


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gartnera gartnera changed the title Do not clobber address_book.seed file Use separate file for address book store Oct 23, 2024
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
storage/localstate_mgr.go (1)

Line range hint 177-195: Consider implementing atomic write operation for data safety.

While the implementation is generally sound, consider these improvements for production reliability:

  1. Add validation for empty address map
  2. Implement atomic write using temporary file and rename operation to prevent data corruption on system crashes

Here's a suggested implementation:

 func (fsm *FileStateMgr) SaveAddressBook(address map[peer.ID][]maddr.Multiaddr) error {
     if len(fsm.folder) < 1 {
         return errors.New("base file path is invalid")
     }
+    if len(address) == 0 {
+        return errors.New("empty address map")
+    }
     filePathName := filepath.Join(fsm.folder, addressBookName)
+    tempFile := filePathName + ".tmp"
     var buf bytes.Buffer
     
     for peer, addrs := range address {
         for _, addr := range addrs {
             if strings.Contains(addr.String(), "127.0.0.1") {
                 continue
             }
             record := addr.String() + "/p2p/" + peer.String() + "\n"
             _, err := buf.WriteString(record)
             if err != nil {
                 return errors.New("fail to write the record to buffer")
             }
         }
     }
     fsm.writeLock.Lock()
     defer fsm.writeLock.Unlock()
-    return os.WriteFile(filePathName, buf.Bytes(), 0o600)
+    if err := os.WriteFile(tempFile, buf.Bytes(), 0o600); err != nil {
+        return fmt.Errorf("failed to write temporary file: %w", err)
+    }
+    return os.Rename(tempFile, filePathName)
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between f8b548c and d2a913f.

📒 Files selected for processing (1)
  • storage/localstate_mgr.go (4 hunks)
🧰 Additional context used
🔇 Additional comments (1)
storage/localstate_mgr.go (1)

27-27: LGTM: Well-defined constant for address book state file.

The constant improves maintainability by centralizing the file name definition and clearly distinguishes it from the seed file.

Comment on lines 198 to 235
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
filePathName := filepath.Join(fsm.folder, "address_book.seed")
filePathName = filepath.Clean(filePathName)

_, err := os.Stat(filePathName)
if err != nil {
return nil, err
}
fsm.writeLock.RLock()
input, err := ioutil.ReadFile(filePathName)
defer fsm.writeLock.RUnlock()

seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedPath = filepath.Clean(seedPath)
seedAddresses, err := loadAddressBook(seedPath)
if err != nil {
fsm.writeLock.RUnlock()
return nil, err
}
fsm.writeLock.RUnlock()
data := strings.Split(string(input), "\n")
var peerAddresses []maddr.Multiaddr
for _, el := range data {
// we skip the empty entry
if len(el) == 0 {
continue
}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book %w", err)

savePath := filepath.Join(fsm.folder, addressBookName)
savePath = filepath.Clean(savePath)
savedAddresses, err := loadAddressBook(savePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return seedAddresses, nil
}
peerAddresses = append(peerAddresses, addr)
return seedAddresses, err
}
return peerAddresses, nil

return append(seedAddresses, savedAddresses...), nil
Copy link

@coderabbitai coderabbitai bot Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider deduplicating addresses for efficiency.

The implementation correctly loads addresses from both sources, but consider deduplicating the combined addresses to prevent potential issues with duplicate connections.

Here's a suggested implementation for deduplication:

 func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
     // ... existing code ...
-    return append(seedAddresses, savedAddresses...), nil
+    // Deduplicate addresses
+    seen := make(map[string]bool)
+    unique := make([]maddr.Multiaddr, 0, len(seedAddresses)+len(savedAddresses))
+    
+    for _, addr := range append(seedAddresses, savedAddresses...) {
+        addrStr := addr.String()
+        if !seen[addrStr] {
+            seen[addrStr] = true
+            unique = append(unique, addr)
+        }
+    }
+    return unique, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
filePathName := filepath.Join(fsm.folder, "address_book.seed")
filePathName = filepath.Clean(filePathName)
_, err := os.Stat(filePathName)
if err != nil {
return nil, err
}
fsm.writeLock.RLock()
input, err := ioutil.ReadFile(filePathName)
defer fsm.writeLock.RUnlock()
seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedPath = filepath.Clean(seedPath)
seedAddresses, err := loadAddressBook(seedPath)
if err != nil {
fsm.writeLock.RUnlock()
return nil, err
}
fsm.writeLock.RUnlock()
data := strings.Split(string(input), "\n")
var peerAddresses []maddr.Multiaddr
for _, el := range data {
// we skip the empty entry
if len(el) == 0 {
continue
}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book %w", err)
savePath := filepath.Join(fsm.folder, addressBookName)
savePath = filepath.Clean(savePath)
savedAddresses, err := loadAddressBook(savePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return seedAddresses, nil
}
peerAddresses = append(peerAddresses, addr)
return seedAddresses, err
}
return peerAddresses, nil
return append(seedAddresses, savedAddresses...), nil
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
fsm.writeLock.RLock()
defer fsm.writeLock.RUnlock()
seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedPath = filepath.Clean(seedPath)
seedAddresses, err := loadAddressBook(seedPath)
if err != nil {
return nil, err
}
savePath := filepath.Join(fsm.folder, addressBookName)
savePath = filepath.Clean(savePath)
savedAddresses, err := loadAddressBook(savePath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return seedAddresses, nil
}
return seedAddresses, err
}
// Deduplicate addresses
seen := make(map[string]bool)
unique := make([]maddr.Multiaddr, 0, len(seedAddresses)+len(savedAddresses))
for _, addr := range append(seedAddresses, savedAddresses...) {
addrStr := addr.String()
if !seen[addrStr] {
seen[addrStr] = true
unique = append(unique, addr)
}
}
return unique, nil
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really matter imo. libp2p should deduplicate it.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gartnera, understood. Since libp2p handles deduplication, manual deduplication may not be necessary.

(_/_)
( ••)
( -
-)


✏️ Learnings added
Learnt from: gartnera
PR: zeta-chain/go-tss#33
File: storage/localstate_mgr.go:0-0
Timestamp: 2024-10-23T23:04:45.574Z
Learning: libp2p deduplicates addresses, so manual deduplication of addresses in code is unnecessary.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (2)
storage/localstate_mgr_test.go (1)

Line range hint 97-104: Consider expanding test coverage for the new address book functionality.

The current test only verifies saving to the new address book file. Given the architectural change to support both seed and state files, consider adding test cases for:

  1. Loading addresses from both seed and state files
  2. Handling missing or corrupted seed files
  3. Verifying the merged results from both sources

Here's a suggested test structure:

func (s *FileStateMgrTestSuite) TestRetrieveP2PAddresses(c *C) {
    // Setup test data
    seedAddresses := make(map[peer.ID][]maddr.Multiaddr)
    stateAddresses := make(map[peer.ID][]maddr.Multiaddr)
    
    // Test cases:
    // 1. Both files exist
    // 2. Only seed file exists
    // 3. Only state file exists
    // 4. Neither file exists
    // 5. Corrupted seed file
    // 6. Duplicate addresses in both files
}
storage/localstate_mgr.go (1)

Line range hint 177-195: Add input validation for robustness.

The method should validate inputs and address formats before saving.

Consider adding these validations:

 func (fsm *FileStateMgr) SaveAddressBook(address map[peer.ID][]maddr.Multiaddr) error {
+    if address == nil {
+        return errors.New("address map cannot be nil")
+    }
     if len(fsm.folder) < 1 {
         return errors.New("base file path is invalid")
     }
     filePathName := filepath.Join(fsm.folder, addressBookName)
     var buf bytes.Buffer
 
     for peer, addrs := range address {
+        if len(addrs) == 0 {
+            continue
+        }
         for _, addr := range addrs {
+            if addr == nil {
+                continue
+            }
             // we do not save the loopback addr
             if strings.Contains(addr.String(), "127.0.0.1") {
                 continue
             }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between d2a913f and 62f4d14.

📒 Files selected for processing (2)
  • storage/localstate_mgr.go (4 hunks)
  • storage/localstate_mgr_test.go (1 hunks)
🧰 Additional context used
🔇 Additional comments (2)
storage/localstate_mgr_test.go (1)

98-98: LGTM! The path change aligns with the new architecture.

The update correctly uses the new addressBookName constant, maintaining consistency with the implementation.

storage/localstate_mgr.go (1)

27-27: LGTM: Good constant extraction.

The constant extraction improves maintainability by centralizing the address book filename.

storage/localstate_mgr.go Outdated Show resolved Hide resolved
Comment on lines 288 to 322
func loadAddressBook(path string) ([]maddr.Multiaddr, error) {
_, err := os.Stat(path)
if err != nil {
return nil, err
}
input, err := os.ReadFile(path)
if err != nil {
return nil, err
}
data := strings.Split(string(input), "\n")
var peerAddresses []maddr.Multiaddr
for _, el := range data {
// we skip the empty entry
if len(el) == 0 {
continue
}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book: %w", err)
}
peerAddresses = append(peerAddresses, addr)
}
return peerAddresses, nil
}
Copy link

@coderabbitai coderabbitai bot Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enhance error handling and add file size limit.

The function should provide more context in errors and protect against extremely large files.

 func loadAddressBook(path string) ([]maddr.Multiaddr, error) {
     _, err := os.Stat(path)
     if err != nil {
-        return nil, err
+        return nil, fmt.Errorf("failed to stat address book %s: %w", path, err)
+    }
+    
+    // Protect against extremely large files
+    fileInfo, err := os.Stat(path)
+    if err != nil {
+        return nil, fmt.Errorf("failed to stat address book %s: %w", path, err)
+    }
+    if fileInfo.Size() > 10*1024*1024 { // 10MB limit
+        return nil, fmt.Errorf("address book %s exceeds size limit of 10MB", path)
     }
     input, err := os.ReadFile(path)
     if err != nil {
-        return nil, err
+        return nil, fmt.Errorf("failed to read address book %s: %w", path, err)
     }

Committable suggestion was skipped due to low confidence.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really matter imo

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gartnera, understood.

╭(◕﹏◕)╮


✏️ Learnings added
Learnt from: gartnera
PR: zeta-chain/go-tss#33
File: storage/localstate_mgr.go:0-0
Timestamp: 2024-10-23T23:04:27.635Z
Learning: When reviewing the `loadAddressBook` function, it's acceptable not to enforce additional error handling or file size limits as per the team's preference.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (2)
storage/localstate_mgr.go (2)

Line range hint 177-195: Enhance error handling with context.

While the implementation is correct, the error message could be more descriptive to aid in debugging.

-               return errors.New("fail to write the record to buffer")
+               return fmt.Errorf("failed to write address record to buffer: %w", err)

286-310: Enhance error messages with file path context.

While the error handling is present, the error messages could be more descriptive by including the file path.

 	_, err := os.Stat(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to stat address book %s: %w", path, err)
 	}
 	input, err := os.ReadFile(path)
 	if err != nil {
-		return nil, err
+		return nil, fmt.Errorf("failed to read address book %s: %w", path, err)
 	}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 62f4d14 and 07555ce.

📒 Files selected for processing (2)
  • storage/localstate_mgr.go (4 hunks)
  • storage/localstate_mgr_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • storage/localstate_mgr_test.go
🧰 Additional context used
🔇 Additional comments (1)
storage/localstate_mgr.go (1)

27-27: LGTM: Good practice to define file name as a constant.

The constant name is descriptive and follows Go naming conventions.

Comment on lines 216 to 232
if err != nil && !errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) {
return seedAddresses, nil
}
peerAddresses = append(peerAddresses, addr)
return seedAddresses, err
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix redundant and incorrect error handling logic.

The nested if condition is redundant as the outer condition already checks for os.ErrNotExist.

 	if err != nil && !errors.Is(err, os.ErrNotExist) {
-		if errors.Is(err, os.ErrNotExist) {
-			return seedAddresses, nil
-		}
 		return seedAddresses, err
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if err != nil && !errors.Is(err, os.ErrNotExist) {
if errors.Is(err, os.ErrNotExist) {
return seedAddresses, nil
}
peerAddresses = append(peerAddresses, addr)
return seedAddresses, err
if err != nil && !errors.Is(err, os.ErrNotExist) {
return seedAddresses, err
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Outside diff range and nitpick comments (1)
storage/localstate_mgr.go (1)

Line range hint 177-195: Clean file path before writing.

The file path should be cleaned to prevent path traversal vulnerabilities.

Apply this diff:

-    filePathName := filepath.Join(fsm.folder, addressBookName)
+    filePathName := filepath.Clean(filepath.Join(fsm.folder, addressBookName))
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 07555ce and c2b8cbf.

📒 Files selected for processing (2)
  • storage/localstate_mgr.go (4 hunks)
  • storage/localstate_mgr_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • storage/localstate_mgr_test.go
🔇 Additional comments (1)
storage/localstate_mgr.go (1)

27-27: LGTM: Good constant extraction.

The constant improves maintainability by centralizing the address book filename.

storage/localstate_mgr.go Show resolved Hide resolved
Comment on lines 198 to 235
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
filePathName := filepath.Join(fsm.folder, "address_book.seed")
filePathName = filepath.Clean(filePathName)

_, err := os.Stat(filePathName)
if err != nil {
return nil, err
}
fsm.writeLock.RLock()
input, err := ioutil.ReadFile(filePathName)
if err != nil {
fsm.writeLock.RUnlock()
defer fsm.writeLock.RUnlock()

seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedAddresses, err := loadAddressBook(seedPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
fsm.writeLock.RUnlock()
data := strings.Split(string(input), "\n")
var peerAddresses []maddr.Multiaddr
for _, el := range data {
// we skip the empty entry
if len(el) == 0 {
continue
}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book %w", err)
}
peerAddresses = append(peerAddresses, addr)

savePath := filepath.Join(fsm.folder, addressBookName)
savedAddresses, err := loadAddressBook(savePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return seedAddresses, err
}
return peerAddresses, nil

return append(seedAddresses, savedAddresses...), nil
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Improve error handling specificity.

The error handling could be more descriptive to aid in debugging.

Apply this diff:

     seedAddresses, err := loadAddressBook(seedPath)
     if err != nil && !errors.Is(err, os.ErrNotExist) {
-        return nil, err
+        return nil, fmt.Errorf("failed to load seed address book: %w", err)
     }

     savePath := filepath.Join(fsm.folder, addressBookName)
     savedAddresses, err := loadAddressBook(savePath)
     if err != nil && !errors.Is(err, os.ErrNotExist) {
-        return seedAddresses, err
+        return seedAddresses, fmt.Errorf("failed to load state address book: %w", err)
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
filePathName := filepath.Join(fsm.folder, "address_book.seed")
filePathName = filepath.Clean(filePathName)
_, err := os.Stat(filePathName)
if err != nil {
return nil, err
}
fsm.writeLock.RLock()
input, err := ioutil.ReadFile(filePathName)
if err != nil {
fsm.writeLock.RUnlock()
defer fsm.writeLock.RUnlock()
seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedAddresses, err := loadAddressBook(seedPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
}
fsm.writeLock.RUnlock()
data := strings.Split(string(input), "\n")
var peerAddresses []maddr.Multiaddr
for _, el := range data {
// we skip the empty entry
if len(el) == 0 {
continue
}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book %w", err)
}
peerAddresses = append(peerAddresses, addr)
savePath := filepath.Join(fsm.folder, addressBookName)
savedAddresses, err := loadAddressBook(savePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return seedAddresses, err
}
return peerAddresses, nil
return append(seedAddresses, savedAddresses...), nil
// RetrieveP2PAddresses loads addresses from both the seed address book
// and state address book
func (fsm *FileStateMgr) RetrieveP2PAddresses() ([]maddr.Multiaddr, error) {
if len(fsm.folder) < 1 {
return nil, errors.New("base file path is invalid")
}
fsm.writeLock.RLock()
defer fsm.writeLock.RUnlock()
seedPath := filepath.Join(fsm.folder, "address_book.seed")
seedAddresses, err := loadAddressBook(seedPath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, fmt.Errorf("failed to load seed address book: %w", err)
}
savePath := filepath.Join(fsm.folder, addressBookName)
savedAddresses, err := loadAddressBook(savePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return seedAddresses, fmt.Errorf("failed to load state address book: %w", err)
}
return append(seedAddresses, savedAddresses...), nil

@CryptoFewka
Copy link

Part of the goal of replacing address_book.seed was to reset the peer list to a known good configuration.

Would merging this PR undo that, and allow the entries that caused the crash loop to remain or is this PR intended to be layered on top of another mitigation that ensures unexpected peers aren't allowed?

@gartnera
Copy link
Member Author

Part of the goal of replacing address_book.seed was to reset the peer list to a known good configuration.

Would merging this PR undo that, and allow the entries that caused the crash loop to remain or is this PR intended to be layered on top of another mitigation that ensures unexpected peers aren't allowed?

#33 should prevent us from loading unexpected peers. Nothing would prevent you from also managing/clearing address_book if there were issues in the future.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Outside diff range and nitpick comments (6)
storage/mock_localstatemanager.go (1)

24-26: Consider enhancing mock for better testability.

The current implementation always returns nil values. Consider making the mock more configurable to support testing different scenarios, such as error conditions or specific address lists.

Here's a suggested enhancement:

type MockLocalStateManager struct {
+    // Add fields to configure mock behavior
+    BootstrapAddrs []maddr.Multiaddr
+    BootstrapError error
}

func (s *MockLocalStateManager) RetrieveBootstrapAddresses() ([]maddr.Multiaddr, error) {
-    return nil, nil
+    return s.BootstrapAddrs, s.BootstrapError
}

This would allow tests to:

  1. Configure specific addresses to be returned
  2. Simulate error conditions
  3. Verify behavior with empty vs. populated address lists
tss/tss.go (2)

Line range hint 83-89: Verify error handling for bootstrap address retrieval.

The error handling appears to silently fall back to command-line bootstrap peers when RetrieveBootstrapAddresses() fails. Consider logging the error for better observability and debugging.

 savedPeers, err := stateManager.RetrieveBootstrapAddresses()
 if err != nil {
+    t.logger.Debug().Err(err).Msg("failed to retrieve bootstrap addresses, falling back to command-line peers")
     bootstrapPeers = cmdBootstrapPeers
 } else {
     bootstrapPeers = savedPeers
     bootstrapPeers = append(bootstrapPeers, cmdBootstrapPeers...)
 }

Potential duplicate bootstrap peers should be deduplicated

The code in tss/tss.go appends command-line bootstrap peers to saved peers without deduplication, while the peer discovery system (p2p/discovery.go) maintains a knownPeers map to track unique peers. To maintain consistency and optimize peer discovery:

  • Add deduplication logic in tss/tss.go before passing bootstrap peers to the communication layer
  • Consider using a map-based approach similar to the peer discovery implementation to ensure uniqueness

Example solution:

seenPeers := make(map[string]struct{})
uniqueBootstrapPeers := make([]maddr.Multiaddr, 0)

// Helper to add unique peers
addUniquePeer := func(peer maddr.Multiaddr) {
    peerStr := peer.String()
    if _, exists := seenPeers[peerStr]; !exists {
        seenPeers[peerStr] = struct{}{}
        uniqueBootstrapPeers = append(uniqueBootstrapPeers, peer)
    }
}

// Add saved peers
for _, peer := range savedPeers {
    addUniquePeer(peer)
}

// Add command-line peers
for _, peer := range cmdBootstrapPeers {
    addUniquePeer(peer)
}

bootstrapPeers = uniqueBootstrapPeers
🔗 Analysis chain

Line range hint 83-89: Verify the impact of combining bootstrap sources.

The code appends command-line bootstrap peers to saved peers, which could lead to duplicate entries. Consider deduplicating the combined list to optimize peer discovery.

Let's verify if there's potential for duplicate entries in the bootstrap peers:


Let me gather more information about the peer handling logic:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any peer address deduplication logic in the codebase
rg -A 5 "bootstrap.*peer" 

Length of output: 4155


Script:

#!/bin/bash
# Search for peer handling and deduplication logic
ast-grep --pattern 'func $_(bootstrapPeers []peer.AddrInfo) $_'

# Check if there's any map-based peer tracking
rg -A 5 "map\[peer\.ID\]"

# Look for any peer list manipulation
rg -A 5 "append.*peer"

Length of output: 13524

storage/localstate_mgr.go (1)

201-216: Consider enhancing error messages for better debugging.

While the implementation is clean, consider wrapping the error for better context:

 func (fsm *FileStateMgr) RetrieveBootstrapAddresses() ([]maddr.Multiaddr, error) {
     if len(fsm.folder) < 1 {
-        return nil, errors.New("base file path is invalid")
+        return nil, fmt.Errorf("invalid base file path: %q", fsm.folder)
     }
     // ... rest of the function
     if err != nil && !errors.Is(err, os.ErrNotExist) {
-        return nil, err
+        return nil, fmt.Errorf("failed to load seed addresses: %w", err)
     }
keysign/ecdsa/keysign_old_test.go (1)

80-82: LGTM! Consider adding documentation.

The mock implementation correctly returns nil and os.ErrNotExist, consistent with the mock nature of the struct and matching the behavior of RetrieveP2PAddresses.

Consider adding a comment to document the mock behavior:

+// RetrieveBootstrapAddresses implements LocalStateManager interface with mock behavior
 func (s *MockLocalStateOldManager) RetrieveBootstrapAddresses() ([]maddr.Multiaddr, error) {
     return nil, os.ErrNotExist
 }
keysign/ecdsa/keysign_test.go (1)

92-94: LGTM! Consider adding a comment to document the mock behavior.

The implementation correctly returns nil and os.ErrNotExist as expected for a mock implementation. This aligns with the PR's objective of managing address book files separately.

Consider adding a comment to document the mock behavior:

+// RetrieveBootstrapAddresses implements LocalStateManager interface by returning os.ErrNotExist
 func (s *MockLocalStateManager) RetrieveBootstrapAddresses() ([]maddr.Multiaddr, error) {
     return nil, os.ErrNotExist
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c2b8cbf and 03068fc.

📒 Files selected for processing (7)
  • keysign/ecdsa/keysign_old_test.go (1 hunks)
  • keysign/ecdsa/keysign_test.go (1 hunks)
  • keysign/eddsa/keysign_test.go (1 hunks)
  • storage/localstate_mgr.go (5 hunks)
  • storage/localstate_mgr_test.go (1 hunks)
  • storage/mock_localstatemanager.go (1 hunks)
  • tss/tss.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • storage/localstate_mgr_test.go
🧰 Additional context used
📓 Learnings (1)
storage/localstate_mgr.go (1)
Learnt from: gartnera
PR: zeta-chain/go-tss#33
File: storage/localstate_mgr.go:283-307
Timestamp: 2024-11-12T13:20:12.657Z
Learning: In the `loadAddressBook` function in `storage/localstate_mgr.go`, the `path` parameter will never be empty, so input validation for an empty path is unnecessary.
🔇 Additional comments (7)
storage/mock_localstatemanager.go (1)

24-26: Implementation follows mock patterns correctly.

The new method maintains consistency with other mock methods in the struct and provides the expected interface implementation.

tss/tss.go (1)

Line range hint 91-108: LGTM: Robust whitelist filtering implementation.

The implementation correctly filters bootstrap peers against the whitelist, ensuring only authorized peers are included. The error handling for peer address parsing is appropriate.

keysign/eddsa/keysign_test.go (1)

88-90: LGTM: Mock implementation aligns with PR objectives

The mock implementation correctly returns os.ErrNotExist to simulate the behavior of a non-existent bootstrap addresses file, which is consistent with the PR's goal of separating address book files.

storage/localstate_mgr.go (4)

27-27: LGTM! Clear constant naming and backward-compatible interface update.

The new constant addressBookName and the interface addition of RetrieveBootstrapAddresses are well-structured and maintain backward compatibility.

Also applies to: 54-54


178-178: LGTM! Good modernization of file operations.

The changes improve maintainability by using the constant and modernize the code by switching to os.WriteFile.

Also applies to: 196-196


218-235: LGTM! Well-structured implementation with good code reuse.

The implementation efficiently reuses RetrieveBootstrapAddresses and correctly combines addresses from both sources. The approach is clean and maintainable.


298-322: LGTM! Robust helper function with good error handling.

The implementation properly handles empty entries and provides clear error messages for invalid addresses. The function is well-structured and focused.

@gartnera gartnera requested a review from a team December 13, 2024 15:32
Copy link

@kingpinXD kingpinXD left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just the changes in the PR look good , Have not been able to review all the implications yet .

}
addr, err := maddr.NewMultiaddr(el)
if err != nil {
return nil, fmt.Errorf("invalid address in address book: %w", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to check can we skip invalid ones, or this wants to also ensure we have error in that case?

Copy link
Member Author

@gartnera gartnera Dec 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If for whatever reason the address_book file is invalid it will just be ignored and only use the address_book.seed will be used. The address_book.seed must be valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants