canopy-network/canopy · error
invalid dial peer %s: %s
Error message
invalid dial peer %s: %s
What it means
When dialing statically configured peers, the node parses each configured peer string into a lib.PeerAddress via FromString. This error wraps any parse failure, identifying both the offending address string and the underlying reason (bad format, network/chain mismatch, etc.).
Source
Thrown at p2p/p2p.go:195
if err = p.AddPeer(c, &lib.PeerInfo{Address: &lib.PeerAddress{NetAddress: netAddress}}, false, false); err != nil {
p.log.Error(err.Error())
_ = c.Close()
return
}
}(c)
}
}
// DialForOutboundPeers() uses the config and peer book to try to max out the outbound peer connections
func (p *P2P) DialForOutboundPeers() {
// create a tracking variable to ensure not 'over dialing'
var dialing atomic.Int32
getPeerFromString := func(address string) (*lib.PeerAddress, error) {
// start a peer address structure using the basic configurations
peer := &lib.PeerAddress{PeerMeta: &lib.PeerMeta{NetworkId: p.meta.NetworkId, ChainId: p.meta.ChainId}}
// try to populate the peer address using the peer string from the given string
if err := peer.FromString(address); err != nil {
return nil, fmt.Errorf("invalid dial peer %s: %s", address, err.Error())
}
// exit
return peer, nil
}
// Try to connect to the DialPeers in the config
for _, peerString := range p.config.DialPeers {
peerAddress, err := getPeerFromString(peerString)
if err != nil {
// log the invalid format
p.log.Error(err.Error())
// continue with the next
continue
}
// dial in a non-blocking fashion
go func() {
// increment dialing
dialing.Add(1)
// dial the peer with exponential backoffView on GitHub (pinned to ee8197d91d)
Solutions
- Check the wrapped inner error to see which part of the address failed (format vs network/chain ID)
- Correct the peer address string in the DialPeers config to match the expected format and the node's NetworkId/ChainId
- Obtain a fresh peer address string from the target peer's published address or another node on the same network
- Remove stale entries from DialPeers if the peer is no longer available
Example fix
// before (config)
"dial_peers": ["/ip4/10.0.0.1/tcp{PORT}/p2p/BADID"]
// after
"dial_peers": ["/ip4/10.0.0.1/tcp5001/p2p/12D3KooWCorrectBase58ID"] Defensive patterns
Strategy: validation
Validate before calling
if peerString == "" { return errors.New("empty dial peer") }
probe := &lib.PeerAddress{PeerMeta: &lib.PeerMeta{NetworkId: expectedNet, ChainId: expectedChain}}
if err := probe.FromString(peerString); err != nil {
return fmt.Errorf("config dial_peers entry %q invalid: %v", peerString, err)
} Type guard
func isValidPeerAddress(s string, net, chain string) bool {
p := &lib.PeerAddress{PeerMeta: &lib.PeerMeta{NetworkId: net, ChainId: chain}}
return p.FromString(s) == nil
} Try / catch
peer, err := getPeerFromString(addr)
if err != nil {
log.Warnf("skipping bad dial peer: %v", err)
continue // or fail fast on startup, depending on policy
} Prevention
- Validate the entire DialPeers list at config-load time, before starting the node
- Copy peer strings only from nodes on the same network/chain ID
- Keep peer addresses in version-controlled config and test them in CI
- Re-fetch peer addresses after protocol upgrades
When it happens
Trigger: Config field DialPeers contains a string that PeerAddress.FromString cannot parse — malformed multiaddr/peer string, wrong network ID or chain ID in the address, or empty/garbage entry in the config list.
Common situations: Typo in a dial peer address in config.json, copying a peer address from a different network (mainnet address on testnet config), stale peer strings after a protocol upgrade changed the address format.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
Related errors
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/39131ec99526707f.
Report an issue: GitHub.