diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9aaa759e74f..2a1770d8140 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: os: [macos-12, ubuntu-20.04, ubuntu-22.04, windows-2022, [self-hosted, linux, ARM64, focal], [self-hosted, linux, ARM64, jammy]] steps: - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - uses: actions/setup-go@v3 with: go-version: ${{ env.go_version }} check-latest: true diff --git a/chains/manager.go b/chains/manager.go index 8d63db69f9c..8d8ce2a8f15 100644 --- a/chains/manager.go +++ b/chains/manager.go @@ -94,7 +94,6 @@ var ( errUnknownVMType = errors.New("the vm should have type avalanche.DAGVM or snowman.ChainVM") errCreatePlatformVM = errors.New("attempted to create a chain running the PlatformVM") errNotBootstrapped = errors.New("subnets not bootstrapped") - errNoPrimaryNetworkConfig = errors.New("no subnet config for primary network found") errPartialSyncAsAValidator = errors.New("partial sync should not be configured for a validator") fxs = map[ids.ID]fx.Factory{ @@ -230,6 +229,8 @@ type ManagerConfig struct { StateSyncBeacons []ids.NodeID ChainDataDir string + + Subnets *Subnets } type manager struct { @@ -253,11 +254,6 @@ type manager struct { chainCreatorShutdownCh chan struct{} chainCreatorExited sync.WaitGroup - subnetsLock sync.RWMutex - // Key: Subnet's ID - // Value: Subnet description - subnets map[ids.ID]subnets.Subnet - chainsLock sync.Mutex // Key: Chain's ID // Value: The chain @@ -274,7 +270,6 @@ func New(config *ManagerConfig) Manager { ManagerConfig: *config, stakingSigner: config.StakingTLSCert.PrivateKey.(crypto.Signer), stakingCert: staking.CertificateFromX509(config.StakingTLSCert.Leaf), - subnets: make(map[ids.ID]subnets.Subnet), chains: make(map[ids.ID]handler.Handler), chainsQueue: buffer.NewUnboundedBlockingDeque[ChainParameters](initialQueueSize), unblockChainCreatorCh: make(chan struct{}), @@ -285,25 +280,10 @@ func New(config *ManagerConfig) Manager { // QueueChainCreation queues a chain creation request // Invariant: Tracked Subnet must be checked before calling this function func (m *manager) QueueChainCreation(chainParams ChainParameters) { - m.subnetsLock.Lock() - subnetID := chainParams.SubnetID - sb, exists := m.subnets[subnetID] - if !exists { - sbConfig, ok := m.SubnetConfigs[subnetID] - if !ok { - // default to primary subnet config - sbConfig = m.SubnetConfigs[constants.PrimaryNetworkID] - } - sb = subnets.New(m.NodeID, sbConfig) - m.subnets[chainParams.SubnetID] = sb - } - addedChain := sb.AddChain(chainParams.ID) - m.subnetsLock.Unlock() - - if !addedChain { + if sb, _ := m.Subnets.GetOrCreate(chainParams.SubnetID); !sb.AddChain(chainParams.ID) { m.Log.Debug("skipping chain creation", zap.String("reason", "chain already staged"), - zap.Stringer("subnetID", subnetID), + zap.Stringer("subnetID", chainParams.SubnetID), zap.Stringer("chainID", chainParams.ID), zap.Stringer("vmID", chainParams.VMID), ) @@ -313,7 +293,7 @@ func (m *manager) QueueChainCreation(chainParams ChainParameters) { if ok := m.chainsQueue.PushRight(chainParams); !ok { m.Log.Warn("skipping chain creation", zap.String("reason", "couldn't enqueue chain"), - zap.Stringer("subnetID", subnetID), + zap.Stringer("subnetID", chainParams.SubnetID), zap.Stringer("chainID", chainParams.ID), zap.Stringer("vmID", chainParams.VMID), ) @@ -331,9 +311,7 @@ func (m *manager) createChain(chainParams ChainParameters) { zap.Stringer("vmID", chainParams.VMID), ) - m.subnetsLock.RLock() - sb := m.subnets[chainParams.SubnetID] - m.subnetsLock.RUnlock() + sb, _ := m.Subnets.GetOrCreate(chainParams.SubnetID) // Note: buildChain builds all chain's relevant objects (notably engine and handler) // but does not start their operations. Starting of the handler (which could potentially @@ -1304,23 +1282,9 @@ func (m *manager) IsBootstrapped(id ids.ID) bool { return chain.Context().State.Get().State == snow.NormalOp } -func (m *manager) subnetsNotBootstrapped() []ids.ID { - m.subnetsLock.RLock() - defer m.subnetsLock.RUnlock() - - subnetsBootstrapping := make([]ids.ID, 0, len(m.subnets)) - for subnetID, subnet := range m.subnets { - if !subnet.IsBootstrapped() { - subnetsBootstrapping = append(subnetsBootstrapping, subnetID) - } - } - return subnetsBootstrapping -} - func (m *manager) registerBootstrappedHealthChecks() error { bootstrappedCheck := health.CheckerFunc(func(context.Context) (interface{}, error) { - subnetIDs := m.subnetsNotBootstrapped() - if len(subnetIDs) != 0 { + if subnetIDs := m.Subnets.Bootstrapping(); len(subnetIDs) != 0 { return subnetIDs, errNotBootstrapped } return []ids.ID{}, nil @@ -1362,18 +1326,9 @@ func (m *manager) registerBootstrappedHealthChecks() error { // Starts chain creation loop to process queued chains func (m *manager) StartChainCreator(platformParams ChainParameters) error { - // Get the Primary Network's subnet config. If it wasn't registered, then we - // throw a fatal error. - sbConfig, ok := m.SubnetConfigs[constants.PrimaryNetworkID] - if !ok { - return errNoPrimaryNetworkConfig - } - - sb := subnets.New(m.NodeID, sbConfig) - m.subnetsLock.Lock() - m.subnets[platformParams.SubnetID] = sb + // Add the P-Chain to the Primary Network + sb, _ := m.Subnets.GetOrCreate(constants.PrimaryNetworkID) sb.AddChain(platformParams.ID) - m.subnetsLock.Unlock() // The P-chain is created synchronously to ensure that `VM.Initialize` has // finished before returning from this function. This is required because diff --git a/chains/subnets.go b/chains/subnets.go new file mode 100644 index 00000000000..fda66a7eded --- /dev/null +++ b/chains/subnets.go @@ -0,0 +1,82 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package chains + +import ( + "errors" + "sync" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/subnets" + "github.com/ava-labs/avalanchego/utils/constants" +) + +var ErrNoPrimaryNetworkConfig = errors.New("no subnet config for primary network found") + +// Subnets holds the currently running subnets on this node +type Subnets struct { + nodeID ids.NodeID + configs map[ids.ID]subnets.Config + + lock sync.RWMutex + subnets map[ids.ID]subnets.Subnet +} + +// GetOrCreate returns a subnet running on this node, or creates one if it was +// not running before. Returns the subnet and if the subnet was created. +func (s *Subnets) GetOrCreate(subnetID ids.ID) (subnets.Subnet, bool) { + s.lock.Lock() + defer s.lock.Unlock() + + if subnet, ok := s.subnets[subnetID]; ok { + return subnet, false + } + + // Default to the primary network config if a subnet config was not + // specified + config, ok := s.configs[subnetID] + if !ok { + config = s.configs[constants.PrimaryNetworkID] + } + + subnet := subnets.New(s.nodeID, config) + s.subnets[subnetID] = subnet + + return subnet, true +} + +// Bootstrapping returns the subnetIDs of any chains that are still +// bootstrapping. +func (s *Subnets) Bootstrapping() []ids.ID { + s.lock.RLock() + defer s.lock.RUnlock() + + subnetsBootstrapping := make([]ids.ID, 0, len(s.subnets)) + for subnetID, subnet := range s.subnets { + if !subnet.IsBootstrapped() { + subnetsBootstrapping = append(subnetsBootstrapping, subnetID) + } + } + + return subnetsBootstrapping +} + +// NewSubnets returns an instance of Subnets +func NewSubnets( + nodeID ids.NodeID, + configs map[ids.ID]subnets.Config, +) (*Subnets, error) { + if _, ok := configs[constants.PrimaryNetworkID]; !ok { + return nil, ErrNoPrimaryNetworkConfig + } + + s := &Subnets{ + nodeID: nodeID, + configs: configs, + subnets: make(map[ids.ID]subnets.Subnet), + } + + _, _ = s.GetOrCreate(constants.PrimaryNetworkID) + return s, nil +} diff --git a/chains/subnets_test.go b/chains/subnets_test.go new file mode 100644 index 00000000000..231a8f970a1 --- /dev/null +++ b/chains/subnets_test.go @@ -0,0 +1,173 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package chains + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/subnets" + "github.com/ava-labs/avalanchego/utils/constants" +) + +func TestNewSubnets(t *testing.T) { + require := require.New(t) + config := map[ids.ID]subnets.Config{ + constants.PrimaryNetworkID: {}, + } + + subnets, err := NewSubnets(ids.EmptyNodeID, config) + require.NoError(err) + + subnet, ok := subnets.GetOrCreate(constants.PrimaryNetworkID) + require.False(ok) + require.Equal(config[constants.PrimaryNetworkID], subnet.Config()) +} + +func TestNewSubnetsNoPrimaryNetworkConfig(t *testing.T) { + require := require.New(t) + config := map[ids.ID]subnets.Config{} + + _, err := NewSubnets(ids.EmptyNodeID, config) + require.ErrorIs(err, ErrNoPrimaryNetworkConfig) +} + +func TestSubnetsGetOrCreate(t *testing.T) { + testSubnetID := ids.GenerateTestID() + + type args struct { + subnetID ids.ID + want bool + } + + tests := []struct { + name string + args []args + }{ + { + name: "adding duplicate subnet is a noop", + args: []args{ + { + subnetID: testSubnetID, + want: true, + }, + { + subnetID: testSubnetID, + }, + }, + }, + { + name: "adding unique subnets succeeds", + args: []args{ + { + subnetID: ids.GenerateTestID(), + want: true, + }, + { + subnetID: ids.GenerateTestID(), + want: true, + }, + { + subnetID: ids.GenerateTestID(), + want: true, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + config := map[ids.ID]subnets.Config{ + constants.PrimaryNetworkID: {}, + } + subnets, err := NewSubnets(ids.EmptyNodeID, config) + require.NoError(err) + + for _, arg := range tt.args { + _, got := subnets.GetOrCreate(arg.subnetID) + require.Equal(arg.want, got) + } + }) + } +} + +func TestSubnetConfigs(t *testing.T) { + testSubnetID := ids.GenerateTestID() + + tests := []struct { + name string + config map[ids.ID]subnets.Config + subnetID ids.ID + want subnets.Config + }{ + { + name: "default to primary network config", + config: map[ids.ID]subnets.Config{ + constants.PrimaryNetworkID: {}, + }, + subnetID: testSubnetID, + want: subnets.Config{}, + }, + { + name: "use subnet config", + config: map[ids.ID]subnets.Config{ + constants.PrimaryNetworkID: {}, + testSubnetID: { + GossipConfig: subnets.GossipConfig{ + AcceptedFrontierValidatorSize: 123456789, + }, + }, + }, + subnetID: testSubnetID, + want: subnets.Config{ + GossipConfig: subnets.GossipConfig{ + AcceptedFrontierValidatorSize: 123456789, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + require := require.New(t) + + subnets, err := NewSubnets(ids.EmptyNodeID, tt.config) + require.NoError(err) + + subnet, ok := subnets.GetOrCreate(tt.subnetID) + require.True(ok) + + require.Equal(tt.want, subnet.Config()) + }) + } +} + +func TestSubnetsBootstrapping(t *testing.T) { + require := require.New(t) + + config := map[ids.ID]subnets.Config{ + constants.PrimaryNetworkID: {}, + } + + subnets, err := NewSubnets(ids.EmptyNodeID, config) + require.NoError(err) + + subnetID := ids.GenerateTestID() + chainID := ids.GenerateTestID() + + subnet, ok := subnets.GetOrCreate(subnetID) + require.True(ok) + + // Start bootstrapping + subnet.AddChain(chainID) + bootstrapping := subnets.Bootstrapping() + require.Contains(bootstrapping, subnetID) + + // Finish bootstrapping + subnet.Bootstrapped(chainID) + require.Empty(subnets.Bootstrapping()) +} diff --git a/message/messages_benchmark_test.go b/message/messages_benchmark_test.go index 7ac2163f5b1..595ba1b1826 100644 --- a/message/messages_benchmark_test.go +++ b/message/messages_benchmark_test.go @@ -53,8 +53,9 @@ func BenchmarkMarshalHandshake(b *testing.B) { IpPort: 0, MyVersion: "v1.2.3", IpSigningTime: uint64(time.Now().Unix()), - Sig: []byte{'y', 'e', 'e', 't'}, + IpNodeIdSig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{id[:]}, + IpBlsSig: []byte{'y', 'e', 'e', 't', '2'}, }, }, } @@ -109,8 +110,9 @@ func BenchmarkUnmarshalHandshake(b *testing.B) { IpPort: 0, MyVersion: "v1.2.3", IpSigningTime: uint64(time.Now().Unix()), - Sig: []byte{'y', 'e', 'e', 't'}, + IpNodeIdSig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{id[:]}, + IpBlsSig: []byte{'y', 'e', 'e', 't', '2'}, }, }, } diff --git a/message/messages_test.go b/message/messages_test.go index 7b6ed93698b..6e4978dd71a 100644 --- a/message/messages_test.go +++ b/message/messages_test.go @@ -131,8 +131,9 @@ func TestMessage(t *testing.T) { IpPort: 9651, MyVersion: "v1.2.3", IpSigningTime: uint64(nowUnix), - Sig: []byte{'y', 'e', 'e', 't'}, + IpNodeIdSig: []byte{'y', 'e', 'e', 't'}, TrackedSubnets: [][]byte{testID[:]}, + IpBlsSig: []byte{'y', 'e', 'e', 't', '2'}, }, }, }, diff --git a/message/mock_outbound_message_builder.go b/message/mock_outbound_message_builder.go index 0d053f71090..ac9a09a51b5 100644 --- a/message/mock_outbound_message_builder.go +++ b/message/mock_outbound_message_builder.go @@ -268,18 +268,18 @@ func (mr *MockOutboundMsgBuilderMockRecorder) GetStateSummaryFrontier(arg0, arg1 } // Handshake mocks base method. -func (m *MockOutboundMsgBuilder) Handshake(arg0 uint32, arg1 uint64, arg2 ips.IPPort, arg3, arg4 string, arg5, arg6, arg7 uint32, arg8 uint64, arg9 []byte, arg10 []ids.ID, arg11, arg12 []uint32, arg13, arg14 []byte) (OutboundMessage, error) { +func (m *MockOutboundMsgBuilder) Handshake(arg0 uint32, arg1 uint64, arg2 ips.IPPort, arg3, arg4 string, arg5, arg6, arg7 uint32, arg8 uint64, arg9, arg10 []byte, arg11 []ids.ID, arg12, arg13 []uint32, arg14, arg15 []byte) (OutboundMessage, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Handshake", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) + ret := m.ctrl.Call(m, "Handshake", arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) ret0, _ := ret[0].(OutboundMessage) ret1, _ := ret[1].(error) return ret0, ret1 } // Handshake indicates an expected call of Handshake. -func (mr *MockOutboundMsgBuilderMockRecorder) Handshake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14 any) *gomock.Call { +func (mr *MockOutboundMsgBuilderMockRecorder) Handshake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15 any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handshake", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Handshake), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Handshake", reflect.TypeOf((*MockOutboundMsgBuilder)(nil).Handshake), arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15) } // PeerList mocks base method. diff --git a/message/outbound_msg_builder.go b/message/outbound_msg_builder.go index 150c9f4f3a6..bffd2df3625 100644 --- a/message/outbound_msg_builder.go +++ b/message/outbound_msg_builder.go @@ -28,7 +28,8 @@ type OutboundMsgBuilder interface { minor uint32, patch uint32, ipSigningTime uint64, - sig []byte, + ipNodeIDSig []byte, + ipBLSSig []byte, trackedSubnets []ids.ID, supportedACPs []uint32, objectedACPs []uint32, @@ -243,7 +244,8 @@ func (b *outMsgBuilder) Handshake( minor uint32, patch uint32, ipSigningTime uint64, - sig []byte, + ipNodeIDSig []byte, + ipBLSSig []byte, trackedSubnets []ids.ID, supportedACPs []uint32, objectedACPs []uint32, @@ -262,7 +264,7 @@ func (b *outMsgBuilder) Handshake( IpPort: uint32(ip.Port), MyVersion: myVersion, IpSigningTime: ipSigningTime, - Sig: sig, + IpNodeIdSig: ipNodeIDSig, TrackedSubnets: subnetIDBytes, Client: &p2p.Client{ Name: client, @@ -276,6 +278,7 @@ func (b *outMsgBuilder) Handshake( Filter: knownPeersFilter, Salt: knownPeersSalt, }, + IpBlsSig: ipBLSSig, }, }, }, diff --git a/network/config.go b/network/config.go index 5cb014741f5..64c57d120cd 100644 --- a/network/config.go +++ b/network/config.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanchego/snow/uptime" "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/utils/compression" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/set" ) @@ -142,6 +143,8 @@ type Config struct { // TLSKey is this node's TLS key that is used to sign IPs. TLSKey crypto.Signer `json:"-"` + // BLSKey is this node's BLS key that is used to sign IPs. + BLSKey *bls.SecretKey `json:"-"` // TrackedSubnets of the node. TrackedSubnets set.Set[ids.ID] `json:"-"` diff --git a/network/network.go b/network/network.go index 9f599ca302b..5e4b3cdc4e8 100644 --- a/network/network.go +++ b/network/network.go @@ -260,6 +260,7 @@ func NewNetwork( VersionCompatibility: version.GetCompatibility(config.NetworkID), MySubnets: config.TrackedSubnets, Beacons: config.Beacons, + Validators: config.Validators, NetworkID: config.NetworkID, PingFrequency: config.PingFrequency, PongTimeout: config.PingPongTimeout, @@ -268,7 +269,7 @@ func NewNetwork( ObjectedACPs: config.ObjectedACPs.List(), ResourceTracker: config.ResourceTracker, UptimeCalculator: config.UptimeCalculator, - IPSigner: peer.NewIPSigner(config.MyIPPort, config.TLSKey), + IPSigner: peer.NewIPSigner(config.MyIPPort, config.TLSKey, config.BLSKey), } // Invariant: We delay the activation of durango during the TLS handshake to @@ -435,7 +436,7 @@ func (n *network) Connected(nodeID ids.NodeID) { peer.Cert(), peerIP.IPPort, peerIP.Timestamp, - peerIP.Signature, + peerIP.TLSSignature, ) n.ipTracker.Connected(newIP) @@ -621,7 +622,7 @@ func (n *network) track(ip *ips.ClaimedIPPort) error { IPPort: ip.IPPort, Timestamp: ip.Timestamp, }, - Signature: ip.Signature, + TLSSignature: ip.Signature, } maxTimestamp := n.peerConfig.Clock.Time().Add(n.peerConfig.MaxClockDifference) if err := signedIP.Verify(ip.Cert, maxTimestamp); err != nil { diff --git a/network/network_test.go b/network/network_test.go index f58e0ef601d..0f95d722b65 100644 --- a/network/network_test.go +++ b/network/network_test.go @@ -28,6 +28,7 @@ import ( "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math/meter" @@ -171,11 +172,15 @@ func newTestNetwork(t *testing.T, count int) (*testDialer, []*testListener, []id ip, listener := dialer.NewListener() nodeID, tlsCert, tlsConfig := getTLS(t, i) + blsKey, err := bls.NewSecretKey() + require.NoError(t, err) + config := defaultConfig config.TLSConfig = tlsConfig config.MyNodeID = nodeID config.MyIPPort = ip config.TLSKey = tlsCert.PrivateKey.(crypto.Signer) + config.BLSKey = blsKey listeners[i] = listener nodeIDs[i] = nodeID @@ -542,7 +547,7 @@ func TestDialDeletesNonValidators(t *testing.T) { } config := configs[0] - signer := peer.NewIPSigner(config.MyIPPort, config.TLSKey) + signer := peer.NewIPSigner(config.MyIPPort, config.TLSKey, config.BLSKey) ip, err := signer.GetSignedIP() require.NoError(err) @@ -555,7 +560,7 @@ func TestDialDeletesNonValidators(t *testing.T) { staking.CertificateFromX509(config.TLSConfig.Certificates[0].Leaf), ip.IPPort, ip.Timestamp, - ip.Signature, + ip.TLSSignature, ), }) require.NoError(err) diff --git a/network/peer/config.go b/network/peer/config.go index 0a01cf87fb9..3eb8319216d 100644 --- a/network/peer/config.go +++ b/network/peer/config.go @@ -35,6 +35,7 @@ type Config struct { VersionCompatibility version.Compatibility MySubnets set.Set[ids.ID] Beacons validators.Manager + Validators validators.Manager NetworkID uint32 PingFrequency time.Duration PongTimeout time.Duration diff --git a/network/peer/ip.go b/network/peer/ip.go index 590003c850d..a873f1668d6 100644 --- a/network/peer/ip.go +++ b/network/peer/ip.go @@ -11,6 +11,7 @@ import ( "time" "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/hashing" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/wrappers" @@ -18,7 +19,7 @@ import ( var ( errTimestampTooFarInFuture = errors.New("timestamp too far in the future") - errInvalidSignature = errors.New("invalid signature") + errInvalidTLSSignature = errors.New("invalid TLS signature") ) // UnsignedIP is used for a validator to claim an IP. The [Timestamp] is used to @@ -30,21 +31,25 @@ type UnsignedIP struct { } // Sign this IP with the provided signer and return the signed IP. -func (ip *UnsignedIP) Sign(signer crypto.Signer) (*SignedIP, error) { - sig, err := signer.Sign( +func (ip *UnsignedIP) Sign(tlsSigner crypto.Signer, blsSigner *bls.SecretKey) (*SignedIP, error) { + ipBytes := ip.bytes() + tlsSignature, err := tlsSigner.Sign( rand.Reader, - hashing.ComputeHash256(ip.bytes()), + hashing.ComputeHash256(ipBytes), crypto.SHA256, ) + blsSignature := bls.SignProofOfPossession(blsSigner, ipBytes) return &SignedIP{ - UnsignedIP: *ip, - Signature: sig, + UnsignedIP: *ip, + TLSSignature: tlsSignature, + BLSSignature: blsSignature, + BLSSignatureBytes: bls.SignatureToBytes(blsSignature), }, err } func (ip *UnsignedIP) bytes() []byte { p := wrappers.Packer{ - Bytes: make([]byte, wrappers.IPLen+wrappers.LongLen), + Bytes: make([]byte, ips.IPPortLen+wrappers.LongLen), } ips.PackIP(&p, ip.IPPort) p.PackLong(ip.Timestamp) @@ -54,12 +59,14 @@ func (ip *UnsignedIP) bytes() []byte { // SignedIP is a wrapper of an UnsignedIP with the signature from a signer. type SignedIP struct { UnsignedIP - Signature []byte + TLSSignature []byte + BLSSignature *bls.Signature + BLSSignatureBytes []byte } // Returns nil if: // * [ip.Timestamp] is not after [maxTimestamp]. -// * [ip.Signature] is a valid signature over [ip.UnsignedIP] from [cert]. +// * [ip.TLSSignature] is a valid signature over [ip.UnsignedIP] from [cert]. func (ip *SignedIP) Verify( cert *staking.Certificate, maxTimestamp time.Time, @@ -72,9 +79,9 @@ func (ip *SignedIP) Verify( if err := staking.CheckSignature( cert, ip.UnsignedIP.bytes(), - ip.Signature, + ip.TLSSignature, ); err != nil { - return fmt.Errorf("%w: %w", errInvalidSignature, err) + return fmt.Errorf("%w: %w", errInvalidTLSSignature, err) } return nil } diff --git a/network/peer/ip_signer.go b/network/peer/ip_signer.go index cfe85f38781..1c38d4e6752 100644 --- a/network/peer/ip_signer.go +++ b/network/peer/ip_signer.go @@ -7,15 +7,17 @@ import ( "crypto" "sync" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/timer/mockable" ) // IPSigner will return a signedIP for the current value of our dynamic IP. type IPSigner struct { - ip ips.DynamicIPPort - clock mockable.Clock - signer crypto.Signer + ip ips.DynamicIPPort + clock mockable.Clock + tlsSigner crypto.Signer + blsSigner *bls.SecretKey // Must be held while accessing [signedIP] signedIPLock sync.RWMutex @@ -26,11 +28,13 @@ type IPSigner struct { func NewIPSigner( ip ips.DynamicIPPort, - signer crypto.Signer, + tlsSigner crypto.Signer, + blsSigner *bls.SecretKey, ) *IPSigner { return &IPSigner{ - ip: ip, - signer: signer, + ip: ip, + tlsSigner: tlsSigner, + blsSigner: blsSigner, } } @@ -67,7 +71,7 @@ func (s *IPSigner) GetSignedIP() (*SignedIP, error) { IPPort: ip, Timestamp: s.clock.Unix(), } - signedIP, err := unsignedIP.Sign(s.signer) + signedIP, err := unsignedIP.Sign(s.tlsSigner, s.blsSigner) if err != nil { return nil, err } diff --git a/network/peer/ip_signer_test.go b/network/peer/ip_signer_test.go index 7e5314f5f58..315becd8f08 100644 --- a/network/peer/ip_signer_test.go +++ b/network/peer/ip_signer_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" ) @@ -26,9 +27,11 @@ func TestIPSigner(t *testing.T) { tlsCert, err := staking.NewTLSCert() require.NoError(err) - key := tlsCert.PrivateKey.(crypto.Signer) + tlsKey := tlsCert.PrivateKey.(crypto.Signer) + blsKey, err := bls.NewSecretKey() + require.NoError(err) - s := NewIPSigner(dynIP, key) + s := NewIPSigner(dynIP, tlsKey, blsKey) s.clock.Set(time.Unix(10, 0)) @@ -43,7 +46,7 @@ func TestIPSigner(t *testing.T) { require.NoError(err) require.Equal(dynIP.IPPort(), signedIP2.IPPort) require.Equal(uint64(10), signedIP2.Timestamp) - require.Equal(signedIP1.Signature, signedIP2.Signature) + require.Equal(signedIP1.TLSSignature, signedIP2.TLSSignature) dynIP.SetIP(net.IPv4(1, 2, 3, 4)) @@ -51,5 +54,5 @@ func TestIPSigner(t *testing.T) { require.NoError(err) require.Equal(dynIP.IPPort(), signedIP3.IPPort) require.Equal(uint64(11), signedIP3.Timestamp) - require.NotEqual(signedIP2.Signature, signedIP3.Signature) + require.NotEqual(signedIP2.TLSSignature, signedIP3.TLSSignature) } diff --git a/network/peer/ip_test.go b/network/peer/ip_test.go index 9c9b0072cef..dd39d5a8a8b 100644 --- a/network/peer/ip_test.go +++ b/network/peer/ip_test.go @@ -12,6 +12,7 @@ import ( "github.com/stretchr/testify/require" "github.com/ava-labs/avalanchego/staking" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" ) @@ -20,6 +21,9 @@ func TestSignedIpVerify(t *testing.T) { require.NoError(t, err) cert1 := staking.CertificateFromX509(tlsCert1.Leaf) require.NoError(t, staking.ValidateCertificate(cert1)) + tlsKey1 := tlsCert1.PrivateKey.(crypto.Signer) + blsKey1, err := bls.NewSecretKey() + require.NoError(t, err) tlsCert2, err := staking.NewTLSCert() require.NoError(t, err) @@ -30,7 +34,8 @@ func TestSignedIpVerify(t *testing.T) { type test struct { name string - signer crypto.Signer + tlsSigner crypto.Signer + blsSigner *bls.SecretKey expectedCert *staking.Certificate ip UnsignedIP maxTimestamp time.Time @@ -40,7 +45,8 @@ func TestSignedIpVerify(t *testing.T) { tests := []test{ { name: "valid (before max time)", - signer: tlsCert1.PrivateKey.(crypto.Signer), + tlsSigner: tlsKey1, + blsSigner: blsKey1, expectedCert: cert1, ip: UnsignedIP{ IPPort: ips.IPPort{ @@ -54,7 +60,8 @@ func TestSignedIpVerify(t *testing.T) { }, { name: "valid (at max time)", - signer: tlsCert1.PrivateKey.(crypto.Signer), + tlsSigner: tlsKey1, + blsSigner: blsKey1, expectedCert: cert1, ip: UnsignedIP{ IPPort: ips.IPPort{ @@ -68,7 +75,8 @@ func TestSignedIpVerify(t *testing.T) { }, { name: "timestamp too far ahead", - signer: tlsCert1.PrivateKey.(crypto.Signer), + tlsSigner: tlsKey1, + blsSigner: blsKey1, expectedCert: cert1, ip: UnsignedIP{ IPPort: ips.IPPort{ @@ -82,7 +90,8 @@ func TestSignedIpVerify(t *testing.T) { }, { name: "sig from wrong cert", - signer: tlsCert1.PrivateKey.(crypto.Signer), + tlsSigner: tlsKey1, + blsSigner: blsKey1, expectedCert: cert2, // note this isn't cert1 ip: UnsignedIP{ IPPort: ips.IPPort{ @@ -92,13 +101,13 @@ func TestSignedIpVerify(t *testing.T) { Timestamp: uint64(now.Unix()), }, maxTimestamp: now, - expectedErr: errInvalidSignature, + expectedErr: errInvalidTLSSignature, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - signedIP, err := tt.ip.Sign(tt.signer) + signedIP, err := tt.ip.Sign(tt.tlsSigner, tt.blsSigner) require.NoError(t, err) err = signedIP.Verify(tt.expectedCert, tt.maxTimestamp) diff --git a/network/peer/peer.go b/network/peer/peer.go index f5cebb613e0..58b4e648125 100644 --- a/network/peer/peer.go +++ b/network/peer/peer.go @@ -23,6 +23,7 @@ import ( "github.com/ava-labs/avalanchego/utils" "github.com/ava-labs/avalanchego/utils/bloom" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/json" "github.com/ava-labs/avalanchego/utils/set" @@ -142,6 +143,14 @@ type peer struct { supportedACPs set.Set[uint32] objectedACPs set.Set[uint32] + // txIDOfVerifiedBLSKey is the txID that added the BLS key that was most + // recently verified to have signed the IP. + // + // Invariant: Prior to the handshake being completed, this can only be + // accessed by the reader goroutine. After the handshake has been completed, + // this can only be accessed by the message sender goroutine. + txIDOfVerifiedBLSKey ids.ID + observedUptimesLock sync.RWMutex // [observedUptimesLock] must be held while accessing [observedUptime] // Subnet ID --> Our uptime for the given subnet as perceived by the peer @@ -550,7 +559,8 @@ func (p *peer) writeMessages() { uint32(myVersion.Minor), uint32(myVersion.Patch), mySignedIP.Timestamp, - mySignedIP.Signature, + mySignedIP.TLSSignature, + mySignedIP.BLSSignatureBytes, p.MySubnets.List(), p.SupportedACPs, p.ObjectedACPs, @@ -697,16 +707,11 @@ func (p *peer) sendNetworkMessages() { return } - if p.finishedHandshake.Get() { - if err := p.VersionCompatibility.Compatible(p.version); err != nil { - p.Log.Debug("disconnecting from peer", - zap.String("reason", "version not compatible"), - zap.Stringer("nodeID", p.id), - zap.Stringer("peerVersion", p.version), - zap.Error(err), - ) - return - } + // Only check if we should disconnect after the handshake is + // finished to avoid race conditions and accessing uninitialized + // values. + if p.finishedHandshake.Get() && p.shouldDisconnect() { + return } primaryUptime, subnetUptimes := p.getUptimes() @@ -727,6 +732,68 @@ func (p *peer) sendNetworkMessages() { } } +// shouldDisconnect is called both during receipt of the Handshake message and +// periodically when sending a Ping message (after finishing the handshake!). +// +// It is called during the Handshake to prevent marking a peer as connected and +// then immediately disconnecting from them. +// +// It is called when sending a Ping message to account for validator set +// changes. It's called when sending a Ping rather than in a validator set +// callback to avoid signature verification on the P-chain accept path. +func (p *peer) shouldDisconnect() bool { + if err := p.VersionCompatibility.Compatible(p.version); err != nil { + p.Log.Debug("disconnecting from peer", + zap.String("reason", "version not compatible"), + zap.Stringer("nodeID", p.id), + zap.Stringer("peerVersion", p.version), + zap.Error(err), + ) + return true + } + + // Enforce that all validators that have registered a BLS key are signing + // their IP with it after the activation of Durango. + vdr, ok := p.Validators.GetValidator(constants.PrimaryNetworkID, p.id) + if !ok || vdr.PublicKey == nil || vdr.TxID == p.txIDOfVerifiedBLSKey { + return false + } + + postDurango := p.Clock.Time().After(version.GetDurangoTime(constants.MainnetID)) + if postDurango && p.ip.BLSSignature == nil { + p.Log.Debug("disconnecting from peer", + zap.String("reason", "missing BLS signature"), + zap.Stringer("nodeID", p.id), + ) + return true + } + + // If Durango hasn't activated on mainnet yet, we don't require BLS + // signatures to be provided. However, if they are provided, verify that + // they are correct. + if p.ip.BLSSignature == nil { + return false + } + + validSignature := bls.VerifyProofOfPossession( + vdr.PublicKey, + p.ip.BLSSignature, + p.ip.UnsignedIP.bytes(), + ) + if !validSignature { + p.Log.Debug("disconnecting from peer", + zap.String("reason", "invalid BLS signature"), + zap.Stringer("nodeID", p.id), + ) + return true + } + + // Avoid unnecessary signature verifications by only verifing the signature + // once per validation period. + p.txIDOfVerifiedBLSKey = vdr.TxID + return false +} + func (p *peer) handle(msg message.InboundMessage) { switch m := msg.Message().(type) { // Network-related message types case *p2p.Ping: @@ -965,16 +1032,6 @@ func (p *peer) handleHandshake(msg *p2p.Handshake) { } } - if err := p.VersionCompatibility.Compatible(p.version); err != nil { - p.Log.Verbo("peer version not compatible", - zap.Stringer("nodeID", p.id), - zap.Stringer("peerVersion", p.version), - zap.Error(err), - ) - p.StartClose() - return - } - // handle subnet IDs for _, subnetIDBytes := range msg.TrackedSubnets { subnetID, err := ids.ToID(subnetIDBytes) @@ -1076,13 +1133,14 @@ func (p *peer) handleHandshake(msg *p2p.Handshake) { }, Timestamp: msg.IpSigningTime, }, - Signature: msg.Sig, + TLSSignature: msg.IpNodeIdSig, } maxTimestamp := myTime.Add(p.MaxClockDifference) if err := p.ip.Verify(p.cert, maxTimestamp); err != nil { if _, ok := p.Beacons.GetValidator(constants.PrimaryNetworkID, p.id); ok { p.Log.Warn("beacon has invalid signature or is out of sync", zap.Stringer("nodeID", p.id), + zap.String("signatureType", "tls"), zap.Uint64("peerTime", msg.MyTime), zap.Uint64("myTime", myTimeUnix), zap.Error(err), @@ -1090,6 +1148,7 @@ func (p *peer) handleHandshake(msg *p2p.Handshake) { } else { p.Log.Debug("peer has invalid signature or is out of sync", zap.Stringer("nodeID", p.id), + zap.String("signatureType", "tls"), zap.Uint64("peerTime", msg.MyTime), zap.Uint64("myTime", myTimeUnix), zap.Error(err), @@ -1100,6 +1159,31 @@ func (p *peer) handleHandshake(msg *p2p.Handshake) { return } + // TODO: After v1.11.x is activated, require the key to be provided. + if len(msg.IpBlsSig) > 0 { + signature, err := bls.SignatureFromBytes(msg.IpBlsSig) + if err != nil { + p.Log.Debug("peer has malformed signature", + zap.Stringer("nodeID", p.id), + zap.String("signatureType", "bls"), + zap.Error(err), + ) + p.StartClose() + return + } + + p.ip.BLSSignature = signature + p.ip.BLSSignatureBytes = msg.IpBlsSig + } + + // If the peer is running an incompatible version or has an invalid BLS + // signature, disconnect from them prior to marking the handshake as + // completed. + if p.shouldDisconnect() { + p.StartClose() + return + } + p.gotHandshake.Set(true) peerIPs := p.Network.Peers(p.id, knownPeers, salt) diff --git a/network/peer/peer_test.go b/network/peer/peer_test.go index 31de437288b..e52273fc6fb 100644 --- a/network/peer/peer_test.go +++ b/network/peer/peer_test.go @@ -23,11 +23,13 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math/meter" "github.com/ava-labs/avalanchego/utils/resource" "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/timer/mockable" "github.com/ava-labs/avalanchego/version" ) @@ -102,6 +104,7 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee MySubnets: trackedSubnets, UptimeCalculator: uptime.NoOpCalculator, Beacons: validators.NewManager(), + Validators: validators.NewManager(), NetworkID: constants.LocalID, PingFrequency: constants.DefaultPingFrequency, PongTimeout: constants.DefaultPingPongTimeout, @@ -113,7 +116,10 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee ip0 := ips.NewDynamicIPPort(net.IPv6loopback, 1) tls0 := tlsCert0.PrivateKey.(crypto.Signer) - peerConfig0.IPSigner = NewIPSigner(ip0, tls0) + bls0, err := bls.NewSecretKey() + require.NoError(err) + + peerConfig0.IPSigner = NewIPSigner(ip0, tls0, bls0) peerConfig0.Network = TestNetwork inboundMsgChan0 := make(chan message.InboundMessage) @@ -123,7 +129,10 @@ func makeRawTestPeers(t *testing.T, trackedSubnets set.Set[ids.ID]) (*rawTestPee ip1 := ips.NewDynamicIPPort(net.IPv6loopback, 2) tls1 := tlsCert1.PrivateKey.(crypto.Signer) - peerConfig1.IPSigner = NewIPSigner(ip1, tls1) + bls1, err := bls.NewSecretKey() + require.NoError(err) + + peerConfig1.IPSigner = NewIPSigner(ip1, tls1, bls1) peerConfig1.Network = TestNetwork inboundMsgChan1 := make(chan message.InboundMessage) @@ -376,6 +385,449 @@ func TestPingUptimes(t *testing.T) { } } +// Test that a peer using the wrong BLS key is disconnected from. +func TestInvalidBLSKeyDisconnects(t *testing.T) { + require := require.New(t) + + rawPeer0, rawPeer1 := makeRawTestPeers(t, nil) + require.NoError(rawPeer0.config.Validators.AddStaker( + constants.PrimaryNetworkID, + rawPeer1.nodeID, + bls.PublicFromSecretKey(rawPeer1.config.IPSigner.blsSigner), + ids.GenerateTestID(), + 1, + )) + + bogusBLSKey, err := bls.NewSecretKey() + require.NoError(err) + require.NoError(rawPeer1.config.Validators.AddStaker( + constants.PrimaryNetworkID, + rawPeer0.nodeID, + bls.PublicFromSecretKey(bogusBLSKey), // This is the wrong BLS key for this peer + ids.GenerateTestID(), + 1, + )) + peer0 := &testPeer{ + Peer: Start( + rawPeer0.config, + rawPeer0.conn, + rawPeer1.cert, + rawPeer1.nodeID, + NewThrottledMessageQueue( + rawPeer0.config.Metrics, + rawPeer1.nodeID, + logging.NoLog{}, + throttling.NewNoOutboundThrottler(), + ), + ), + inboundMsgChan: rawPeer0.inboundMsgChan, + } + peer1 := &testPeer{ + Peer: Start( + rawPeer1.config, + rawPeer1.conn, + rawPeer0.cert, + rawPeer0.nodeID, + NewThrottledMessageQueue( + rawPeer1.config.Metrics, + rawPeer0.nodeID, + logging.NoLog{}, + throttling.NewNoOutboundThrottler(), + ), + ), + inboundMsgChan: rawPeer1.inboundMsgChan, + } + + // Because peer1 thinks that peer0 is using the wrong BLS key, they should + // disconnect from each other. + require.NoError(peer0.AwaitClosed(context.Background())) + require.NoError(peer1.AwaitClosed(context.Background())) +} + +func TestShouldDisconnect(t *testing.T) { + peerID := ids.GenerateTestNodeID() + txID := ids.GenerateTestID() + blsKey, err := bls.NewSecretKey() + require.NoError(t, err) + + tests := []struct { + name string + initialPeer *peer + expectedPeer *peer + expectedShouldDisconnect bool + }{ + { + name: "peer is reporting old version", + initialPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + }, + version: &version.Application{ + Name: version.Client, + Major: 0, + Minor: 0, + Patch: 0, + }, + }, + expectedPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + }, + version: &version.Application{ + Name: version.Client, + Major: 0, + Minor: 0, + Patch: 0, + }, + }, + expectedShouldDisconnect: true, + }, + { + name: "peer is not a validator", + initialPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: validators.NewManager(), + }, + version: version.CurrentApp, + }, + expectedPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: validators.NewManager(), + }, + version: version.CurrentApp, + }, + expectedShouldDisconnect: false, + }, + { + name: "peer is a validator without a BLS key", + initialPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + nil, + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + }, + expectedPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + nil, + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + }, + expectedShouldDisconnect: false, + }, + { + name: "already verified peer", + initialPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + txIDOfVerifiedBLSKey: txID, + }, + expectedPeer: &peer{ + Config: &Config{ + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + txIDOfVerifiedBLSKey: txID, + }, + expectedShouldDisconnect: false, + }, + { + name: "past durango without a signature", + initialPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(mockable.MaxTime) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{}, + }, + expectedPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(mockable.MaxTime) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{}, + }, + expectedShouldDisconnect: true, + }, + { + name: "pre durango without a signature", + initialPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{}, + }, + expectedPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{}, + }, + expectedShouldDisconnect: false, + }, + { + name: "pre durango with an invalid signature", + initialPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{ + BLSSignature: bls.SignProofOfPossession(blsKey, []byte("wrong message")), + }, + }, + expectedPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{ + BLSSignature: bls.SignProofOfPossession(blsKey, []byte("wrong message")), + }, + }, + expectedShouldDisconnect: true, + }, + { + name: "pre durango with a valid signature", + initialPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{ + BLSSignature: bls.SignProofOfPossession(blsKey, (&UnsignedIP{}).bytes()), + }, + }, + expectedPeer: &peer{ + Config: &Config{ + Clock: func() mockable.Clock { + clk := mockable.Clock{} + clk.Set(time.Time{}) + return clk + }(), + Log: logging.NoLog{}, + VersionCompatibility: version.GetCompatibility(constants.UnitTestID), + Validators: func() validators.Manager { + vdrs := validators.NewManager() + require.NoError(t, vdrs.AddStaker( + constants.PrimaryNetworkID, + peerID, + bls.PublicFromSecretKey(blsKey), + txID, + 1, + )) + return vdrs + }(), + }, + id: peerID, + version: version.CurrentApp, + ip: &SignedIP{ + BLSSignature: bls.SignProofOfPossession(blsKey, (&UnsignedIP{}).bytes()), + }, + txIDOfVerifiedBLSKey: txID, + }, + expectedShouldDisconnect: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + require := require.New(t) + + shouldDisconnect := test.initialPeer.shouldDisconnect() + require.Equal(test.expectedPeer, test.initialPeer) + require.Equal(test.expectedShouldDisconnect, shouldDisconnect) + }) + } +} + // Helper to send a message from sender to receiver and assert that the // receiver receives the message. This can be used to test a prior message // was handled by the peer. diff --git a/network/peer/test_peer.go b/network/peer/test_peer.go index 04cfd93aaa7..eb1a7947648 100644 --- a/network/peer/test_peer.go +++ b/network/peer/test_peer.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/avalanchego/snow/validators" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math/meter" @@ -104,7 +105,11 @@ func StartTestPeer( } signerIP := ips.NewDynamicIPPort(net.IPv6zero, 1) - tls := tlsCert.PrivateKey.(crypto.Signer) + tlsKey := tlsCert.PrivateKey.(crypto.Signer) + blsKey, err := bls.NewSecretKey() + if err != nil { + return nil, err + } peer := Start( &Config{ @@ -117,13 +122,14 @@ func StartTestPeer( VersionCompatibility: version.GetCompatibility(networkID), MySubnets: set.Set[ids.ID]{}, Beacons: validators.NewManager(), + Validators: validators.NewManager(), NetworkID: networkID, PingFrequency: constants.DefaultPingFrequency, PongTimeout: constants.DefaultPingPongTimeout, MaxClockDifference: time.Minute, ResourceTracker: resourceTracker, UptimeCalculator: uptime.NoOpCalculator, - IPSigner: NewIPSigner(signerIP, tls), + IPSigner: NewIPSigner(signerIP, tlsKey, blsKey), }, conn, cert, diff --git a/network/test_network.go b/network/test_network.go index 8079e76240c..1cb56127c1a 100644 --- a/network/test_network.go +++ b/network/test_network.go @@ -25,6 +25,7 @@ import ( "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/subnets" "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" "github.com/ava-labs/avalanchego/utils/ips" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/math/meter" @@ -187,6 +188,10 @@ func NewTestNetwork( tlsConfig := peer.TLSConfig(*tlsCert, nil) networkConfig.TLSConfig = tlsConfig networkConfig.TLSKey = tlsCert.PrivateKey.(crypto.Signer) + networkConfig.BLSKey, err = bls.NewSecretKey() + if err != nil { + return nil, err + } networkConfig.Validators = currentValidators networkConfig.Beacons = validators.NewManager() diff --git a/node/node.go b/node/node.go index 2a5ebb680c6..54caaba1251 100644 --- a/node/node.go +++ b/node/node.go @@ -600,6 +600,7 @@ func (n *Node) initNetworking() error { n.Config.NetworkConfig.Beacons = n.bootstrappers n.Config.NetworkConfig.TLSConfig = tlsConfig n.Config.NetworkConfig.TLSKey = tlsKey + n.Config.NetworkConfig.BLSKey = n.Config.StakingSigningKey n.Config.NetworkConfig.TrackedSubnets = n.Config.TrackedSubnets n.Config.NetworkConfig.UptimeCalculator = n.uptimeCalculator n.Config.NetworkConfig.UptimeRequirement = n.Config.UptimeRequirement @@ -1115,51 +1116,58 @@ func (n *Node) initChainManager(avaxAssetID ids.ID) error { return fmt.Errorf("couldn't initialize chain router: %w", err) } - n.chainManager = chains.New(&chains.ManagerConfig{ - SybilProtectionEnabled: n.Config.SybilProtectionEnabled, - StakingTLSCert: n.Config.StakingTLSCert, - StakingBLSKey: n.Config.StakingSigningKey, - Log: n.Log, - LogFactory: n.LogFactory, - VMManager: n.VMManager, - BlockAcceptorGroup: n.BlockAcceptorGroup, - TxAcceptorGroup: n.TxAcceptorGroup, - VertexAcceptorGroup: n.VertexAcceptorGroup, - DB: n.DB, - MsgCreator: n.msgCreator, - Router: n.chainRouter, - Net: n.Net, - Validators: n.vdrs, - PartialSyncPrimaryNetwork: n.Config.PartialSyncPrimaryNetwork, - NodeID: n.ID, - NetworkID: n.Config.NetworkID, - Server: n.APIServer, - Keystore: n.keystore, - AtomicMemory: n.sharedMemory, - AVAXAssetID: avaxAssetID, - XChainID: xChainID, - CChainID: cChainID, - CriticalChains: criticalChains, - TimeoutManager: n.timeoutManager, - Health: n.health, - ShutdownNodeFunc: n.Shutdown, - MeterVMEnabled: n.Config.MeterVMEnabled, - Metrics: n.MetricsGatherer, - SubnetConfigs: n.Config.SubnetConfigs, - ChainConfigs: n.Config.ChainConfigs, - FrontierPollFrequency: n.Config.FrontierPollFrequency, - ConsensusAppConcurrency: n.Config.ConsensusAppConcurrency, - BootstrapMaxTimeGetAncestors: n.Config.BootstrapMaxTimeGetAncestors, - BootstrapAncestorsMaxContainersSent: n.Config.BootstrapAncestorsMaxContainersSent, - BootstrapAncestorsMaxContainersReceived: n.Config.BootstrapAncestorsMaxContainersReceived, - ApricotPhase4Time: version.GetApricotPhase4Time(n.Config.NetworkID), - ApricotPhase4MinPChainHeight: version.ApricotPhase4MinPChainHeight[n.Config.NetworkID], - ResourceTracker: n.resourceTracker, - StateSyncBeacons: n.Config.StateSyncIDs, - TracingEnabled: n.Config.TraceConfig.Enabled, - Tracer: n.tracer, - ChainDataDir: n.Config.ChainDataDir, - }) + subnets, err := chains.NewSubnets(n.ID, n.Config.SubnetConfigs) + if err != nil { + return fmt.Errorf("failed to initialize subnets: %w", err) + } + n.chainManager = chains.New( + &chains.ManagerConfig{ + SybilProtectionEnabled: n.Config.SybilProtectionEnabled, + StakingTLSCert: n.Config.StakingTLSCert, + StakingBLSKey: n.Config.StakingSigningKey, + Log: n.Log, + LogFactory: n.LogFactory, + VMManager: n.VMManager, + BlockAcceptorGroup: n.BlockAcceptorGroup, + TxAcceptorGroup: n.TxAcceptorGroup, + VertexAcceptorGroup: n.VertexAcceptorGroup, + DB: n.DB, + MsgCreator: n.msgCreator, + Router: n.chainRouter, + Net: n.Net, + Validators: n.vdrs, + PartialSyncPrimaryNetwork: n.Config.PartialSyncPrimaryNetwork, + NodeID: n.ID, + NetworkID: n.Config.NetworkID, + Server: n.APIServer, + Keystore: n.keystore, + AtomicMemory: n.sharedMemory, + AVAXAssetID: avaxAssetID, + XChainID: xChainID, + CChainID: cChainID, + CriticalChains: criticalChains, + TimeoutManager: n.timeoutManager, + Health: n.health, + ShutdownNodeFunc: n.Shutdown, + MeterVMEnabled: n.Config.MeterVMEnabled, + Metrics: n.MetricsGatherer, + SubnetConfigs: n.Config.SubnetConfigs, + ChainConfigs: n.Config.ChainConfigs, + FrontierPollFrequency: n.Config.FrontierPollFrequency, + ConsensusAppConcurrency: n.Config.ConsensusAppConcurrency, + BootstrapMaxTimeGetAncestors: n.Config.BootstrapMaxTimeGetAncestors, + BootstrapAncestorsMaxContainersSent: n.Config.BootstrapAncestorsMaxContainersSent, + BootstrapAncestorsMaxContainersReceived: n.Config.BootstrapAncestorsMaxContainersReceived, + ApricotPhase4Time: version.GetApricotPhase4Time(n.Config.NetworkID), + ApricotPhase4MinPChainHeight: version.ApricotPhase4MinPChainHeight[n.Config.NetworkID], + ResourceTracker: n.resourceTracker, + StateSyncBeacons: n.Config.StateSyncIDs, + TracingEnabled: n.Config.TraceConfig.Enabled, + Tracer: n.tracer, + ChainDataDir: n.Config.ChainDataDir, + Subnets: subnets, + }, + ) // Notify the API server when new chains are created n.chainManager.AddRegistrant(n.APIServer) diff --git a/proto/p2p/p2p.proto b/proto/p2p/p2p.proto index 6b5deacc7e0..71a7c4f8f84 100644 --- a/proto/p2p/p2p.proto +++ b/proto/p2p/p2p.proto @@ -112,14 +112,18 @@ message Handshake { string my_version = 5; // Timestamp of the IP uint64 ip_signing_time = 6; - // Signature of the peer IP port pair at a provided timestamp - bytes sig = 7; + // Signature of the peer IP port pair at a provided timestamp with the TLS + // key. + bytes ip_node_id_sig = 7; // Subnets the peer is tracking repeated bytes tracked_subnets = 8; Client client = 9; repeated uint32 supported_acps = 10; repeated uint32 objected_acps = 11; BloomFilter known_peers = 12; + // Signature of the peer IP port pair at a provided timestamp with the BLS + // key. + bytes ip_bls_sig = 13; } // Metadata about a peer's P2P client used to determine compatibility diff --git a/proto/pb/p2p/p2p.pb.go b/proto/pb/p2p/p2p.pb.go index 0732bf1a13c..18ef744e2c9 100644 --- a/proto/pb/p2p/p2p.pb.go +++ b/proto/pb/p2p/p2p.pb.go @@ -708,14 +708,18 @@ type Handshake struct { MyVersion string `protobuf:"bytes,5,opt,name=my_version,json=myVersion,proto3" json:"my_version,omitempty"` // Timestamp of the IP IpSigningTime uint64 `protobuf:"varint,6,opt,name=ip_signing_time,json=ipSigningTime,proto3" json:"ip_signing_time,omitempty"` - // Signature of the peer IP port pair at a provided timestamp - Sig []byte `protobuf:"bytes,7,opt,name=sig,proto3" json:"sig,omitempty"` + // Signature of the peer IP port pair at a provided timestamp with the TLS + // key. + IpNodeIdSig []byte `protobuf:"bytes,7,opt,name=ip_node_id_sig,json=ipNodeIdSig,proto3" json:"ip_node_id_sig,omitempty"` // Subnets the peer is tracking TrackedSubnets [][]byte `protobuf:"bytes,8,rep,name=tracked_subnets,json=trackedSubnets,proto3" json:"tracked_subnets,omitempty"` Client *Client `protobuf:"bytes,9,opt,name=client,proto3" json:"client,omitempty"` SupportedAcps []uint32 `protobuf:"varint,10,rep,packed,name=supported_acps,json=supportedAcps,proto3" json:"supported_acps,omitempty"` ObjectedAcps []uint32 `protobuf:"varint,11,rep,packed,name=objected_acps,json=objectedAcps,proto3" json:"objected_acps,omitempty"` KnownPeers *BloomFilter `protobuf:"bytes,12,opt,name=known_peers,json=knownPeers,proto3" json:"known_peers,omitempty"` + // Signature of the peer IP port pair at a provided timestamp with the BLS + // key. + IpBlsSig []byte `protobuf:"bytes,13,opt,name=ip_bls_sig,json=ipBlsSig,proto3" json:"ip_bls_sig,omitempty"` } func (x *Handshake) Reset() { @@ -792,9 +796,9 @@ func (x *Handshake) GetIpSigningTime() uint64 { return 0 } -func (x *Handshake) GetSig() []byte { +func (x *Handshake) GetIpNodeIdSig() []byte { if x != nil { - return x.Sig + return x.IpNodeIdSig } return nil } @@ -834,6 +838,13 @@ func (x *Handshake) GetKnownPeers() *BloomFilter { return nil } +func (x *Handshake) GetIpBlsSig() []byte { + if x != nil { + return x.IpBlsSig + } + return nil +} + // Metadata about a peer's P2P client used to determine compatibility type Client struct { state protoimpl.MessageState @@ -2738,7 +2749,7 @@ var file_p2p_p2p_proto_rawDesc = []byte{ 0x74, 0x69, 0x6d, 0x65, 0x12, 0x38, 0x0a, 0x0e, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x5f, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x52, - 0x0d, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, 0x9b, + 0x0d, 0x73, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x55, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x22, 0xcc, 0x03, 0x0a, 0x09, 0x48, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6d, @@ -2750,233 +2761,237 @@ var file_p2p_p2p_proto_rawDesc = []byte{ 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6d, 0x79, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x26, 0x0a, 0x0f, 0x69, 0x70, 0x5f, 0x73, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0d, - 0x69, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x10, 0x0a, - 0x03, 0x73, 0x69, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x73, 0x69, 0x67, 0x12, - 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x6e, 0x65, - 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, - 0x64, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, - 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, - 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x41, 0x63, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, - 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, - 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x5e, 0x0a, 0x06, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x61, - 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, 0x72, - 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, 0x0b, - 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x66, - 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, 0x6c, - 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0xbd, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, 0x69, - 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, 0x30, - 0x39, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, - 0x63, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, 0x0a, - 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, - 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, 0x72, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, - 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x22, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, 0x65, - 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, - 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, - 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, - 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, 0x65, - 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, - 0x5f, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x12, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, - 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, - 0x72, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, - 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, - 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, - 0x22, 0x89, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, - 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, - 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x04, 0x52, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, 0x14, - 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, - 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, - 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1f, - 0x0a, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, - 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, 0x22, - 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, - 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, - 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, - 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x30, 0x0a, - 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x22, - 0x75, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, 0x74, - 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, - 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, - 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, - 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xba, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, 0x63, - 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x0a, 0x0d, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, - 0x73, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, - 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, - 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, - 0x79, 0x70, 0x65, 0x22, 0x6f, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, + 0x69, 0x70, 0x53, 0x69, 0x67, 0x6e, 0x69, 0x6e, 0x67, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x23, 0x0a, + 0x0e, 0x69, 0x70, 0x5f, 0x6e, 0x6f, 0x64, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x69, 0x67, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x69, 0x70, 0x4e, 0x6f, 0x64, 0x65, 0x49, 0x64, 0x53, + 0x69, 0x67, 0x12, 0x27, 0x0a, 0x0f, 0x74, 0x72, 0x61, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x73, 0x75, + 0x62, 0x6e, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0e, 0x74, 0x72, 0x61, + 0x63, 0x6b, 0x65, 0x64, 0x53, 0x75, 0x62, 0x6e, 0x65, 0x74, 0x73, 0x12, 0x23, 0x0a, 0x06, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x70, 0x32, + 0x70, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, + 0x12, 0x25, 0x0a, 0x0e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, + 0x70, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0d, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, + 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x62, 0x6a, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x70, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0d, 0x52, 0x0c, + 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x65, 0x64, 0x41, 0x63, 0x70, 0x73, 0x12, 0x31, 0x0a, 0x0b, + 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x0c, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x52, 0x0a, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x12, + 0x1c, 0x0a, 0x0a, 0x69, 0x70, 0x5f, 0x62, 0x6c, 0x73, 0x5f, 0x73, 0x69, 0x67, 0x18, 0x0d, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x08, 0x69, 0x70, 0x42, 0x6c, 0x73, 0x53, 0x69, 0x67, 0x22, 0x5e, 0x0a, + 0x06, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6d, + 0x61, 0x6a, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x6d, 0x61, 0x6a, 0x6f, + 0x72, 0x12, 0x14, 0x0a, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x05, 0x6d, 0x69, 0x6e, 0x6f, 0x72, 0x12, 0x14, 0x0a, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x70, 0x61, 0x74, 0x63, 0x68, 0x22, 0x39, 0x0a, + 0x0b, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, + 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x73, 0x61, 0x6c, 0x74, 0x22, 0xbd, 0x01, 0x0a, 0x0d, 0x43, 0x6c, 0x61, + 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x78, 0x35, + 0x30, 0x39, 0x5f, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0f, 0x78, 0x35, 0x30, 0x39, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x17, 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x12, 0x17, + 0x0a, 0x07, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x06, 0x69, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, + 0x74, 0x61, 0x6d, 0x70, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x1c, 0x0a, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, 0x75, + 0x72, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x73, 0x69, 0x67, 0x6e, 0x61, 0x74, + 0x75, 0x72, 0x65, 0x12, 0x13, 0x0a, 0x05, 0x74, 0x78, 0x5f, 0x69, 0x64, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x04, 0x74, 0x78, 0x49, 0x64, 0x22, 0x40, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x50, + 0x65, 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x31, 0x0a, 0x0b, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, + 0x5f, 0x70, 0x65, 0x65, 0x72, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, + 0x32, 0x70, 0x2e, 0x42, 0x6c, 0x6f, 0x6f, 0x6d, 0x46, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x52, 0x0a, + 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x50, 0x65, 0x65, 0x72, 0x73, 0x22, 0x48, 0x0a, 0x08, 0x50, 0x65, + 0x65, 0x72, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x3c, 0x0a, 0x10, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, + 0x64, 0x5f, 0x69, 0x70, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x12, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x43, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, + 0x50, 0x6f, 0x72, 0x74, 0x52, 0x0e, 0x63, 0x6c, 0x61, 0x69, 0x6d, 0x65, 0x64, 0x49, 0x70, 0x50, + 0x6f, 0x72, 0x74, 0x73, 0x22, 0x6f, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, - 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, 0x04, - 0x08, 0x04, 0x10, 0x05, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, 0x65, - 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, - 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x22, 0x6b, 0x0a, 0x09, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, + 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, + 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x22, 0x6a, 0x0a, 0x14, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb0, 0x01, - 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, + 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x22, 0x89, 0x01, 0x0a, 0x17, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, + 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, + 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, + 0x69, 0x6e, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x04, 0x52, 0x07, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x73, 0x22, 0x71, 0x0a, + 0x14, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, + 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, - 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, - 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, - 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x22, 0x8f, 0x01, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, + 0x1f, 0x0a, 0x0b, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x49, 0x64, 0x73, + 0x22, 0x9d, 0x01, 0x0a, 0x13, 0x47, 0x65, 0x74, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, + 0x46, 0x72, 0x6f, 0x6e, 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, - 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, - 0x70, 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, 0x79, + 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x30, + 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, + 0x22, 0x75, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x46, 0x72, 0x6f, 0x6e, + 0x74, 0x69, 0x65, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, + 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x64, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xba, 0x01, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x41, + 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x23, 0x0a, + 0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x04, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, + 0x64, 0x73, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, + 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, + 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, + 0x54, 0x79, 0x70, 0x65, 0x22, 0x6f, 0x0a, 0x08, 0x41, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, - 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, - 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, - 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x22, 0xe1, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, - 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, - 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, - 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, - 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, - 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, - 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, - 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x69, 0x74, 0x73, 0x12, - 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, - 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, 0x65, - 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, 0x0b, - 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, 0x0a, - 0x16, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x61, 0x74, - 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, 0x70, - 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x41, 0x74, 0x48, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x22, 0x7f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0c, 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x73, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb9, 0x01, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x41, 0x6e, 0x63, + 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x6b, 0x0a, 0x09, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x73, 0x12, 0x19, + 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0a, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x73, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x22, 0xb0, + 0x01, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, + 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, + 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, + 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, + 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, + 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, + 0x65, 0x22, 0x8f, 0x01, 0x0a, 0x03, 0x50, 0x75, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, + 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, + 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x49, 0x64, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, + 0x18, 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, + 0x79, 0x70, 0x65, 0x22, 0xdc, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x73, 0x68, 0x51, 0x75, 0x65, 0x72, + 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x74, 0x61, + 0x69, 0x6e, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x74, + 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x5f, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, 0x2e, 0x70, 0x32, 0x70, + 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, 0x0a, 0x65, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, + 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x48, 0x65, 0x69, 0x67, + 0x68, 0x74, 0x22, 0xe1, 0x01, 0x0a, 0x09, 0x50, 0x75, 0x6c, 0x6c, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, 0x65, - 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, - 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, - 0x74, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, - 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x41, 0x70, - 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, - 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, - 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, - 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, 0x69, - 0x70, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, 0x09, - 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x5d, 0x0a, 0x0a, 0x45, 0x6e, 0x67, - 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e, 0x47, 0x49, 0x4e, - 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, - 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, - 0x59, 0x50, 0x45, 0x5f, 0x41, 0x56, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x48, 0x45, 0x10, 0x01, 0x12, - 0x17, 0x0a, 0x13, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x53, - 0x4e, 0x4f, 0x57, 0x4d, 0x41, 0x4e, 0x10, 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, 0x68, - 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, 0x2f, - 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, + 0x6e, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x49, 0x64, 0x12, 0x30, 0x0a, 0x0b, 0x65, 0x6e, 0x67, + 0x69, 0x6e, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x0f, + 0x2e, 0x70, 0x32, 0x70, 0x2e, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x52, + 0x0a, 0x65, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x64, + 0x48, 0x65, 0x69, 0x67, 0x68, 0x74, 0x22, 0xba, 0x01, 0x0a, 0x05, 0x43, 0x68, 0x69, 0x74, 0x73, + 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, + 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x21, 0x0a, 0x0c, 0x70, 0x72, + 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x0b, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x12, 0x1f, 0x0a, + 0x0b, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x65, 0x64, 0x49, 0x64, 0x12, 0x33, + 0x0a, 0x16, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x5f, 0x69, 0x64, 0x5f, 0x61, + 0x74, 0x5f, 0x68, 0x65, 0x69, 0x67, 0x68, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x13, + 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x72, 0x65, 0x64, 0x49, 0x64, 0x41, 0x74, 0x48, 0x65, 0x69, + 0x67, 0x68, 0x74, 0x22, 0x7f, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, + 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, + 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1a, 0x0a, 0x08, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x64, + 0x65, 0x61, 0x64, 0x6c, 0x69, 0x6e, 0x65, 0x12, 0x1b, 0x0a, 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, + 0x79, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x0b, 0x41, 0x70, 0x70, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0x88, 0x01, 0x0a, 0x08, 0x41, + 0x70, 0x70, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, + 0x49, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x69, 0x64, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x49, + 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x11, 0x52, 0x09, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, + 0x12, 0x23, 0x0a, 0x0d, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x4d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x43, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x47, 0x6f, 0x73, 0x73, + 0x69, 0x70, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x63, 0x68, 0x61, 0x69, 0x6e, 0x49, 0x64, 0x12, 0x1b, 0x0a, + 0x09, 0x61, 0x70, 0x70, 0x5f, 0x62, 0x79, 0x74, 0x65, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x08, 0x61, 0x70, 0x70, 0x42, 0x79, 0x74, 0x65, 0x73, 0x2a, 0x5d, 0x0a, 0x0a, 0x45, 0x6e, + 0x67, 0x69, 0x6e, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x1b, 0x0a, 0x17, 0x45, 0x4e, 0x47, 0x49, + 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, + 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x19, 0x0a, 0x15, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, + 0x54, 0x59, 0x50, 0x45, 0x5f, 0x41, 0x56, 0x41, 0x4c, 0x41, 0x4e, 0x43, 0x48, 0x45, 0x10, 0x01, + 0x12, 0x17, 0x0a, 0x13, 0x45, 0x4e, 0x47, 0x49, 0x4e, 0x45, 0x5f, 0x54, 0x59, 0x50, 0x45, 0x5f, + 0x53, 0x4e, 0x4f, 0x57, 0x4d, 0x41, 0x4e, 0x10, 0x02, 0x42, 0x2e, 0x5a, 0x2c, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x76, 0x61, 0x2d, 0x6c, 0x61, 0x62, 0x73, + 0x2f, 0x61, 0x76, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x68, 0x65, 0x67, 0x6f, 0x2f, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2f, 0x70, 0x62, 0x2f, 0x70, 0x32, 0x70, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/snow/consensus/snowball/binary_slush.go b/snow/consensus/snowball/binary_slush.go index a440fce0e1e..700c03c5fe6 100644 --- a/snow/consensus/snowball/binary_slush.go +++ b/snow/consensus/snowball/binary_slush.go @@ -5,8 +5,6 @@ package snowball import "fmt" -var _ BinarySlush = (*binarySlush)(nil) - func newBinarySlush(choice int) binarySlush { return binarySlush{ preference: choice, diff --git a/snow/consensus/snowball/binary_snowball.go b/snow/consensus/snowball/binary_snowball.go index f1b213ad98f..2e17bc93501 100644 --- a/snow/consensus/snowball/binary_snowball.go +++ b/snow/consensus/snowball/binary_snowball.go @@ -5,7 +5,7 @@ package snowball import "fmt" -var _ BinarySnowball = (*binarySnowball)(nil) +var _ Binary = (*binarySnowball)(nil) func newBinarySnowball(beta, choice int) binarySnowball { return binarySnowball{ diff --git a/snow/consensus/snowball/binary_snowflake.go b/snow/consensus/snowball/binary_snowflake.go index 139bd40361f..5f897af8843 100644 --- a/snow/consensus/snowball/binary_snowflake.go +++ b/snow/consensus/snowball/binary_snowflake.go @@ -5,7 +5,7 @@ package snowball import "fmt" -var _ BinarySnowflake = (*binarySnowflake)(nil) +var _ Binary = (*binarySnowflake)(nil) func newBinarySnowflake(beta, choice int) binarySnowflake { return binarySnowflake{ diff --git a/snow/consensus/snowball/consensus.go b/snow/consensus/snowball/consensus.go index e28f4cad4d3..82d57b749f2 100644 --- a/snow/consensus/snowball/consensus.go +++ b/snow/consensus/snowball/consensus.go @@ -40,15 +40,18 @@ type Consensus interface { Finalized() bool } -// NnarySnowball augments NnarySnowflake with a counter that tracks the total -// number of positive responses from a network sample. -type NnarySnowball interface{ NnarySnowflake } - -// NnarySnowflake is a snowflake instance deciding between an unbounded number -// of values. After performing a network sample of k nodes, if you have alpha -// votes for one of the choices, you should vote for that choice. Otherwise, you -// should reset. -type NnarySnowflake interface { +// Factory produces Nnary and Unary decision instances +type Factory interface { + NewNnary(params Parameters, choice ids.ID) Nnary + NewUnary(params Parameters) Unary +} + +// Nnary is a snow instance deciding between an unbounded number of values. +// The caller samples k nodes and then calls +// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes +// 2. RecordPollPreference if choice collects >= alphaPreference votes +// 3. RecordUnsuccessfulPoll otherwise +type Nnary interface { fmt.Stringer // Adds a new possible choice @@ -73,29 +76,12 @@ type NnarySnowflake interface { Finalized() bool } -// NnarySlush is a slush instance deciding between an unbounded number of -// values. After performing a network sample of k nodes, if you have alpha -// votes for one of the choices, you should vote for that choice. -type NnarySlush interface { - fmt.Stringer - - // Returns the currently preferred choice to be finalized - Preference() ids.ID - - // RecordSuccessfulPoll records a successful poll towards finalizing the - // specified choice. Assumes the choice was previously added. - RecordSuccessfulPoll(choice ids.ID) -} - -// BinarySnowball augments BinarySnowflake with a counter that tracks the total -// number of positive responses from a network sample. -type BinarySnowball interface{ BinarySnowflake } - -// BinarySnowflake is a snowball instance deciding between two values -// After performing a network sample of k nodes, if you have alpha votes for -// one of the choices, you should vote for that choice. Otherwise, you should -// reset. -type BinarySnowflake interface { +// Binary is a snow instance deciding between two values. +// The caller samples k nodes and then calls +// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes +// 2. RecordPollPreference if choice collects >= alphaPreference votes +// 3. RecordUnsuccessfulPoll otherwise +type Binary interface { fmt.Stringer // Returns the currently preferred choice to be finalized @@ -116,31 +102,20 @@ type BinarySnowflake interface { Finalized() bool } -// BinarySlush is a slush instance deciding between two values. After performing -// a network sample of k nodes, if you have alpha votes for one of the choices, -// you should vote for that choice. -type BinarySlush interface { - fmt.Stringer - - // Returns the currently preferred choice to be finalized - Preference() int - - // RecordSuccessfulPoll records a successful poll towards finalizing the - // specified choice - RecordSuccessfulPoll(choice int) -} - -// UnarySnowball is a snowball instance deciding on one value. After performing -// a network sample of k nodes, if you have alpha votes for the choice, you -// should vote. Otherwise, you should reset. -type UnarySnowball interface { +// Unary is a snow instance deciding on one value. +// The caller samples k nodes and then calls +// 1. RecordSuccessfulPoll if choice collects >= alphaConfidence votes +// 2. RecordPollPreference if choice collects >= alphaPreference votes +// 3. RecordUnsuccessfulPoll otherwise +type Unary interface { fmt.Stringer - // RecordSuccessfulPoll records a successful poll towards finalizing + // RecordSuccessfulPoll records a successful poll that reaches an alpha + // confidence threshold. RecordSuccessfulPoll() - // RecordPollPreference records a poll that strengthens the preference but - // did not contribute towards finalizing + // RecordPollPreference records a poll that receives an alpha preference + // threshold, but not an alpha confidence threshold. RecordPollPreference() // RecordUnsuccessfulPoll resets the snowflake counter of this instance @@ -151,31 +126,8 @@ type UnarySnowball interface { // Returns a new binary snowball instance with the agreement parameters // transferred. Takes in the new beta value and the original choice - Extend(beta, originalPreference int) BinarySnowball - - // Returns a new unary snowball instance with the same state - Clone() UnarySnowball -} - -// UnarySnowflake is a snowflake instance deciding on one value. After -// performing a network sample of k nodes, if you have alpha votes for the -// choice, you should vote. Otherwise, you should reset. -type UnarySnowflake interface { - fmt.Stringer - - // RecordSuccessfulPoll records a successful poll towards finalizing - RecordSuccessfulPoll() - - // RecordUnsuccessfulPoll resets the snowflake counter of this instance - RecordUnsuccessfulPoll() - - // Return whether a choice has been finalized - Finalized() bool - - // Returns a new binary snowball instance with the agreement parameters - // transferred. Takes in the new beta value and the original choice - Extend(beta, originalPreference int) BinarySnowflake + Extend(beta, originalPreference int) Binary // Returns a new unary snowflake instance with the same state - Clone() UnarySnowflake + Clone() Unary } diff --git a/snow/consensus/snowball/consensus_performance_test.go b/snow/consensus/snowball/consensus_performance_test.go index 86a0b1a9a49..776e11fa717 100644 --- a/snow/consensus/snowball/consensus_performance_test.go +++ b/snow/consensus/snowball/consensus_performance_test.go @@ -29,10 +29,10 @@ func TestDualAlphaOptimization(t *testing.T) { source = prng.NewMT19937() ) - singleAlphaNetwork := NewNetwork(params, numColors, source) + singleAlphaNetwork := NewNetwork(SnowballFactory, params, numColors, source) params.AlphaPreference = params.K/2 + 1 - dualAlphaNetwork := NewNetwork(params, numColors, source) + dualAlphaNetwork := NewNetwork(SnowballFactory, params, numColors, source) source.Seed(seed) for i := 0; i < numNodes; i++ { @@ -62,8 +62,8 @@ func TestTreeConvergenceOptimization(t *testing.T) { source = prng.NewMT19937() ) - treeNetwork := NewNetwork(params, numColors, source) - flatNetwork := NewNetwork(params, numColors, source) + treeNetwork := NewNetwork(SnowballFactory, params, numColors, source) + flatNetwork := NewNetwork(SnowballFactory, params, numColors, source) source.Seed(seed) for i := 0; i < numNodes; i++ { diff --git a/snow/consensus/snowball/consensus_reversibility_test.go b/snow/consensus/snowball/consensus_reversibility_test.go index 4668c0de8e0..578fea1dea1 100644 --- a/snow/consensus/snowball/consensus_reversibility_test.go +++ b/snow/consensus/snowball/consensus_reversibility_test.go @@ -23,7 +23,7 @@ func TestSnowballGovernance(t *testing.T) { source = prng.NewMT19937() ) - nBitwise := NewNetwork(params, numColors, source) + nBitwise := NewNetwork(SnowballFactory, params, numColors, source) source.Seed(seed) for i := 0; i < numRed; i++ { diff --git a/snow/consensus/snowball/consensus_test.go b/snow/consensus/snowball/consensus_test.go index 264edaa733d..a7c8d4e3f63 100644 --- a/snow/consensus/snowball/consensus_test.go +++ b/snow/consensus/snowball/consensus_test.go @@ -16,7 +16,7 @@ var ( _ Consensus = (*Byzantine)(nil) ) -func NewByzantine(_ Parameters, choice ids.ID) Consensus { +func NewByzantine(_ Factory, _ Parameters, choice ids.ID) Consensus { return &Byzantine{ preference: choice, } diff --git a/snow/consensus/snowball/factory.go b/snow/consensus/snowball/factory.go new file mode 100644 index 00000000000..1de693b46d1 --- /dev/null +++ b/snow/consensus/snowball/factory.go @@ -0,0 +1,35 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package snowball + +import "github.com/ava-labs/avalanchego/ids" + +var ( + SnowballFactory Factory = snowballFactory{} + SnowflakeFactory Factory = snowflakeFactory{} +) + +type snowballFactory struct{} + +func (snowballFactory) NewNnary(params Parameters, choice ids.ID) Nnary { + sb := newNnarySnowball(params.BetaVirtuous, params.BetaRogue, choice) + return &sb +} + +func (snowballFactory) NewUnary(params Parameters) Unary { + sb := newUnarySnowball(params.BetaVirtuous) + return &sb +} + +type snowflakeFactory struct{} + +func (snowflakeFactory) NewNnary(params Parameters, choice ids.ID) Nnary { + sf := newNnarySnowflake(params.BetaVirtuous, params.BetaRogue, choice) + return &sf +} + +func (snowflakeFactory) NewUnary(params Parameters) Unary { + sf := newUnarySnowflake(params.BetaVirtuous) + return &sf +} diff --git a/snow/consensus/snowball/flat.go b/snow/consensus/snowball/flat.go index 97c549816be..01b5975cba0 100644 --- a/snow/consensus/snowball/flat.go +++ b/snow/consensus/snowball/flat.go @@ -10,19 +10,19 @@ import ( var _ Consensus = (*Flat)(nil) -func NewFlat(params Parameters, choice ids.ID) Consensus { +func NewFlat(factory Factory, params Parameters, choice ids.ID) Consensus { return &Flat{ - nnarySnowball: newNnarySnowball(params.BetaVirtuous, params.BetaRogue, choice), - params: params, + Nnary: factory.NewNnary(params, choice), + params: params, } } -// Flat is a naive implementation of a multi-choice snowball instance +// Flat is a naive implementation of a multi-choice snow instance type Flat struct { - // wraps the n-nary snowball logic - nnarySnowball + // wraps the n-nary snow logic + Nnary - // params contains all the configurations of a snowball instance + // params contains all the configurations of a snow instance params Parameters } diff --git a/snow/consensus/snowball/flat_test.go b/snow/consensus/snowball/flat_test.go index 38ca57d83b0..3f6eab8d5fd 100644 --- a/snow/consensus/snowball/flat_test.go +++ b/snow/consensus/snowball/flat_test.go @@ -21,7 +21,7 @@ func TestFlat(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - f := NewFlat(params, Red) + f := NewFlat(SnowballFactory, params, Red) f.Add(Green) f.Add(Blue) diff --git a/snow/consensus/snowball/network_test.go b/snow/consensus/snowball/network_test.go index 10448662a30..2bfb6df4077 100644 --- a/snow/consensus/snowball/network_test.go +++ b/snow/consensus/snowball/network_test.go @@ -9,20 +9,22 @@ import ( "github.com/ava-labs/avalanchego/utils/sampler" ) -type newConsensusFunc func(params Parameters, choice ids.ID) Consensus +type newConsensusFunc func(factory Factory, params Parameters, choice ids.ID) Consensus type Network struct { params Parameters colors []ids.ID rngSource sampler.Source nodes, running []Consensus + factory Factory } // Create a new network with [numColors] different possible colors to finalize. -func NewNetwork(params Parameters, numColors int, rngSource sampler.Source) *Network { +func NewNetwork(factory Factory, params Parameters, numColors int, rngSource sampler.Source) *Network { n := &Network{ params: params, rngSource: rngSource, + factory: factory, } for i := 0; i < numColors; i++ { n.colors = append(n.colors, ids.Empty.Prefix(uint64(i))) @@ -35,7 +37,7 @@ func (n *Network) AddNode(newConsensusFunc newConsensusFunc) Consensus { s.Initialize(uint64(len(n.colors))) indices, _ := s.Sample(len(n.colors)) - consensus := newConsensusFunc(n.params, n.colors[int(indices[0])]) + consensus := newConsensusFunc(n.factory, n.params, n.colors[int(indices[0])]) for _, index := range indices[1:] { consensus.Add(n.colors[int(index)]) } @@ -56,7 +58,7 @@ func (n *Network) AddNodeSpecificColor( initialPreference int, options []int, ) Consensus { - consensus := newConsensusFunc(n.params, n.colors[initialPreference]) + consensus := newConsensusFunc(n.factory, n.params, n.colors[initialPreference]) for _, i := range options { consensus.Add(n.colors[i]) diff --git a/snow/consensus/snowball/nnary_slush.go b/snow/consensus/snowball/nnary_slush.go index dad85252906..067e75c2f4e 100644 --- a/snow/consensus/snowball/nnary_slush.go +++ b/snow/consensus/snowball/nnary_slush.go @@ -9,8 +9,6 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -var _ NnarySlush = (*nnarySlush)(nil) - func newNnarySlush(choice ids.ID) nnarySlush { return nnarySlush{ preference: choice, diff --git a/snow/consensus/snowball/nnary_snowball.go b/snow/consensus/snowball/nnary_snowball.go index 2a968c0ba91..77ee48440cb 100644 --- a/snow/consensus/snowball/nnary_snowball.go +++ b/snow/consensus/snowball/nnary_snowball.go @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -var _ NnarySnowball = (*nnarySnowball)(nil) +var _ Nnary = (*nnarySnowball)(nil) func newNnarySnowball(betaVirtuous, betaRogue int, choice ids.ID) nnarySnowball { return nnarySnowball{ diff --git a/snow/consensus/snowball/nnary_snowflake.go b/snow/consensus/snowball/nnary_snowflake.go index de898f155f3..897b2bb1e3a 100644 --- a/snow/consensus/snowball/nnary_snowflake.go +++ b/snow/consensus/snowball/nnary_snowflake.go @@ -9,7 +9,7 @@ import ( "github.com/ava-labs/avalanchego/ids" ) -var _ NnarySnowflake = (*nnarySnowflake)(nil) +var _ Nnary = (*nnarySnowflake)(nil) func newNnarySnowflake(betaVirtuous, betaRogue int, choice ids.ID) nnarySnowflake { return nnarySnowflake{ diff --git a/snow/consensus/snowball/tree.go b/snow/consensus/snowball/tree.go index b5c947ab1ce..4b8d8cfe8dc 100644 --- a/snow/consensus/snowball/tree.go +++ b/snow/consensus/snowball/tree.go @@ -17,33 +17,32 @@ var ( _ node = (*binaryNode)(nil) ) -func NewTree(params Parameters, choice ids.ID) Consensus { - snowball := newUnarySnowball(params.BetaVirtuous) - +func NewTree(factory Factory, params Parameters, choice ids.ID) Consensus { t := &Tree{ - params: params, + params: params, + factory: factory, } t.node = &unaryNode{ tree: t, preference: choice, commonPrefix: ids.NumBits, // The initial state has no conflicts - snowball: &snowball, + snow: factory.NewUnary(params), } return t } -// Tree implements the snowball interface by using a modified patricia tree. +// Tree implements the Consensus interface by using a modified patricia tree. type Tree struct { - // node is the root that represents the first snowball instance in the tree, - // and contains references to all the other snowball instances in the tree. + // node is the root that represents the first snow instance in the tree, + // and contains references to all the other snow instances in the tree. node - // params contains all the configurations of a snowball instance + // params contains all the configurations of a snow instance params Parameters // shouldReset is used as an optimization to prevent needless tree - // traversals. If a snowball instance does not get an alpha majority, that + // traversals. If a snow instance does not get an alpha majority, that // instance needs to reset by calling RecordUnsuccessfulPoll. Because the // tree splits votes based on the branch, when an instance doesn't get an // alpha majority none of the children of this instance can get an alpha @@ -52,6 +51,9 @@ type Tree struct { // that any later traversal into this sub-tree should call // RecordUnsuccessfulPoll before performing any other action. shouldReset bool + + // factory is used to produce new snow instances as needed + factory Factory } func (t *Tree) Add(choice ids.ID) { @@ -74,11 +76,11 @@ func (t *Tree) RecordPoll(votes bag.Bag[ids.ID]) bool { }) // Now that the votes have been restricted to valid votes, pass them into - // the first snowball instance + // the first snow instance var successful bool t.node, successful = t.node.RecordPoll(filteredVotes, t.shouldReset) - // Because we just passed the reset into the snowball instance, we should no + // Because we just passed the reset into the snow instance, we should no // longer reset. t.shouldReset = false return successful @@ -137,7 +139,7 @@ type node interface { } // unary is a node with either no children, or a single child. It handles the -// voting on a range of identical, virtuous, snowball instances. +// voting on a range of identical, virtuous, snow instances. type unaryNode struct { // tree references the tree that contains this node tree *Tree @@ -153,8 +155,8 @@ type unaryNode struct { // references commonPrefix int // Will be in the range (decidedPrefix, 256) - // snowball wraps the snowball logic - snowball UnarySnowball + // snow wraps the unary decision logic + snow Unary // shouldReset is used as an optimization to prevent needless tree // traversals. It is the continuation of shouldReset in the Tree struct. @@ -348,19 +350,19 @@ func (u *unaryNode) Add(newChoice ids.ID) node { b := &binaryNode{ tree: u.tree, bit: index, - snowball: u.snowball.Extend(u.tree.params.BetaRogue, bit), + snow: u.snow.Extend(u.tree.params.BetaRogue, bit), shouldReset: [2]bool{u.shouldReset, u.shouldReset}, } b.preferences[bit] = u.preference b.preferences[1-bit] = newChoice - newChildSnowball := newUnarySnowball(u.tree.params.BetaVirtuous) + newChildSnow := u.tree.factory.NewUnary(u.tree.params) newChild := &unaryNode{ tree: u.tree, preference: newChoice, decidedPrefix: index + 1, // The new child assumes this branch has decided in it's favor commonPrefix: ids.NumBits, // The new child has no conflicts under this branch - snowball: &newChildSnowball, + snow: newChildSnow, } switch { @@ -397,7 +399,7 @@ func (u *unaryNode) Add(newChoice ids.ID) node { preference: u.preference, decidedPrefix: originalDecidedPrefix, commonPrefix: index, - snowball: u.snowball.Clone(), + snow: u.snow.Clone(), child: b, } } @@ -410,22 +412,22 @@ func (u *unaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) { // If my parent didn't get enough votes previously, then neither did I if reset { - u.snowball.RecordUnsuccessfulPoll() + u.snow.RecordUnsuccessfulPoll() u.shouldReset = true // Make sure my child is also reset correctly } switch numVotes := votes.Len(); { case numVotes >= u.tree.params.AlphaConfidence: // I got enough votes to increase my confidence - u.snowball.RecordSuccessfulPoll() + u.snow.RecordSuccessfulPoll() case numVotes >= u.tree.params.AlphaPreference: // I got enough votes to update my preference, but not increase my // confidence. - u.snowball.RecordPollPreference() + u.snow.RecordPollPreference() default: // I didn't get enough votes, I must reset and my child must reset as // well - u.snowball.RecordUnsuccessfulPoll() + u.snow.RecordUnsuccessfulPoll() u.shouldReset = true return u, false } @@ -455,12 +457,12 @@ func (u *unaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) { } func (u *unaryNode) Finalized() bool { - return u.snowball.Finalized() + return u.snow.Finalized() } func (u *unaryNode) Printable() (string, []node) { s := fmt.Sprintf("%s Bits = [%d, %d)", - u.snowball, u.decidedPrefix, u.commonPrefix) + u.snow, u.decidedPrefix, u.commonPrefix) if u.child == nil { return s, nil } @@ -468,7 +470,7 @@ func (u *unaryNode) Printable() (string, []node) { } // binaryNode is a node with either no children, or two children. It handles the -// voting of a single, rogue, snowball instance. +// voting of a single, rogue, snow instance. type binaryNode struct { // tree references the tree that contains this node tree *Tree @@ -480,8 +482,8 @@ type binaryNode struct { // bit is the index in the id of the choice this node is deciding on bit int // Will be in the range [0, 256) - // snowball wraps the snowball logic - snowball BinarySnowball + // snow wraps the binary decision logic + snow Binary // shouldReset is used as an optimization to prevent needless tree // traversals. It is the continuation of shouldReset in the Tree struct. @@ -493,7 +495,7 @@ type binaryNode struct { } func (b *binaryNode) Preference() ids.ID { - return b.preferences[b.snowball.Preference()] + return b.preferences[b.snow.Preference()] } func (b *binaryNode) DecidedPrefix() int { @@ -532,7 +534,7 @@ func (b *binaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) } if reset { - b.snowball.RecordUnsuccessfulPoll() + b.snow.RecordUnsuccessfulPoll() b.shouldReset[bit] = true // 1-bit isn't set here because it is set below anyway } @@ -542,13 +544,13 @@ func (b *binaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) switch numVotes := prunedVotes.Len(); { case numVotes >= b.tree.params.AlphaConfidence: // I got enough votes to increase my confidence. - b.snowball.RecordSuccessfulPoll(bit) + b.snow.RecordSuccessfulPoll(bit) case numVotes >= b.tree.params.AlphaPreference: // I got enough votes to update my preference, but not increase my // confidence. - b.snowball.RecordPollPreference(bit) + b.snow.RecordPollPreference(bit) default: - b.snowball.RecordUnsuccessfulPoll() + b.snow.RecordUnsuccessfulPoll() // The winning child didn't get enough votes either b.shouldReset[bit] = true return b, false @@ -563,7 +565,7 @@ func (b *binaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) }) newChild, _ := child.RecordPoll(filteredVotes, b.shouldReset[bit]) - if b.snowball.Finalized() { + if b.snow.Finalized() { // If we are decided here, that means we must have decided due // to this poll. Therefore, we must have decided on bit. return newChild, true @@ -576,11 +578,11 @@ func (b *binaryNode) RecordPoll(votes bag.Bag[ids.ID], reset bool) (node, bool) } func (b *binaryNode) Finalized() bool { - return b.snowball.Finalized() + return b.snow.Finalized() } func (b *binaryNode) Printable() (string, []node) { - s := fmt.Sprintf("%s Bit = %d", b.snowball, b.bit) + s := fmt.Sprintf("%s Bit = %d", b.snow, b.bit) if b.children[0] == nil { return s, nil } diff --git a/snow/consensus/snowball/tree_test.go b/snow/consensus/snowball/tree_test.go index 31a976a577d..1d337afdda6 100644 --- a/snow/consensus/snowball/tree_test.go +++ b/snow/consensus/snowball/tree_test.go @@ -27,7 +27,7 @@ func TestSnowballSingleton(t *testing.T) { BetaVirtuous: 2, BetaRogue: 5, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) require.False(tree.Finalized()) @@ -68,7 +68,7 @@ func TestSnowballRecordUnsuccessfulPoll(t *testing.T) { BetaVirtuous: 3, BetaRogue: 5, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) require.False(tree.Finalized()) @@ -98,7 +98,7 @@ func TestSnowballBinary(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) tree.Add(Blue) require.Equal(Red, tree.Preference()) @@ -141,7 +141,7 @@ func TestSnowballLastBinary(t *testing.T) { BetaVirtuous: 2, BetaRogue: 2, } - tree := NewTree(params, zero) + tree := NewTree(SnowballFactory, params, zero) tree.Add(one) // Should do nothing @@ -185,7 +185,7 @@ func TestSnowballAddPreviouslyRejected(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, zero) + tree := NewTree(SnowballFactory, params, zero) tree.Add(one) tree.Add(four) @@ -242,7 +242,7 @@ func TestSnowballNewUnary(t *testing.T) { BetaVirtuous: 2, BetaRogue: 3, } - tree := NewTree(params, zero) + tree := NewTree(SnowballFactory, params, zero) tree.Add(one) { @@ -292,7 +292,7 @@ func TestSnowballTransitiveReset(t *testing.T) { BetaVirtuous: 2, BetaRogue: 2, } - tree := NewTree(params, zero) + tree := NewTree(SnowballFactory, params, zero) tree.Add(two) tree.Add(eight) @@ -376,7 +376,7 @@ func TestSnowballTrinary(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, Green) + tree := NewTree(SnowballFactory, params, Green) tree.Add(Red) tree.Add(Blue) @@ -430,7 +430,7 @@ func TestSnowballCloseTrinary(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, yellow) + tree := NewTree(SnowballFactory, params, yellow) tree.Add(cyan) tree.Add(magenta) @@ -479,7 +479,7 @@ func TestSnowballAddRejected(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, c0000) + tree := NewTree(SnowballFactory, params, c0000) tree.Add(c1000) tree.Add(c0010) @@ -528,7 +528,7 @@ func TestSnowballResetChild(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, c0000) + tree := NewTree(SnowballFactory, params, c0000) tree.Add(c0100) tree.Add(c1000) @@ -591,7 +591,7 @@ func TestSnowballResetSibling(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, c0000) + tree := NewTree(SnowballFactory, params, c0000) tree.Add(c0100) tree.Add(c1000) @@ -657,14 +657,14 @@ func TestSnowball5Colors(t *testing.T) { colors = append(colors, ids.Empty.Prefix(uint64(i))) } - tree0 := NewTree(params, colors[4]) + tree0 := NewTree(SnowballFactory, params, colors[4]) tree0.Add(colors[0]) tree0.Add(colors[1]) tree0.Add(colors[2]) tree0.Add(colors[3]) - tree1 := NewTree(params, colors[3]) + tree1 := NewTree(SnowballFactory, params, colors[3]) tree1.Add(colors[0]) tree1.Add(colors[1]) @@ -691,7 +691,7 @@ func TestSnowballFineGrained(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, c0000) + tree := NewTree(SnowballFactory, params, c0000) require.Equal(initialUnaryDescription, tree.String()) require.Equal(c0000, tree.Preference()) @@ -785,7 +785,7 @@ func TestSnowballDoubleAdd(t *testing.T) { BetaVirtuous: 3, BetaRogue: 5, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) tree.Add(Red) require.Equal(initialUnaryDescription, tree.String()) @@ -810,7 +810,7 @@ func TestSnowballConsistent(t *testing.T) { source = prng.NewMT19937() ) - n := NewNetwork(params, numColors, source) + n := NewNetwork(SnowballFactory, params, numColors, source) source.Seed(seed) for i := 0; i < numNodes; i++ { @@ -839,7 +839,7 @@ func TestSnowballFilterBinaryChildren(t *testing.T) { BetaVirtuous: 1, BetaRogue: 2, } - tree := NewTree(params, c0000) + tree := NewTree(SnowballFactory, params, c0000) require.Equal(initialUnaryDescription, tree.String()) require.Equal(c0000, tree.Preference()) @@ -920,7 +920,7 @@ func TestSnowballRecordPreferencePollBinary(t *testing.T) { BetaVirtuous: 2, BetaRogue: 2, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) tree.Add(Blue) require.Equal(Red, tree.Preference()) require.False(tree.Finalized()) @@ -955,7 +955,7 @@ func TestSnowballRecordPreferencePollUnary(t *testing.T) { BetaVirtuous: 2, BetaRogue: 2, } - tree := NewTree(params, Red) + tree := NewTree(SnowballFactory, params, Red) require.Equal(Red, tree.Preference()) require.False(tree.Finalized()) diff --git a/snow/consensus/snowball/unary_snowball.go b/snow/consensus/snowball/unary_snowball.go index 3e4477b4b82..638b6d798d8 100644 --- a/snow/consensus/snowball/unary_snowball.go +++ b/snow/consensus/snowball/unary_snowball.go @@ -5,7 +5,7 @@ package snowball import "fmt" -var _ UnarySnowball = (*unarySnowball)(nil) +var _ Unary = (*unarySnowball)(nil) func newUnarySnowball(beta int) unarySnowball { return unarySnowball{ @@ -32,7 +32,7 @@ func (sb *unarySnowball) RecordPollPreference() { sb.unarySnowflake.RecordUnsuccessfulPoll() } -func (sb *unarySnowball) Extend(beta int, choice int) BinarySnowball { +func (sb *unarySnowball) Extend(beta int, choice int) Binary { bs := &binarySnowball{ binarySnowflake: binarySnowflake{ binarySlush: binarySlush{preference: choice}, @@ -46,7 +46,7 @@ func (sb *unarySnowball) Extend(beta int, choice int) BinarySnowball { return bs } -func (sb *unarySnowball) Clone() UnarySnowball { +func (sb *unarySnowball) Clone() Unary { newSnowball := *sb return &newSnowball } diff --git a/snow/consensus/snowball/unary_snowflake.go b/snow/consensus/snowball/unary_snowflake.go index 6bcfebe23fe..f9c9b624144 100644 --- a/snow/consensus/snowball/unary_snowflake.go +++ b/snow/consensus/snowball/unary_snowflake.go @@ -5,7 +5,7 @@ package snowball import "fmt" -var _ UnarySnowflake = (*unarySnowflake)(nil) +var _ Unary = (*unarySnowflake)(nil) func newUnarySnowflake(beta int) unarySnowflake { return unarySnowflake{ @@ -33,6 +33,13 @@ func (sf *unarySnowflake) RecordSuccessfulPoll() { sf.finalized = sf.finalized || sf.confidence >= sf.beta } +// RecordPollPreference fails to reach an alpha threshold to increase our +// confidence, so this calls RecordUnsuccessfulPoll to reset the confidence +// counter. +func (sf *unarySnowflake) RecordPollPreference() { + sf.RecordUnsuccessfulPoll() +} + func (sf *unarySnowflake) RecordUnsuccessfulPoll() { sf.confidence = 0 } @@ -41,7 +48,7 @@ func (sf *unarySnowflake) Finalized() bool { return sf.finalized } -func (sf *unarySnowflake) Extend(beta int, choice int) BinarySnowflake { +func (sf *unarySnowflake) Extend(beta int, choice int) Binary { return &binarySnowflake{ binarySlush: binarySlush{preference: choice}, confidence: sf.confidence, @@ -50,7 +57,7 @@ func (sf *unarySnowflake) Extend(beta int, choice int) BinarySnowflake { } } -func (sf *unarySnowflake) Clone() UnarySnowflake { +func (sf *unarySnowflake) Clone() Unary { newSnowflake := *sf return &newSnowflake } diff --git a/snow/consensus/snowman/snowman_block.go b/snow/consensus/snowman/snowman_block.go index a25099b4519..7e8d339d201 100644 --- a/snow/consensus/snowman/snowman_block.go +++ b/snow/consensus/snowman/snowman_block.go @@ -38,7 +38,7 @@ func (n *snowmanBlock) AddChild(child Block) { // if the snowball instance is nil, this is the first child. So the instance // should be initialized. if n.sb == nil { - n.sb = snowball.NewTree(n.params, childID) + n.sb = snowball.NewTree(snowball.SnowballFactory, n.params, childID) n.children = make(map[ids.ID]Block) } else { n.sb.Add(childID) diff --git a/utils/ips/ip_port.go b/utils/ips/ip_port.go index a60e6300ed4..eea203525a8 100644 --- a/utils/ips/ip_port.go +++ b/utils/ips/ip_port.go @@ -13,7 +13,7 @@ import ( ) const ( - IPPortLen = 16 + wrappers.ShortLen + IPPortLen = net.IPv6len + wrappers.ShortLen nullStr = "null" ) diff --git a/utils/wrappers/packing.go b/utils/wrappers/packing.go index 96d6b9999c0..8700ebe1c5e 100644 --- a/utils/wrappers/packing.go +++ b/utils/wrappers/packing.go @@ -22,8 +22,6 @@ const ( LongLen = 8 // BoolLen is the number of bytes per bool BoolLen = 1 - // IPLen is the number of bytes per IP - IPLen = 16 + ShortLen ) func StringLen(str string) int { diff --git a/vms/platformvm/client.go b/vms/platformvm/client.go index 9311fd290a7..962492eda97 100644 --- a/vms/platformvm/client.go +++ b/vms/platformvm/client.go @@ -67,6 +67,8 @@ type Client interface { startUTXOID ids.ID, options ...rpc.Option, ) ([][]byte, ids.ShortID, ids.ID, error) + // GetSubnet returns information about the specified subnet + GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (GetSubnetClientResponse, error) // GetSubnets returns information about the specified subnets // // Deprecated: Subnets should be fetched from a dedicated indexer. @@ -381,6 +383,40 @@ func (c *client) GetAtomicUTXOs( return utxos, endAddr, endUTXOID, err } +// GetSubnetClientResponse is the response from calling GetSubnet on the client +type GetSubnetClientResponse struct { + // whether it is permissioned or not + IsPermissioned bool + // subnet auth information for a permissioned subnet + ControlKeys []ids.ShortID + Threshold uint32 + Locktime uint64 + // subnet transformation tx ID for a permissionless subnet + SubnetTransformationTxID ids.ID +} + +func (c *client) GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (GetSubnetClientResponse, error) { + res := &GetSubnetResponse{} + err := c.requester.SendRequest(ctx, "platform.getSubnet", &GetSubnetArgs{ + SubnetID: subnetID, + }, res, options...) + if err != nil { + return GetSubnetClientResponse{}, err + } + controlKeys, err := address.ParseToIDs(res.ControlKeys) + if err != nil { + return GetSubnetClientResponse{}, err + } + + return GetSubnetClientResponse{ + IsPermissioned: res.IsPermissioned, + ControlKeys: controlKeys, + Threshold: uint32(res.Threshold), + Locktime: uint64(res.Locktime), + SubnetTransformationTxID: res.SubnetTransformationTxID, + }, nil +} + // ClientSubnet is a representation of a subnet used in client methods type ClientSubnet struct { // ID of the subnet diff --git a/vms/platformvm/service.go b/vms/platformvm/service.go index 1a440d59fac..289fc975edd 100644 --- a/vms/platformvm/service.go +++ b/vms/platformvm/service.go @@ -62,22 +62,23 @@ const ( ) var ( - errMissingDecisionBlock = errors.New("should have a decision block within the past two blocks") - errNoSubnetID = errors.New("argument 'subnetID' not provided") - errNoRewardAddress = errors.New("argument 'rewardAddress' not provided") - errInvalidDelegationRate = errors.New("argument 'delegationFeeRate' must be between 0 and 100, inclusive") - errNoAddresses = errors.New("no addresses provided") - errNoKeys = errors.New("user has no keys or funds") - errStartTimeTooSoon = fmt.Errorf("start time must be at least %s in the future", minAddStakerDelay) - errStartTimeTooLate = errors.New("start time is too far in the future") - errNamedSubnetCantBePrimary = errors.New("subnet validator attempts to validate primary network") - errNoAmount = errors.New("argument 'amount' must be > 0") - errMissingName = errors.New("argument 'name' not given") - errMissingVMID = errors.New("argument 'vmID' not given") - errMissingBlockchainID = errors.New("argument 'blockchainID' not given") - errMissingPrivateKey = errors.New("argument 'privateKey' not given") - errStartAfterEndTime = errors.New("start time must be before end time") - errStartTimeInThePast = errors.New("start time in the past") + errMissingDecisionBlock = errors.New("should have a decision block within the past two blocks") + errNoSubnetID = errors.New("argument 'subnetID' not provided") + errPrimaryNetworkIsNotASubnet = errors.New("the primary network isn't a subnet") + errNoRewardAddress = errors.New("argument 'rewardAddress' not provided") + errInvalidDelegationRate = errors.New("argument 'delegationFeeRate' must be between 0 and 100, inclusive") + errNoAddresses = errors.New("no addresses provided") + errNoKeys = errors.New("user has no keys or funds") + errStartTimeTooSoon = fmt.Errorf("start time must be at least %s in the future", minAddStakerDelay) + errStartTimeTooLate = errors.New("start time is too far in the future") + errNamedSubnetCantBePrimary = errors.New("subnet validator attempts to validate primary network") + errNoAmount = errors.New("argument 'amount' must be > 0") + errMissingName = errors.New("argument 'name' not given") + errMissingVMID = errors.New("argument 'vmID' not given") + errMissingBlockchainID = errors.New("argument 'blockchainID' not given") + errMissingPrivateKey = errors.New("argument 'privateKey' not given") + errStartAfterEndTime = errors.New("start time must be before end time") + errStartTimeInThePast = errors.New("start time in the past") ) // Service defines the API calls that can be made to the platform chain @@ -509,6 +510,73 @@ func (s *Service) GetUTXOs(_ *http.Request, args *api.GetUTXOsArgs, response *ap return nil } +// GetSubnetArgs are the arguments to GetSubnet +type GetSubnetArgs struct { + // ID of the subnet to retrieve information about + SubnetID ids.ID `json:"subnetID"` +} + +// GetSubnetResponse is the response from calling GetSubnet +type GetSubnetResponse struct { + // whether it is permissioned or not + IsPermissioned bool `json:"isPermissioned"` + // subnet auth information for a permissioned subnet + ControlKeys []string `json:"controlKeys"` + Threshold avajson.Uint32 `json:"threshold"` + Locktime avajson.Uint64 `json:"locktime"` + // subnet transformation tx ID for a permissionless subnet + SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"` +} + +func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetSubnetResponse) error { + s.vm.ctx.Log.Debug("API called", + zap.String("service", "platform"), + zap.String("method", "getSubnet"), + zap.Stringer("subnetID", args.SubnetID), + ) + + if args.SubnetID == constants.PrimaryNetworkID { + return errPrimaryNetworkIsNotASubnet + } + + s.vm.ctx.Lock.Lock() + defer s.vm.ctx.Lock.Unlock() + + subnetOwner, err := s.vm.state.GetSubnetOwner(args.SubnetID) + if err != nil { + return err + } + owner, ok := subnetOwner.(*secp256k1fx.OutputOwners) + if !ok { + return fmt.Errorf("expected *secp256k1fx.OutputOwners but got %T", subnetOwner) + } + controlAddrs := make([]string, len(owner.Addrs)) + for i, controlKeyID := range owner.Addrs { + addr, err := s.addrManager.FormatLocalAddress(controlKeyID) + if err != nil { + return fmt.Errorf("problem formatting address: %w", err) + } + controlAddrs[i] = addr + } + + response.ControlKeys = controlAddrs + response.Threshold = avajson.Uint32(owner.Threshold) + response.Locktime = avajson.Uint64(owner.Locktime) + + switch subnetTransformationTx, err := s.vm.state.GetSubnetTransformation(args.SubnetID); err { + case nil: + response.IsPermissioned = false + response.SubnetTransformationTxID = subnetTransformationTx.ID() + case database.ErrNotFound: + response.IsPermissioned = true + response.SubnetTransformationTxID = ids.Empty + default: + return err + } + + return nil +} + /* ****************************************************** ******************* Get Subnets ********************** @@ -527,7 +595,7 @@ type APISubnet struct { Threshold avajson.Uint32 `json:"threshold"` } -// GetSubnetsArgs are the arguments to GetSubnet +// GetSubnetsArgs are the arguments to GetSubnets type GetSubnetsArgs struct { // IDs of the subnets to retrieve information about // If omitted, gets all subnets diff --git a/wallet/chain/c/signer.go b/wallet/chain/c/signer.go index cda9ca49016..24de72c1394 100644 --- a/wallet/chain/c/signer.go +++ b/wallet/chain/c/signer.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/ava-labs/coreth/plugin/evm" + "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" @@ -20,7 +21,6 @@ import ( "github.com/ava-labs/avalanchego/vms/secp256k1fx" stdcontext "context" - ethcommon "github.com/ethereum/go-ethereum/common" ) const version = 0 @@ -37,16 +37,23 @@ var ( ) type Signer interface { - SignUnsignedAtomic(ctx stdcontext.Context, tx evm.UnsignedAtomicTx) (*evm.Tx, error) + // SignAtomic adds as many missing signatures as possible to the provided + // transaction. + // + // If there are already some signatures on the transaction, those signatures + // will not be removed. + // + // If the signer doesn't have the ability to provide a required signature, + // the signature slot will be skipped without reporting an error. SignAtomic(ctx stdcontext.Context, tx *evm.Tx) error } type EthKeychain interface { // The returned Signer can provide a signature for [addr] - GetEth(addr ethcommon.Address) (keychain.Signer, bool) + GetEth(addr common.Address) (keychain.Signer, bool) // Returns the set of addresses for which the accessor keeps an associated // signer - EthAddresses() set.Set[ethcommon.Address] + EthAddresses() set.Set[common.Address] } type SignerBackend interface { @@ -67,11 +74,6 @@ func NewSigner(avaxKC keychain.Keychain, ethKC EthKeychain, backend SignerBacken } } -func (s *txSigner) SignUnsignedAtomic(ctx stdcontext.Context, utx evm.UnsignedAtomicTx) (*evm.Tx, error) { - tx := &evm.Tx{UnsignedAtomicTx: utx} - return tx, s.SignAtomic(ctx, tx) -} - func (s *txSigner) SignAtomic(ctx stdcontext.Context, tx *evm.Tx) error { switch utx := tx.UnsignedAtomicTx.(type) { case *evm.UnsignedImportTx: @@ -150,6 +152,11 @@ func (s *txSigner) getExportSigners(ins []evm.EVMInput) [][]keychain.Signer { return txSigners } +func SignUnsignedAtomic(ctx stdcontext.Context, signer Signer, utx evm.UnsignedAtomicTx) (*evm.Tx, error) { + tx := &evm.Tx{UnsignedAtomicTx: utx} + return tx, signer.SignAtomic(ctx, tx) +} + // TODO: remove [signHash] after the ledger supports signing all transactions. func sign(tx *evm.Tx, signHash bool, txSigners [][]keychain.Signer) error { unsignedBytes, err := evm.Codec.Marshal(version, &tx.UnsignedAtomicTx) diff --git a/wallet/chain/c/wallet.go b/wallet/chain/c/wallet.go index d43cc57d176..1f8d6d25174 100644 --- a/wallet/chain/c/wallet.go +++ b/wallet/chain/c/wallet.go @@ -140,7 +140,7 @@ func (w *wallet) IssueUnsignedAtomicTx( ) (*evm.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := w.signer.SignUnsignedAtomic(ctx, utx) + tx, err := SignUnsignedAtomic(ctx, w.signer, utx) if err != nil { return nil, err } diff --git a/wallet/chain/p/builder_test.go b/wallet/chain/p/builder_test.go new file mode 100644 index 00000000000..9e880f27f6d --- /dev/null +++ b/wallet/chain/p/builder_test.go @@ -0,0 +1,779 @@ +// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package p + +import ( + "slices" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/constants" + "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/platformvm/reward" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" + "github.com/ava-labs/avalanchego/vms/platformvm/stakeable" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" + + stdcontext "context" +) + +var ( + testKeys = secp256k1.TestKeys() + + // We hard-code [avaxAssetID] and [subnetAssetID] to make + // ordering of UTXOs generated by [testUTXOsList] is reproducible + avaxAssetID = ids.Empty.Prefix(1789) + subnetAssetID = ids.Empty.Prefix(2024) + + testCtx = NewContext( + constants.UnitTestID, + avaxAssetID, + units.MicroAvax, // BaseTxFee + 19*units.MicroAvax, // CreateSubnetTxFee + 789*units.MicroAvax, // TransformSubnetTxFee + 1234*units.MicroAvax, // CreateBlockchainTxFee + 19*units.MilliAvax, // AddPrimaryNetworkValidatorFee + 765*units.MilliAvax, // AddPrimaryNetworkDelegatorFee + 1010*units.MilliAvax, // AddSubnetValidatorFee + 9*units.Avax, // AddSubnetDelegatorFee + ) +) + +// These tests create and sign a tx, then verify that utxos included +// in the tx are exactly necessary to pay fees for it + +func TestBaseTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = NewBackend(testCtx, chainUTXOs, nil) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr), backend) + + // data to build the transaction + outputsToMove = []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: avaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 7 * units.Avax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{utxoAddr}, + }, + }, + }} + ) + + utx, err := builder.NewBaseTx(outputsToMove) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 2) + require.Len(outs, 2) + + expectedConsumed := testCtx.CreateSubnetTxFee() + outputsToMove[0].Out.Amount() + consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) + require.Equal(outputsToMove[0], outs[1]) +} + +func TestAddSubnetValidatorTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + + // data to build the transaction + subnetValidator = &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(time.Now().Add(time.Hour).Unix()), + }, + Subnet: subnetID, + } + ) + + // build the transaction + utx, err := builder.NewAddSubnetValidatorTx(subnetValidator) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 2) + require.Len(outs, 1) + + expectedConsumed := testCtx.AddSubnetValidatorFee() + consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestRemoveSubnetValidatorTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + ) + + // build the transaction + utx, err := builder.NewRemoveSubnetValidatorTx( + ids.GenerateTestNodeID(), + subnetID, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 1) + require.Len(outs, 1) + + expectedConsumed := testCtx.BaseTxFee() + consumed := ins[0].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestCreateChainTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + + // data to build the transaction + genesisBytes = []byte{'a', 'b', 'c'} + vmID = ids.GenerateTestID() + fxIDs = []ids.ID{ids.GenerateTestID()} + chainName = "dummyChain" + ) + + // build the transaction + utx, err := builder.NewCreateChainTx( + subnetID, + genesisBytes, + vmID, + fxIDs, + chainName, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 1) + require.Len(outs, 1) + + expectedConsumed := testCtx.CreateBlockchainTxFee() + consumed := ins[0].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestCreateSubnetTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + ) + + // build the transaction + utx, err := builder.NewCreateSubnetTx(subnetOwner) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 1) + require.Len(outs, 1) + + expectedConsumed := testCtx.CreateSubnetTxFee() + consumed := ins[0].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestTransferSubnetOwnershipTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + ) + + // build the transaction + utx, err := builder.NewTransferSubnetOwnershipTx( + subnetID, + subnetOwner, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 1) + require.Len(outs, 1) + + expectedConsumed := testCtx.BaseTxFee() + consumed := ins[0].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestImportTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + sourceChainID = ids.GenerateTestID() + importedUTXOs = utxos[:1] + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + sourceChainID: importedUTXOs, + }) + + backend = NewBackend(testCtx, chainUTXOs, nil) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr), backend) + + // data to build the transaction + importKey = testKeys[0] + importTo = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + importKey.Address(), + }, + } + ) + + // build the transaction + utx, err := builder.NewImportTx( + sourceChainID, + importTo, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + importedIns := utx.ImportedInputs + require.Empty(ins) // we spend the imported input (at least partially) + require.Len(importedIns, 1) + require.Len(outs, 1) + + expectedConsumed := testCtx.BaseTxFee() + consumed := importedIns[0].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestExportTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = NewBackend(testCtx, chainUTXOs, nil) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr), backend) + + // data to build the transaction + subnetID = ids.GenerateTestID() + exportedOutputs = []*avax.TransferableOutput{{ + Asset: avax.Asset{ID: avaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 7 * units.Avax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{utxoAddr}, + }, + }, + }} + ) + + // build the transaction + utx, err := builder.NewExportTx( + subnetID, + exportedOutputs, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 2) + require.Len(outs, 1) + + expectedConsumed := testCtx.BaseTxFee() + exportedOutputs[0].Out.Amount() + consumed := ins[0].In.Amount() + ins[1].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) + require.Equal(utx.ExportedOutputs, exportedOutputs) +} + +func TestTransformSubnetTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + + subnetID = ids.GenerateTestID() + subnetAuthKey = testKeys[0] + subnetAuthAddr = subnetAuthKey.Address() + subnetOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{subnetAuthAddr}, + } + subnets = map[ids.ID]*txs.Tx{ + subnetID: { + Unsigned: &txs.CreateSubnetTx{ + Owner: subnetOwner, + }, + }, + } + + backend = NewBackend(testCtx, chainUTXOs, subnets) + + // builder + utxoAddr = utxosKey.Address() + builder = NewBuilder(set.Of(utxoAddr, subnetAuthAddr), backend) + + // data to build the transaction + initialSupply = 40 * units.MegaAvax + maxSupply = 100 * units.MegaAvax + ) + + // build the transaction + utx, err := builder.NewTransformSubnetTx( + subnetID, + subnetAssetID, + initialSupply, // initial supply + maxSupply, // max supply + reward.PercentDenominator, // min consumption rate + reward.PercentDenominator, // max consumption rate + 1, // min validator stake + 100*units.MegaAvax, // max validator stake + time.Second, // min stake duration + 365*24*time.Hour, // max stake duration + 0, // min delegation fee + 1, // min delegator stake + 5, // max validator weight factor + .80*reward.PercentDenominator, // uptime requirement + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + outs := utx.Outs + require.Len(ins, 2) + require.Len(outs, 2) + + expectedConsumedSubnetAsset := maxSupply - initialSupply + consumedSubnetAsset := ins[0].In.Amount() - outs[1].Out.Amount() + require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) + expectedConsumed := testCtx.TransformSubnetTxFee() + consumed := ins[1].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestAddPermissionlessValidatorTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = NewBackend(testCtx, chainUTXOs, nil) + + // builder + utxoAddr = utxosKey.Address() + rewardKey = testKeys[0] + rewardAddr = rewardKey.Address() + builder = NewBuilder(set.Of(utxoAddr, rewardAddr), backend) + + // data to build the transaction + validationRewardsOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + rewardAddr, + }, + } + delegationRewardsOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + rewardAddr, + }, + } + ) + + sk, err := bls.NewSecretKey() + require.NoError(err) + + // build the transaction + utx, err := builder.NewAddPermissionlessValidatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(time.Now().Add(time.Hour).Unix()), + Wght: 2 * units.Avax, + }, + Subnet: constants.PrimaryNetworkID, + }, + signer.NewProofOfPossession(sk), + avaxAssetID, + validationRewardsOwner, + delegationRewardsOwner, + reward.PercentDenominator, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + staked := utx.StakeOuts + outs := utx.Outs + require.Len(ins, 4) + require.Len(staked, 2) + require.Len(outs, 2) + + expectedConsumedSubnetAsset := utx.Validator.Weight() + consumedSubnetAsset := staked[0].Out.Amount() + staked[1].Out.Amount() + require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) + expectedConsumed := testCtx.AddPrimaryNetworkValidatorFee() + consumed := ins[1].In.Amount() + ins[3].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func TestAddPermissionlessDelegatorTx(t *testing.T) { + var ( + require = require.New(t) + + // backend + utxosKey = testKeys[1] + utxos = makeTestUTXOs(utxosKey) + chainUTXOs = newChainUTXOs(require, map[ids.ID][]*avax.UTXO{ + constants.PlatformChainID: utxos, + }) + backend = NewBackend(testCtx, chainUTXOs, nil) + + // builder + utxoAddr = utxosKey.Address() + rewardKey = testKeys[0] + rewardAddr = rewardKey.Address() + builder = NewBuilder(set.Of(utxoAddr, rewardAddr), backend) + + // data to build the transaction + rewardsOwner = &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{ + rewardAddr, + }, + } + ) + + // build the transaction + utx, err := builder.NewAddPermissionlessDelegatorTx( + &txs.SubnetValidator{ + Validator: txs.Validator{ + NodeID: ids.GenerateTestNodeID(), + End: uint64(time.Now().Add(time.Hour).Unix()), + Wght: 2 * units.Avax, + }, + Subnet: constants.PrimaryNetworkID, + }, + avaxAssetID, + rewardsOwner, + ) + require.NoError(err) + + // check UTXOs selection and fee financing + ins := utx.Ins + staked := utx.StakeOuts + outs := utx.Outs + require.Len(ins, 4) + require.Len(staked, 2) + require.Len(outs, 2) + + expectedConsumedSubnetAsset := utx.Validator.Weight() + consumedSubnetAsset := staked[0].Out.Amount() + staked[1].Out.Amount() + require.Equal(expectedConsumedSubnetAsset, consumedSubnetAsset) + expectedConsumed := testCtx.AddPrimaryNetworkDelegatorFee() + consumed := ins[1].In.Amount() + ins[3].In.Amount() - outs[0].Out.Amount() + require.Equal(expectedConsumed, consumed) +} + +func makeTestUTXOs(utxosKey *secp256k1.PrivateKey) []*avax.UTXO { + // Note: we avoid ids.GenerateTestNodeID here to make sure that UTXO IDs won't change + // run by run. This simplifies checking what utxos are included in the built txs. + const utxosOffset uint64 = 2024 + + utxosAddr := utxosKey.Address() + return []*avax.UTXO{ + { // a small UTXO first, which should not be enough to pay fees + UTXOID: avax.UTXOID{ + TxID: ids.Empty.Prefix(utxosOffset), + OutputIndex: uint32(utxosOffset), + }, + Asset: avax.Asset{ID: avaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 2 * units.MilliAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Addrs: []ids.ShortID{utxosAddr}, + Threshold: 1, + }, + }, + }, + { // a locked, small UTXO + UTXOID: avax.UTXOID{ + TxID: ids.Empty.Prefix(utxosOffset + 1), + OutputIndex: uint32(utxosOffset + 1), + }, + Asset: avax.Asset{ID: avaxAssetID}, + Out: &stakeable.LockOut{ + Locktime: uint64(time.Now().Add(time.Hour).Unix()), + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 3 * units.MilliAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{utxosAddr}, + }, + }, + }, + }, + { // a subnetAssetID denominated UTXO + UTXOID: avax.UTXOID{ + TxID: ids.Empty.Prefix(utxosOffset + 2), + OutputIndex: uint32(utxosOffset + 2), + }, + Asset: avax.Asset{ID: subnetAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 99 * units.MegaAvax, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Addrs: []ids.ShortID{utxosAddr}, + Threshold: 1, + }, + }, + }, + { // a locked, large UTXO + UTXOID: avax.UTXOID{ + TxID: ids.Empty.Prefix(utxosOffset + 3), + OutputIndex: uint32(utxosOffset + 3), + }, + Asset: avax.Asset{ID: avaxAssetID}, + Out: &stakeable.LockOut{ + Locktime: uint64(time.Now().Add(time.Hour).Unix()), + TransferableOut: &secp256k1fx.TransferOutput{ + Amt: 88 * units.Avax, + OutputOwners: secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{utxosAddr}, + }, + }, + }, + }, + { // a large UTXO last, which should be enough to pay any fee by itself + UTXOID: avax.UTXOID{ + TxID: ids.Empty.Prefix(utxosOffset + 4), + OutputIndex: uint32(utxosOffset + 4), + }, + Asset: avax.Asset{ID: avaxAssetID}, + Out: &secp256k1fx.TransferOutput{ + Amt: 9 * units.Avax, + OutputOwners: secp256k1fx.OutputOwners{ + Locktime: 0, + Addrs: []ids.ShortID{utxosAddr}, + Threshold: 1, + }, + }, + }, + } +} + +func newChainUTXOs(require *require.Assertions, utxoSets map[ids.ID][]*avax.UTXO) common.ChainUTXOs { + globalUTXOs := common.NewUTXOs() + for subnetID, utxos := range utxoSets { + for _, utxo := range utxos { + require.NoError( + globalUTXOs.AddUTXO(stdcontext.Background(), subnetID, constants.PlatformChainID, utxo), + ) + } + } + return &deterministicChainUTXOs{ + ChainUTXOs: common.NewChainUTXOs(constants.PlatformChainID, globalUTXOs), + } +} + +type deterministicChainUTXOs struct { + common.ChainUTXOs +} + +func (c *deterministicChainUTXOs) UTXOs(ctx stdcontext.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) { + utxos, err := c.ChainUTXOs.UTXOs(ctx, sourceChainID) + if err != nil { + return nil, err + } + + slices.SortFunc(utxos, func(a, b *avax.UTXO) int { + return a.Compare(&b.UTXOID) + }) + return utxos, nil +} diff --git a/wallet/chain/p/signer.go b/wallet/chain/p/signer.go index 8074a8a224a..bedbbdbf562 100644 --- a/wallet/chain/p/signer.go +++ b/wallet/chain/p/signer.go @@ -16,7 +16,14 @@ import ( var _ Signer = (*txSigner)(nil) type Signer interface { - SignUnsigned(ctx stdcontext.Context, tx txs.UnsignedTx) (*txs.Tx, error) + // Sign adds as many missing signatures as possible to the provided + // transaction. + // + // If there are already some signatures on the transaction, those signatures + // will not be removed. + // + // If the signer doesn't have the ability to provide a required signature, + // the signature slot will be skipped without reporting an error. Sign(ctx stdcontext.Context, tx *txs.Tx) error } @@ -37,11 +44,6 @@ func NewSigner(kc keychain.Keychain, backend SignerBackend) Signer { } } -func (s *txSigner) SignUnsigned(ctx stdcontext.Context, utx txs.UnsignedTx) (*txs.Tx, error) { - tx := &txs.Tx{Unsigned: utx} - return tx, s.Sign(ctx, tx) -} - func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx) error { return tx.Unsigned.Visit(&signerVisitor{ kc: s.kc, @@ -50,3 +52,12 @@ func (s *txSigner) Sign(ctx stdcontext.Context, tx *txs.Tx) error { tx: tx, }) } + +func SignUnsigned( + ctx stdcontext.Context, + signer Signer, + utx txs.UnsignedTx, +) (*txs.Tx, error) { + tx := &txs.Tx{Unsigned: utx} + return tx, signer.Sign(ctx, tx) +} diff --git a/wallet/chain/p/wallet.go b/wallet/chain/p/wallet.go index c5b9ecb0604..44cc7e2a4da 100644 --- a/wallet/chain/p/wallet.go +++ b/wallet/chain/p/wallet.go @@ -495,7 +495,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := w.signer.SignUnsigned(ctx, utx) + tx, err := SignUnsigned(ctx, w.signer, utx) if err != nil { return nil, err } diff --git a/wallet/chain/x/signer.go b/wallet/chain/x/signer.go index 4f2fe3c3056..9bc8734e46c 100644 --- a/wallet/chain/x/signer.go +++ b/wallet/chain/x/signer.go @@ -15,7 +15,14 @@ import ( var _ Signer = (*signer)(nil) type Signer interface { - SignUnsigned(ctx stdcontext.Context, tx txs.UnsignedTx) (*txs.Tx, error) + // Sign adds as many missing signatures as possible to the provided + // transaction. + // + // If there are already some signatures on the transaction, those signatures + // will not be removed. + // + // If the signer doesn't have the ability to provide a required signature, + // the signature slot will be skipped without reporting an error. Sign(ctx stdcontext.Context, tx *txs.Tx) error } @@ -35,11 +42,6 @@ func NewSigner(kc keychain.Keychain, backend SignerBackend) Signer { } } -func (s *signer) SignUnsigned(ctx stdcontext.Context, utx txs.UnsignedTx) (*txs.Tx, error) { - tx := &txs.Tx{Unsigned: utx} - return tx, s.Sign(ctx, tx) -} - func (s *signer) Sign(ctx stdcontext.Context, tx *txs.Tx) error { return tx.Unsigned.Visit(&signerVisitor{ kc: s.kc, @@ -48,3 +50,12 @@ func (s *signer) Sign(ctx stdcontext.Context, tx *txs.Tx) error { tx: tx, }) } + +func SignUnsigned( + ctx stdcontext.Context, + signer Signer, + utx txs.UnsignedTx, +) (*txs.Tx, error) { + tx := &txs.Tx{Unsigned: utx} + return tx, signer.Sign(ctx, tx) +} diff --git a/wallet/chain/x/wallet.go b/wallet/chain/x/wallet.go index 75b3914e199..13491a24134 100644 --- a/wallet/chain/x/wallet.go +++ b/wallet/chain/x/wallet.go @@ -286,7 +286,7 @@ func (w *wallet) IssueUnsignedTx( ) (*txs.Tx, error) { ops := common.NewOptions(options) ctx := ops.Context() - tx, err := w.signer.SignUnsigned(ctx, utx) + tx, err := SignUnsigned(ctx, w.signer, utx) if err != nil { return nil, err } diff --git a/wallet/subnet/primary/api.go b/wallet/subnet/primary/api.go index 350581bfa8b..3c30b60d81c 100644 --- a/wallet/subnet/primary/api.go +++ b/wallet/subnet/primary/api.go @@ -9,7 +9,6 @@ import ( "github.com/ava-labs/coreth/ethclient" "github.com/ava-labs/coreth/plugin/evm" - "github.com/ethereum/go-ethereum/common" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/codec" @@ -24,6 +23,9 @@ import ( "github.com/ava-labs/avalanchego/wallet/chain/c" "github.com/ava-labs/avalanchego/wallet/chain/p" "github.com/ava-labs/avalanchego/wallet/chain/x" + + walletcommon "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" + ethcommon "github.com/ethereum/go-ethereum/common" ) const ( @@ -60,7 +62,7 @@ type AVAXState struct { XCTX x.Context CClient evm.Client CCTX c.Context - UTXOs UTXOs + UTXOs walletcommon.UTXOs } func FetchState( @@ -91,7 +93,7 @@ func FetchState( return nil, err } - utxos := NewUTXOs() + utxos := walletcommon.NewUTXOs() addrList := addrs.List() chains := []struct { id ids.ID @@ -143,13 +145,13 @@ func FetchState( type EthState struct { Client ethclient.Client - Accounts map[common.Address]*c.Account + Accounts map[ethcommon.Address]*c.Account } func FetchEthState( ctx context.Context, uri string, - addrs set.Set[common.Address], + addrs set.Set[ethcommon.Address], ) (*EthState, error) { path := fmt.Sprintf( "%s/ext/%s/C/rpc", @@ -161,7 +163,7 @@ func FetchEthState( return nil, err } - accounts := make(map[common.Address]*c.Account, addrs.Len()) + accounts := make(map[ethcommon.Address]*c.Account, addrs.Len()) for addr := range addrs { balance, err := client.BalanceAt(ctx, addr, nil) if err != nil { @@ -188,7 +190,7 @@ func FetchEthState( // expires, then the returned error will be immediately reported. func AddAllUTXOs( ctx context.Context, - utxos UTXOs, + utxos walletcommon.UTXOs, client UTXOClient, codec codec.Manager, sourceChainID ids.ID, diff --git a/wallet/subnet/primary/common/utxos.go b/wallet/subnet/primary/common/utxos.go index 23762a0dd5d..6c96e249b37 100644 --- a/wallet/subnet/primary/common/utxos.go +++ b/wallet/subnet/primary/common/utxos.go @@ -5,11 +5,28 @@ package common import ( "context" + "sync" + "golang.org/x/exp/maps" + + "github.com/ava-labs/avalanchego/database" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/vms/components/avax" ) +var ( + _ UTXOs = (*utxos)(nil) + _ ChainUTXOs = (*chainUTXOs)(nil) +) + +type UTXOs interface { + AddUTXO(ctx context.Context, sourceChainID, destinationChainID ids.ID, utxo *avax.UTXO) error + RemoveUTXO(ctx context.Context, sourceChainID, destinationChainID, utxoID ids.ID) error + + UTXOs(ctx context.Context, sourceChainID, destinationChainID ids.ID) ([]*avax.UTXO, error) + GetUTXO(ctx context.Context, sourceChainID, destinationChainID, utxoID ids.ID) (*avax.UTXO, error) +} + type ChainUTXOs interface { AddUTXO(ctx context.Context, destinationChainID ids.ID, utxo *avax.UTXO) error RemoveUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) error @@ -17,3 +34,110 @@ type ChainUTXOs interface { UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) GetUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) (*avax.UTXO, error) } + +func NewUTXOs() UTXOs { + return &utxos{ + sourceToDestToUTXOIDToUTXO: make(map[ids.ID]map[ids.ID]map[ids.ID]*avax.UTXO), + } +} + +func NewChainUTXOs(chainID ids.ID, utxos UTXOs) ChainUTXOs { + return &chainUTXOs{ + utxos: utxos, + chainID: chainID, + } +} + +type utxos struct { + lock sync.RWMutex + // sourceChainID -> destinationChainID -> utxoID -> utxo + sourceToDestToUTXOIDToUTXO map[ids.ID]map[ids.ID]map[ids.ID]*avax.UTXO +} + +func (u *utxos) AddUTXO(_ context.Context, sourceChainID, destinationChainID ids.ID, utxo *avax.UTXO) error { + u.lock.Lock() + defer u.lock.Unlock() + + destToUTXOIDToUTXO, ok := u.sourceToDestToUTXOIDToUTXO[sourceChainID] + if !ok { + destToUTXOIDToUTXO = make(map[ids.ID]map[ids.ID]*avax.UTXO) + u.sourceToDestToUTXOIDToUTXO[sourceChainID] = destToUTXOIDToUTXO + } + + utxoIDToUTXO, ok := destToUTXOIDToUTXO[destinationChainID] + if !ok { + utxoIDToUTXO = make(map[ids.ID]*avax.UTXO) + destToUTXOIDToUTXO[destinationChainID] = utxoIDToUTXO + } + + utxoIDToUTXO[utxo.InputID()] = utxo + return nil +} + +func (u *utxos) RemoveUTXO(_ context.Context, sourceChainID, destinationChainID, utxoID ids.ID) error { + u.lock.Lock() + defer u.lock.Unlock() + + destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] + utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] + _, ok := utxoIDToUTXO[utxoID] + if !ok { + return nil + } + + delete(utxoIDToUTXO, utxoID) + if len(utxoIDToUTXO) != 0 { + return nil + } + + delete(destToUTXOIDToUTXO, destinationChainID) + if len(destToUTXOIDToUTXO) != 0 { + return nil + } + + delete(u.sourceToDestToUTXOIDToUTXO, sourceChainID) + return nil +} + +func (u *utxos) UTXOs(_ context.Context, sourceChainID, destinationChainID ids.ID) ([]*avax.UTXO, error) { + u.lock.RLock() + defer u.lock.RUnlock() + + destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] + utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] + return maps.Values(utxoIDToUTXO), nil +} + +func (u *utxos) GetUTXO(_ context.Context, sourceChainID, destinationChainID, utxoID ids.ID) (*avax.UTXO, error) { + u.lock.RLock() + defer u.lock.RUnlock() + + destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] + utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] + utxo, ok := utxoIDToUTXO[utxoID] + if !ok { + return nil, database.ErrNotFound + } + return utxo, nil +} + +type chainUTXOs struct { + utxos UTXOs + chainID ids.ID +} + +func (c *chainUTXOs) AddUTXO(ctx context.Context, destinationChainID ids.ID, utxo *avax.UTXO) error { + return c.utxos.AddUTXO(ctx, c.chainID, destinationChainID, utxo) +} + +func (c *chainUTXOs) RemoveUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) error { + return c.utxos.RemoveUTXO(ctx, sourceChainID, c.chainID, utxoID) +} + +func (c *chainUTXOs) UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) { + return c.utxos.UTXOs(ctx, sourceChainID, c.chainID) +} + +func (c *chainUTXOs) GetUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) (*avax.UTXO, error) { + return c.utxos.GetUTXO(ctx, sourceChainID, c.chainID, utxoID) +} diff --git a/wallet/subnet/primary/examples/get-p-chain-balance/main.go b/wallet/subnet/primary/examples/get-p-chain-balance/main.go index 336c25d2dd1..08f2cd538c2 100644 --- a/wallet/subnet/primary/examples/get-p-chain-balance/main.go +++ b/wallet/subnet/primary/examples/get-p-chain-balance/main.go @@ -13,6 +13,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/wallet/chain/p" "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" ) func main() { @@ -35,7 +36,7 @@ func main() { } log.Printf("fetched state of %s in %s\n", addrStr, time.Since(fetchStartTime)) - pUTXOs := primary.NewChainUTXOs(constants.PlatformChainID, state.UTXOs) + pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, state.UTXOs) pBackend := p.NewBackend(state.PCTX, pUTXOs, nil) pBuilder := p.NewBuilder(addresses, pBackend) diff --git a/wallet/subnet/primary/examples/get-x-chain-balance/main.go b/wallet/subnet/primary/examples/get-x-chain-balance/main.go index c43d4c9dd22..9895546879e 100644 --- a/wallet/subnet/primary/examples/get-x-chain-balance/main.go +++ b/wallet/subnet/primary/examples/get-x-chain-balance/main.go @@ -12,6 +12,7 @@ import ( "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/wallet/chain/x" "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" ) func main() { @@ -36,7 +37,7 @@ func main() { xChainID := state.XCTX.BlockchainID() - xUTXOs := primary.NewChainUTXOs(xChainID, state.UTXOs) + xUTXOs := common.NewChainUTXOs(xChainID, state.UTXOs) xBackend := x.NewBackend(state.XCTX, xUTXOs) xBuilder := x.NewBuilder(addresses, xBackend) diff --git a/wallet/subnet/primary/utxos.go b/wallet/subnet/primary/utxos.go deleted file mode 100644 index 71f7629856e..00000000000 --- a/wallet/subnet/primary/utxos.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package primary - -import ( - "context" - "sync" - - "golang.org/x/exp/maps" - - "github.com/ava-labs/avalanchego/database" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" -) - -var ( - _ UTXOs = (*utxos)(nil) - _ common.ChainUTXOs = (*chainUTXOs)(nil) -) - -type UTXOs interface { - AddUTXO(ctx context.Context, sourceChainID, destinationChainID ids.ID, utxo *avax.UTXO) error - RemoveUTXO(ctx context.Context, sourceChainID, destinationChainID, utxoID ids.ID) error - - UTXOs(ctx context.Context, sourceChainID, destinationChainID ids.ID) ([]*avax.UTXO, error) - GetUTXO(ctx context.Context, sourceChainID, destinationChainID, utxoID ids.ID) (*avax.UTXO, error) -} - -func NewUTXOs() UTXOs { - return &utxos{ - sourceToDestToUTXOIDToUTXO: make(map[ids.ID]map[ids.ID]map[ids.ID]*avax.UTXO), - } -} - -func NewChainUTXOs(chainID ids.ID, utxos UTXOs) common.ChainUTXOs { - return &chainUTXOs{ - utxos: utxos, - chainID: chainID, - } -} - -type utxos struct { - lock sync.RWMutex - // sourceChainID -> destinationChainID -> utxoID -> utxo - sourceToDestToUTXOIDToUTXO map[ids.ID]map[ids.ID]map[ids.ID]*avax.UTXO -} - -func (u *utxos) AddUTXO(_ context.Context, sourceChainID, destinationChainID ids.ID, utxo *avax.UTXO) error { - u.lock.Lock() - defer u.lock.Unlock() - - destToUTXOIDToUTXO, ok := u.sourceToDestToUTXOIDToUTXO[sourceChainID] - if !ok { - destToUTXOIDToUTXO = make(map[ids.ID]map[ids.ID]*avax.UTXO) - u.sourceToDestToUTXOIDToUTXO[sourceChainID] = destToUTXOIDToUTXO - } - - utxoIDToUTXO, ok := destToUTXOIDToUTXO[destinationChainID] - if !ok { - utxoIDToUTXO = make(map[ids.ID]*avax.UTXO) - destToUTXOIDToUTXO[destinationChainID] = utxoIDToUTXO - } - - utxoIDToUTXO[utxo.InputID()] = utxo - return nil -} - -func (u *utxos) RemoveUTXO(_ context.Context, sourceChainID, destinationChainID, utxoID ids.ID) error { - u.lock.Lock() - defer u.lock.Unlock() - - destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] - utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] - _, ok := utxoIDToUTXO[utxoID] - if !ok { - return nil - } - - delete(utxoIDToUTXO, utxoID) - if len(utxoIDToUTXO) != 0 { - return nil - } - - delete(destToUTXOIDToUTXO, destinationChainID) - if len(destToUTXOIDToUTXO) != 0 { - return nil - } - - delete(u.sourceToDestToUTXOIDToUTXO, sourceChainID) - return nil -} - -func (u *utxos) UTXOs(_ context.Context, sourceChainID, destinationChainID ids.ID) ([]*avax.UTXO, error) { - u.lock.RLock() - defer u.lock.RUnlock() - - destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] - utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] - return maps.Values(utxoIDToUTXO), nil -} - -func (u *utxos) GetUTXO(_ context.Context, sourceChainID, destinationChainID, utxoID ids.ID) (*avax.UTXO, error) { - u.lock.RLock() - defer u.lock.RUnlock() - - destToUTXOIDToUTXO := u.sourceToDestToUTXOIDToUTXO[sourceChainID] - utxoIDToUTXO := destToUTXOIDToUTXO[destinationChainID] - utxo, ok := utxoIDToUTXO[utxoID] - if !ok { - return nil, database.ErrNotFound - } - return utxo, nil -} - -type chainUTXOs struct { - utxos UTXOs - chainID ids.ID -} - -func (c *chainUTXOs) AddUTXO(ctx context.Context, destinationChainID ids.ID, utxo *avax.UTXO) error { - return c.utxos.AddUTXO(ctx, c.chainID, destinationChainID, utxo) -} - -func (c *chainUTXOs) RemoveUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) error { - return c.utxos.RemoveUTXO(ctx, sourceChainID, c.chainID, utxoID) -} - -func (c *chainUTXOs) UTXOs(ctx context.Context, sourceChainID ids.ID) ([]*avax.UTXO, error) { - return c.utxos.UTXOs(ctx, sourceChainID, c.chainID) -} - -func (c *chainUTXOs) GetUTXO(ctx context.Context, sourceChainID, utxoID ids.ID) (*avax.UTXO, error) { - return c.utxos.GetUTXO(ctx, sourceChainID, c.chainID, utxoID) -} diff --git a/wallet/subnet/primary/wallet.go b/wallet/subnet/primary/wallet.go index 3bb3e996568..9aabf651cff 100644 --- a/wallet/subnet/primary/wallet.go +++ b/wallet/subnet/primary/wallet.go @@ -116,19 +116,19 @@ func MakeWallet(ctx context.Context, config *WalletConfig) (Wallet, error) { pChainTxs[txID] = tx } - pUTXOs := NewChainUTXOs(constants.PlatformChainID, avaxState.UTXOs) + pUTXOs := common.NewChainUTXOs(constants.PlatformChainID, avaxState.UTXOs) pBackend := p.NewBackend(avaxState.PCTX, pUTXOs, pChainTxs) pBuilder := p.NewBuilder(avaxAddrs, pBackend) pSigner := p.NewSigner(config.AVAXKeychain, pBackend) xChainID := avaxState.XCTX.BlockchainID() - xUTXOs := NewChainUTXOs(xChainID, avaxState.UTXOs) + xUTXOs := common.NewChainUTXOs(xChainID, avaxState.UTXOs) xBackend := x.NewBackend(avaxState.XCTX, xUTXOs) xBuilder := x.NewBuilder(avaxAddrs, xBackend) xSigner := x.NewSigner(config.AVAXKeychain, xBackend) cChainID := avaxState.CCTX.BlockchainID() - cUTXOs := NewChainUTXOs(cChainID, avaxState.UTXOs) + cUTXOs := common.NewChainUTXOs(cChainID, avaxState.UTXOs) cBackend := c.NewBackend(avaxState.CCTX, cUTXOs, ethState.Accounts) cBuilder := c.NewBuilder(avaxAddrs, ethAddrs, cBackend) cSigner := c.NewSigner(config.AVAXKeychain, config.EthKeychain, cBackend) diff --git a/x/merkledb/view.go b/x/merkledb/view.go index 674ed7382f4..441cc37166d 100644 --- a/x/merkledb/view.go +++ b/x/merkledb/view.go @@ -793,7 +793,7 @@ func (v *view) recordKeyChange(key Key, after *node, hadValue bool, newNode bool } before, err := v.getParentTrie().getEditableNode(key, hadValue) - if err != nil && !errors.Is(err, database.ErrNotFound) { + if err != nil { return err } v.changes.nodes[key] = &change[*node]{