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

Add ACP signaling #2476

Merged
merged 7 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 58 additions & 0 deletions api/info/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import (
"github.com/ava-labs/avalanchego/network"
"github.com/ava-labs/avalanchego/network/peer"
"github.com/ava-labs/avalanchego/snow/networking/benchlist"
"github.com/ava-labs/avalanchego/snow/validators"
"github.com/ava-labs/avalanchego/utils/constants"
"github.com/ava-labs/avalanchego/utils/ips"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/logging"
"github.com/ava-labs/avalanchego/utils/set"
"github.com/ava-labs/avalanchego/version"
"github.com/ava-labs/avalanchego/vms"
"github.com/ava-labs/avalanchego/vms/platformvm/signer"
Expand All @@ -32,6 +34,7 @@ var errNoChainProvided = errors.New("argument 'chain' not given")
type Info struct {
Parameters
log logging.Logger
validators validators.Manager
myIP ips.DynamicIPPort
networking network.Network
chainManager chains.Manager
Expand Down Expand Up @@ -59,6 +62,7 @@ type Parameters struct {
func NewService(
parameters Parameters,
log logging.Logger,
validators validators.Manager,
chainManager chains.Manager,
vmManager vms.Manager,
myIP ips.DynamicIPPort,
Expand All @@ -73,6 +77,7 @@ func NewService(
&Info{
Parameters: parameters,
log: log,
validators: validators,
chainManager: chainManager,
vmManager: vmManager,
myIP: myIP,
Expand Down Expand Up @@ -319,6 +324,59 @@ func (i *Info) Uptime(_ *http.Request, args *UptimeRequest, reply *UptimeRespons
return nil
}

type ACP struct {
SupportWeight json.Uint64 `json:"supportWeight"`
Supporters set.Set[ids.NodeID] `json:"supporters"`
ObjectWeight json.Uint64 `json:"objectWeight"`
Objectors set.Set[ids.NodeID] `json:"objectors"`
AbstainWeight json.Uint64 `json:"abstainWeight"`
StephenButtolph marked this conversation as resolved.
Show resolved Hide resolved
}

type ACPsReply struct {
ACPs map[uint32]*ACP `json:"acps"`
}

func (a *ACPsReply) getACP(acpNum uint32) *ACP {
acp, ok := a.ACPs[acpNum]
if !ok {
acp = &ACP{}
a.ACPs[acpNum] = acp
}
return acp
}

func (i *Info) Acps(_ *http.Request, _ *struct{}, reply *ACPsReply) error {
i.log.Debug("API called",
zap.String("service", "info"),
zap.String("method", "acps"),
)

reply.ACPs = make(map[uint32]*ACP, constants.CurrentACPs.Len())
peers := i.networking.PeerInfo(nil)
patrick-ogrady marked this conversation as resolved.
Show resolved Hide resolved
for _, peer := range peers {
for acpNum := range peer.SupportedACPs {
acp := reply.getACP(acpNum)
acp.Supporters.Add(peer.ID)
acp.SupportWeight += json.Uint64(i.validators.GetWeight(constants.PrimaryNetworkID, peer.ID))
}
for acpNum := range peer.ObjectedACPs {
acp := reply.getACP(acpNum)
acp.Objectors.Add(peer.ID)
acp.ObjectWeight += json.Uint64(i.validators.GetWeight(constants.PrimaryNetworkID, peer.ID))
}
}

totalWeight, err := i.validators.TotalWeight(constants.PrimaryNetworkID)
if err != nil {
return err
}
for acpNum := range constants.CurrentACPs {
acp := reply.getACP(acpNum)
acp.AbstainWeight = json.Uint64(totalWeight) - acp.SupportWeight - acp.ObjectWeight
}
return nil
}

type GetTxFeeResponse struct {
TxFee json.Uint64 `json:"txFee"`
CreateAssetTxFee json.Uint64 `json:"createAssetTxFee"`
Expand Down
23 changes: 23 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var (
ConsensusGossipOnAcceptPeerSizeKey: acceptedFrontierGossipDeprecationMsg,
}

errConflictingACPOpinion = errors.New("supporting and objecting to the same ACP")
errSybilProtectionDisabledStakerWeights = errors.New("sybil protection disabled weights must be positive")
errSybilProtectionDisabledOnPublicNetwork = errors.New("sybil protection disabled on public network")
errAuthPasswordTooWeak = errors.New("API auth password is not strong enough")
Expand Down Expand Up @@ -346,6 +347,25 @@ func getNetworkConfig(
allowPrivateIPs = v.GetBool(NetworkAllowPrivateIPsKey)
}

var supportedACPs set.Set[uint32]
for _, acp := range v.GetIntSlice(ACPSupportKey) {
if acp < 0 || acp > math.MaxInt32 {
return network.Config{}, fmt.Errorf("invalid ACP: %d", acp)
}
supportedACPs.Add(uint32(acp))
}

var objectedACPs set.Set[uint32]
for _, acp := range v.GetIntSlice(ACPObjectKey) {
if acp < 0 || acp > math.MaxInt32 {
return network.Config{}, fmt.Errorf("invalid ACP: %d", acp)
}
objectedACPs.Add(uint32(acp))
}
if supportedACPs.Overlaps(objectedACPs) {
return network.Config{}, errConflictingACPOpinion
}

config := network.Config{
ThrottlerConfig: network.ThrottlerConfig{
MaxInboundConnsPerSec: maxInboundConnsPerSec,
Expand Down Expand Up @@ -425,6 +445,9 @@ func getNetworkConfig(
UptimeMetricFreq: v.GetDuration(UptimeMetricFreqKey),
MaximumInboundMessageTimeout: v.GetDuration(NetworkMaximumInboundTimeoutKey),

SupportedACPs: supportedACPs,
ObjectedACPs: objectedACPs,

RequireValidatorToConnect: v.GetBool(NetworkRequireValidatorToConnectKey),
PeerReadBufferSize: int(v.GetUint(NetworkPeerReadBufferSizeKey)),
PeerWriteBufferSize: int(v.GetUint(NetworkPeerWriteBufferSizeKey)),
Expand Down
4 changes: 4 additions & 0 deletions config/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func addNodeFlags(fs *pflag.FlagSet) {
// Network ID
fs.String(NetworkNameKey, constants.MainnetName, "Network ID this node will connect to")

// ACP flagging
fs.IntSlice(ACPSupportKey, nil, "ACPs to support adoption")
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we use UintSlice here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Viper doesn't seem to have support for UintSlice - so I felt it was more straight-forward to use IntSlice in both places.

fs.IntSlice(ACPObjectKey, nil, "ACPs to object adoption")

// AVAX fees
fs.Uint64(TxFeeKey, genesis.LocalParams.TxFee, "Transaction fee, in nAVAX")
fs.Uint64(CreateAssetTxFeeKey, genesis.LocalParams.CreateAssetTxFee, "Transaction fee, in nAVAX, for transactions that create new assets")
Expand Down
2 changes: 2 additions & 0 deletions config/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const (
GenesisFileKey = "genesis-file"
GenesisFileContentKey = "genesis-file-content"
NetworkNameKey = "network-id"
ACPSupportKey = "acp-support"
ACPObjectKey = "acp-object"
TxFeeKey = "tx-fee"
CreateAssetTxFeeKey = "create-asset-tx-fee"
CreateSubnetTxFeeKey = "create-subnet-tx-fee"
Expand Down
8 changes: 4 additions & 4 deletions message/mock_outbound_message_builder.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions message/outbound_msg_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type OutboundMsgBuilder interface {
myVersionTime uint64,
sig []byte,
trackedSubnets []ids.ID,
supportedACPs []uint32,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not specific to this PR, but we might consider renaming Version to Handshake or something to reflect the fact that it now contains a lot more than just the version of the sender.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree

Copy link
Contributor

Choose a reason for hiding this comment

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

+1

Copy link
Collaborator

Choose a reason for hiding this comment

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

#2479

ask and ye shall receive

objectedACPs []uint32,
) (OutboundMessage, error)

PeerList(
Expand Down Expand Up @@ -240,6 +242,8 @@ func (b *outMsgBuilder) Version(
myVersionTime uint64,
sig []byte,
trackedSubnets []ids.ID,
supportedACPs []uint32,
objectedACPs []uint32,
) (OutboundMessage, error) {
subnetIDBytes := make([][]byte, len(trackedSubnets))
encodeIDs(trackedSubnets, subnetIDBytes)
Expand All @@ -261,6 +265,8 @@ func (b *outMsgBuilder) Version(
Minor: minor,
Patch: patch,
},
SupportedAcps: supportedACPs,
ObjectedAcps: objectedACPs,
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions network/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,9 @@ type Config struct {
PingFrequency time.Duration `json:"pingFrequency"`
AllowPrivateIPs bool `json:"allowPrivateIPs"`

SupportedACPs set.Set[uint32] `json:"supportedACPs"`
ObjectedACPs set.Set[uint32] `json:"objectedACPs"`

// The compression type to use when compressing outbound messages.
// Assumes all peers support this compression type.
CompressionType compression.Type `json:"compressionType"`
Expand Down
2 changes: 2 additions & 0 deletions network/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,8 @@ func NewNetwork(
PingFrequency: config.PingFrequency,
PongTimeout: config.PingPongTimeout,
MaxClockDifference: config.MaxClockDifference,
SupportedACPs: config.SupportedACPs.List(),
ObjectedACPs: config.ObjectedACPs.List(),
ResourceTracker: config.ResourceTracker,
UptimeCalculator: config.UptimeCalculator,
IPSigner: peer.NewIPSigner(config.MyIPPort, config.TLSKey),
Expand Down
3 changes: 3 additions & 0 deletions network/peer/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ type Config struct {
PongTimeout time.Duration
MaxClockDifference time.Duration

SupportedACPs []uint32
ObjectedACPs []uint32

// Unix time of the last message sent and received respectively
// Must only be accessed atomically
LastSent, LastReceived int64
Expand Down
5 changes: 4 additions & 1 deletion network/peer/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ava-labs/avalanchego/ids"
"github.com/ava-labs/avalanchego/utils/json"
"github.com/ava-labs/avalanchego/utils/set"
)

type Info struct {
Expand All @@ -19,5 +20,7 @@ type Info struct {
LastReceived time.Time `json:"lastReceived"`
ObservedUptime json.Uint32 `json:"observedUptime"`
ObservedSubnetUptimes map[ids.ID]json.Uint32 `json:"observedSubnetUptimes"`
TrackedSubnets []ids.ID `json:"trackedSubnets"`
TrackedSubnets set.Set[ids.ID] `json:"trackedSubnets"`
SupportedACPs set.Set[uint32] `json:"supportedACPs"`
ObjectedACPs set.Set[uint32] `json:"objectedACPs"`
}
37 changes: 33 additions & 4 deletions network/peer/peer.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ type peer struct {
// trackedSubnets is the subset of subnetIDs the peer sent us in the Version
// message that we are also tracking.
trackedSubnets set.Set[ids.ID]
// options of ACPs provided in the Version message.
supportedACPs set.Set[uint32]
objectedACPs set.Set[uint32]

observedUptimesLock sync.RWMutex
// [observedUptimesLock] must be held while accessing [observedUptime]
Expand Down Expand Up @@ -246,10 +249,9 @@ func (p *peer) Info() Info {
publicIPStr = p.ip.IPPort.String()
}

trackedSubnets := p.trackedSubnets.List()
uptimes := make(map[ids.ID]json.Uint32, len(trackedSubnets))
uptimes := make(map[ids.ID]json.Uint32, p.trackedSubnets.Len())

for _, subnetID := range trackedSubnets {
for subnetID := range p.trackedSubnets {
uptime, exist := p.ObservedUptime(subnetID)
if !exist {
continue
Expand All @@ -271,7 +273,9 @@ func (p *peer) Info() Info {
LastReceived: p.LastReceived(),
ObservedUptime: json.Uint32(primaryUptime),
ObservedSubnetUptimes: uptimes,
TrackedSubnets: trackedSubnets,
TrackedSubnets: p.trackedSubnets,
SupportedACPs: p.supportedACPs,
ObjectedACPs: p.objectedACPs,
}
}

Expand Down Expand Up @@ -517,6 +521,8 @@ func (p *peer) writeMessages() {
mySignedIP.Timestamp,
mySignedIP.Signature,
p.MySubnets.List(),
p.SupportedACPs,
p.ObjectedACPs,
)
if err != nil {
p.Log.Error("failed to create message",
Expand Down Expand Up @@ -956,6 +962,29 @@ func (p *peer) handleVersion(msg *p2p.Version) {
}
}

for _, acp := range msg.SupportedAcps {
if constants.CurrentACPs.Contains(acp) {
p.supportedACPs.Add(acp)
}
}
for _, acp := range msg.ObjectedAcps {
if constants.CurrentACPs.Contains(acp) {
p.objectedACPs.Add(acp)
}
}

if p.supportedACPs.Overlaps(p.objectedACPs) {
p.Log.Debug("message with invalid field",
zap.Stringer("nodeID", p.id),
zap.Stringer("messageOp", message.VersionOp),
zap.String("field", "ACPs"),
zap.Reflect("supportedACPs", p.supportedACPs),
zap.Reflect("objectedACPs", p.objectedACPs),
)
p.StartClose()
return
}

// "net.IP" type in Golang is 16-byte
if ipLen := len(msg.IpAddr); ipLen != net.IPv6len {
p.Log.Debug("message with invalid field",
Expand Down
20 changes: 20 additions & 0 deletions node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,25 @@ func (n *Node) initNetworking() error {
)
}

// We allow nodes to gossip unknown ACPs in case the current ACPs constant
Copy link
Contributor

Choose a reason for hiding this comment

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

I wonder if this check/log should be done in config/config. Found it "odd" that we checked some conditions there and some here 🤷

Copy link
Contributor

Choose a reason for hiding this comment

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

But not sure if there is some pattern we are following of what we verify here vs there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's here because we have a ref to the logger here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As dan noted - this is done here because the logger is initialized. We aren't really "verifying" here... because we do still allow the node to include acps that haven't been included in the client yet

// becomes out of date.
var unknownACPs set.Set[uint32]
for acp := range n.Config.NetworkConfig.SupportedACPs {
if !constants.CurrentACPs.Contains(acp) {
unknownACPs.Add(acp)
}
}
for acp := range n.Config.NetworkConfig.ObjectedACPs {
if !constants.CurrentACPs.Contains(acp) {
unknownACPs.Add(acp)
}
}
if unknownACPs.Len() > 0 {
n.Log.Warn("gossipping unknown ACPs",
zap.Reflect("acps", unknownACPs),
)
}

tlsConfig := peer.TLSConfig(n.Config.StakingTLSCert, n.tlsKeyLogWriterCloser)

// Configure benchlist
Expand Down Expand Up @@ -1272,6 +1291,7 @@ func (n *Node) initInfoAPI() error {
VMManager: n.VMManager,
},
n.Log,
n.vdrs,
n.chainManager,
n.VMManager,
n.Config.NetworkConfig.MyIPPort,
Expand Down
2 changes: 2 additions & 0 deletions proto/p2p/p2p.proto
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ message Version {
// Subnets the peer is tracking
repeated bytes tracked_subnets = 8;
Client client = 9;
repeated uint32 supported_acps = 10;
patrick-ogrady marked this conversation as resolved.
Show resolved Hide resolved
repeated uint32 objected_acps = 11;
}

// Metadata about a peer's P2P client used to determine compatibility
Expand Down