github.com/MetalBlockchain/metalgo@v1.11.9/network/peer/ip_signer.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package peer
     5  
     6  import (
     7  	"crypto"
     8  	"net/netip"
     9  	"sync"
    10  
    11  	"github.com/MetalBlockchain/metalgo/utils"
    12  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    13  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    14  )
    15  
    16  // IPSigner will return a signedIP for the current value of our dynamic IP.
    17  type IPSigner struct {
    18  	ip        *utils.Atomic[netip.AddrPort]
    19  	clock     mockable.Clock
    20  	tlsSigner crypto.Signer
    21  	blsSigner *bls.SecretKey
    22  
    23  	// Must be held while accessing [signedIP]
    24  	signedIPLock sync.RWMutex
    25  	// Note that the values in [*signedIP] are constants and can be inspected
    26  	// without holding [signedIPLock].
    27  	signedIP *SignedIP
    28  }
    29  
    30  func NewIPSigner(
    31  	ip *utils.Atomic[netip.AddrPort],
    32  	tlsSigner crypto.Signer,
    33  	blsSigner *bls.SecretKey,
    34  ) *IPSigner {
    35  	return &IPSigner{
    36  		ip:        ip,
    37  		tlsSigner: tlsSigner,
    38  		blsSigner: blsSigner,
    39  	}
    40  }
    41  
    42  // GetSignedIP returns the signedIP of the current value of the provided
    43  // dynamicIP. If the dynamicIP hasn't changed since the prior call to
    44  // GetSignedIP, then the same [SignedIP] will be returned.
    45  //
    46  // It's safe for multiple goroutines to concurrently call GetSignedIP.
    47  func (s *IPSigner) GetSignedIP() (*SignedIP, error) {
    48  	// Optimistically, the IP should already be signed. By grabbing a read lock
    49  	// here we enable full concurrency of new connections.
    50  	s.signedIPLock.RLock()
    51  	signedIP := s.signedIP
    52  	s.signedIPLock.RUnlock()
    53  	ip := s.ip.Get()
    54  	if signedIP != nil && signedIP.AddrPort == ip {
    55  		return signedIP, nil
    56  	}
    57  
    58  	// If our current IP hasn't been signed yet - then we should sign it.
    59  	s.signedIPLock.Lock()
    60  	defer s.signedIPLock.Unlock()
    61  
    62  	// It's possible that multiple threads read [n.signedIP] as incorrect at the
    63  	// same time, we should verify that we are the first thread to attempt to
    64  	// update it.
    65  	signedIP = s.signedIP
    66  	if signedIP != nil && signedIP.AddrPort == ip {
    67  		return signedIP, nil
    68  	}
    69  
    70  	// We should now sign our new IP at the current timestamp.
    71  	unsignedIP := UnsignedIP{
    72  		AddrPort:  ip,
    73  		Timestamp: s.clock.Unix(),
    74  	}
    75  	signedIP, err := unsignedIP.Sign(s.tlsSigner, s.blsSigner)
    76  	if err != nil {
    77  		return nil, err
    78  	}
    79  
    80  	s.signedIP = signedIP
    81  	return s.signedIP, nil
    82  }