From fce9e900b5b4f93b39f2e99962163f46f8dae965 Mon Sep 17 00:00:00 2001 From: lklimek <842586+lklimek@users.noreply.github.com> Date: Fri, 9 Feb 2024 04:45:58 +0100 Subject: [PATCH] fix: race condition when adding new channel to NodeInfo (#735) * fix: race condition when adding new channel to NodeInfo * chore: fix missing nodeInfo.Channels initialization * fix(sync): concurrent slice marshal/unmarshal json * fix: json marshal nodeInfo channels fails --- internal/libs/sync/concurrent_slice.go | 116 ++++++++ internal/libs/sync/concurrent_slice_test.go | 96 +++++++ internal/p2p/p2p_test.go | 5 +- internal/p2p/p2ptest/network.go | 2 + internal/p2p/router.go | 10 +- internal/p2p/router_test.go | 7 +- internal/p2p/transport_test.go | 13 +- node/setup.go | 42 +-- proto/tendermint/p2p/types.pb.go | 290 ++++++++++++-------- proto/tendermint/p2p/types.proto | 2 +- types/node_info.go | 45 +-- types/node_info_test.go | 36 ++- version/version.go | 2 +- 13 files changed, 482 insertions(+), 184 deletions(-) create mode 100644 internal/libs/sync/concurrent_slice.go create mode 100644 internal/libs/sync/concurrent_slice_test.go diff --git a/internal/libs/sync/concurrent_slice.go b/internal/libs/sync/concurrent_slice.go new file mode 100644 index 0000000000..48d674eb78 --- /dev/null +++ b/internal/libs/sync/concurrent_slice.go @@ -0,0 +1,116 @@ +package sync + +import ( + "encoding/json" + "sync" +) + +// ConcurrentSlice is a thread-safe slice. +// +// It is safe to use from multiple goroutines without additional locking. +// It should be referenced by pointer. +// +// Initialize using NewConcurrentSlice(). +type ConcurrentSlice[T any] struct { + mtx sync.RWMutex + items []T +} + +// NewConcurrentSlice creates a new thread-safe slice. +func NewConcurrentSlice[T any](initial ...T) *ConcurrentSlice[T] { + return &ConcurrentSlice[T]{ + items: initial, + } +} + +// Append adds an element to the slice +func (s *ConcurrentSlice[T]) Append(val ...T) { + s.mtx.Lock() + defer s.mtx.Unlock() + + s.items = append(s.items, val...) +} + +// Reset removes all elements from the slice +func (s *ConcurrentSlice[T]) Reset() { + s.mtx.Lock() + defer s.mtx.Unlock() + + s.items = []T{} +} + +// Get returns the value at the given index +func (s *ConcurrentSlice[T]) Get(index int) T { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return s.items[index] +} + +// Set updates the value at the given index. +// If the index is greater than the length of the slice, it panics. +// If the index is equal to the length of the slice, the value is appended. +// Otherwise, the value at the index is updated. +func (s *ConcurrentSlice[T]) Set(index int, val T) { + s.mtx.Lock() + defer s.mtx.Unlock() + + if index > len(s.items) { + panic("index out of range") + } else if index == len(s.items) { + s.items = append(s.items, val) + return + } + + s.items[index] = val +} + +// ToSlice returns a copy of the underlying slice +func (s *ConcurrentSlice[T]) ToSlice() []T { + s.mtx.RLock() + defer s.mtx.RUnlock() + + slice := make([]T, len(s.items)) + copy(slice, s.items) + return slice +} + +// Len returns the length of the slice +func (s *ConcurrentSlice[T]) Len() int { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return len(s.items) +} + +// Copy returns a new deep copy of concurrentSlice with the same elements +func (s *ConcurrentSlice[T]) Copy() ConcurrentSlice[T] { + s.mtx.RLock() + defer s.mtx.RUnlock() + + return ConcurrentSlice[T]{ + items: s.ToSlice(), + } +} + +// MarshalJSON implements the json.Marshaler interface. +func (cs *ConcurrentSlice[T]) MarshalJSON() ([]byte, error) { + cs.mtx.RLock() + defer cs.mtx.RUnlock() + + return json.Marshal(cs.items) +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (cs *ConcurrentSlice[T]) UnmarshalJSON(data []byte) error { + var items []T + if err := json.Unmarshal(data, &items); err != nil { + return err + } + + cs.mtx.Lock() + defer cs.mtx.Unlock() + + cs.items = items + return nil +} diff --git a/internal/libs/sync/concurrent_slice_test.go b/internal/libs/sync/concurrent_slice_test.go new file mode 100644 index 0000000000..122f3a1a28 --- /dev/null +++ b/internal/libs/sync/concurrent_slice_test.go @@ -0,0 +1,96 @@ +package sync + +import ( + "encoding/json" + "sync" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConcurrentSlice(t *testing.T) { + s := NewConcurrentSlice[int](1, 2, 3) + + // Test Append + s.Append(4) + if s.Len() != 4 { + t.Errorf("Expected length of slice to be 4, got %d", s.Len()) + } + + // Test Get + if s.Get(3) != 4 { + t.Errorf("Expected element at index 3 to be 4, got %d", s.Get(3)) + } + + // Test Set + s.Set(1, 5) + + // Test ToSlice + slice := s.ToSlice() + if len(slice) != 4 || slice[3] != 4 || slice[1] != 5 { + t.Errorf("Expected ToSlice to return [1 5 3 4], got %v", slice) + } + + // Test Reset + s.Reset() + if s.Len() != 0 { + t.Errorf("Expected length of slice to be 0 after Reset, got %d", s.Len()) + } + + // Test Copy + s.Append(5) + copy := s.Copy() + if copy.Len() != 1 || copy.Get(0) != 5 { + t.Errorf("Expected Copy to return a new slice with [5], got %v", copy.ToSlice()) + } +} + +func TestConcurrentSlice_Concurrency(t *testing.T) { + s := NewConcurrentSlice[int]() + + var wg sync.WaitGroup + for i := 0; i < 100; i++ { + wg.Add(1) + go func(val int) { + defer wg.Done() + s.Append(val) + }(i) + } + + wg.Wait() + + assert.Equal(t, 100, s.Len()) + + if s.Len() != 100 { + t.Errorf("Expected length of slice to be 100, got %d", s.Len()) + } + + for i := 0; i < 100; i++ { + assert.Contains(t, s.ToSlice(), i) + } +} + +func TestConcurrentSlice_MarshalUnmarshalJSON(t *testing.T) { + type node struct { + Channels *ConcurrentSlice[uint16] + } + cs := NewConcurrentSlice[uint16](1, 2, 3) + + node1 := node{ + Channels: cs, + } + + // Marshal to JSON + data, err := json.Marshal(node1) + assert.NoError(t, err, "Failed to marshal concurrentSlice") + + // Unmarshal from JSON + node2 := node{ + // Channels: NewConcurrentSlice[uint16](), + } + + err = json.Unmarshal(data, &node2) + assert.NoError(t, err, "Failed to unmarshal concurrentSlice") + + assert.EqualValues(t, node1.Channels.ToSlice(), node2.Channels.ToSlice()) +} diff --git a/internal/p2p/p2p_test.go b/internal/p2p/p2p_test.go index 97a008d025..2d2c6c2c48 100644 --- a/internal/p2p/p2p_test.go +++ b/internal/p2p/p2p_test.go @@ -3,6 +3,7 @@ package p2p_test import ( "github.com/dashpay/tenderdash/crypto" "github.com/dashpay/tenderdash/crypto/ed25519" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" "github.com/dashpay/tenderdash/internal/p2p" "github.com/dashpay/tenderdash/types" ) @@ -25,7 +26,7 @@ var ( ListenAddr: "0.0.0.0:0", Network: "test", Moniker: string(selfID), - Channels: []byte{0x01, 0x02}, + Channels: tmsync.NewConcurrentSlice[uint16](0x01, 0x02), } peerKey crypto.PrivKey = ed25519.GenPrivKeyFromSecret([]byte{0x84, 0xd7, 0x01, 0xbf, 0x83, 0x20, 0x1c, 0xfe}) @@ -35,6 +36,6 @@ var ( ListenAddr: "0.0.0.0:0", Network: "test", Moniker: string(peerID), - Channels: []byte{0x01, 0x02}, + Channels: tmsync.NewConcurrentSlice[uint16](0x01, 0x02), } ) diff --git a/internal/p2p/p2ptest/network.go b/internal/p2p/p2ptest/network.go index faf4ed83c6..49d0d74722 100644 --- a/internal/p2p/p2ptest/network.go +++ b/internal/p2p/p2ptest/network.go @@ -12,6 +12,7 @@ import ( "github.com/dashpay/tenderdash/config" "github.com/dashpay/tenderdash/crypto" "github.com/dashpay/tenderdash/crypto/ed25519" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" "github.com/dashpay/tenderdash/internal/p2p" p2pclient "github.com/dashpay/tenderdash/internal/p2p/client" "github.com/dashpay/tenderdash/libs/log" @@ -272,6 +273,7 @@ func (n *Network) MakeNode(ctx context.Context, t *testing.T, proTxHash crypto.P ListenAddr: "0.0.0.0:0", // FIXME: We have to fake this for now. Moniker: string(nodeID), ProTxHash: proTxHash.Copy(), + Channels: tmsync.NewConcurrentSlice[uint16](), } transport := n.memoryNetwork.CreateTransport(nodeID) diff --git a/internal/p2p/router.go b/internal/p2p/router.go index 644fcb7592..2dac816c60 100644 --- a/internal/p2p/router.go +++ b/internal/p2p/router.go @@ -501,7 +501,7 @@ func (r *Router) openConnection(ctx context.Context, conn Connection) { return } - r.routePeer(ctx, peerInfo.NodeID, conn, toChannelIDs(peerInfo.Channels)) + r.routePeer(ctx, peerInfo.NodeID, conn, toChannelIDs(peerInfo.Channels.ToSlice())) } // dialPeers maintains outbound connections to peers by dialing them. @@ -589,7 +589,7 @@ func (r *Router) connectPeer(ctx context.Context, address NodeAddress) { } // routePeer (also) calls connection close - go r.routePeer(ctx, address.NodeID, conn, toChannelIDs(peerInfo.Channels)) + go r.routePeer(ctx, address.NodeID, conn, toChannelIDs(peerInfo.Channels.ToSlice())) } func (r *Router) getOrMakeQueue(peerID types.NodeID, channels ChannelIDSet) queue { @@ -943,9 +943,9 @@ func (cs ChannelIDSet) Contains(id ChannelID) bool { return ok } -func toChannelIDs(bytes []byte) ChannelIDSet { - c := make(map[ChannelID]struct{}, len(bytes)) - for _, b := range bytes { +func toChannelIDs(ids []uint16) ChannelIDSet { + c := make(map[ChannelID]struct{}, len(ids)) + for _, b := range ids { c[ChannelID(b)] = struct{}{} } return c diff --git a/internal/p2p/router_test.go b/internal/p2p/router_test.go index 37aa4e2c76..05fdf33aba 100644 --- a/internal/p2p/router_test.go +++ b/internal/p2p/router_test.go @@ -17,6 +17,7 @@ import ( dbm "github.com/tendermint/tm-db" "github.com/dashpay/tenderdash/crypto" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" "github.com/dashpay/tenderdash/internal/p2p" "github.com/dashpay/tenderdash/internal/p2p/mocks" "github.com/dashpay/tenderdash/internal/p2p/p2ptest" @@ -303,6 +304,7 @@ func TestRouter_AcceptPeers(t *testing.T) { ListenAddr: "0.0.0.0:0", Network: "other-network", Moniker: string(peerID), + Channels: tmsync.NewConcurrentSlice[uint16](), }, peerKey.PubKey(), false, @@ -504,6 +506,7 @@ func TestRouter_DialPeers(t *testing.T) { ListenAddr: "0.0.0.0:0", Network: "other-network", Moniker: string(peerID), + Channels: tmsync.NewConcurrentSlice[uint16](), }, peerKey.PubKey(), nil, @@ -766,7 +769,7 @@ func TestRouter_ChannelCompatability(t *testing.T) { ListenAddr: "0.0.0.0:0", Network: "test", Moniker: string(peerID), - Channels: []byte{0x03}, + Channels: tmsync.NewConcurrentSlice[uint16](0x03), } mockConnection := &mocks.Connection{} @@ -817,7 +820,7 @@ func TestRouter_DontSendOnInvalidChannel(t *testing.T) { ListenAddr: "0.0.0.0:0", Network: "test", Moniker: string(peerID), - Channels: []byte{0x02}, + Channels: tmsync.NewConcurrentSlice[uint16](0x02), } mockConnection := &mocks.Connection{} diff --git a/internal/p2p/transport_test.go b/internal/p2p/transport_test.go index d22dbb9e28..1d87c5179c 100644 --- a/internal/p2p/transport_test.go +++ b/internal/p2p/transport_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/require" "github.com/dashpay/tenderdash/crypto/ed25519" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" "github.com/dashpay/tenderdash/internal/p2p" - "github.com/dashpay/tenderdash/libs/bytes" "github.com/dashpay/tenderdash/types" ) @@ -283,7 +283,7 @@ func TestConnection_Handshake(t *testing.T) { ListenAddr: "listenaddr", Network: "network", Version: "1.2.3", - Channels: bytes.HexBytes([]byte{0xf0, 0x0f}), + Channels: tmsync.NewConcurrentSlice[uint16](0xf0, 0x0f), Moniker: "moniker", Other: types.NodeInfoOther{ TxIndex: "txindex", @@ -291,7 +291,10 @@ func TestConnection_Handshake(t *testing.T) { }, } bKey := ed25519.GenPrivKey() - bInfo := types.NodeInfo{NodeID: types.NodeIDFromPubKey(bKey.PubKey())} + bInfo := types.NodeInfo{ + NodeID: types.NodeIDFromPubKey(bKey.PubKey()), + Channels: tmsync.NewConcurrentSlice[uint16](), + } errCh := make(chan error, 1) go func() { @@ -641,13 +644,13 @@ func dialAcceptHandshake(ctx context.Context, t *testing.T, a, b p2p.Transport) errCh := make(chan error, 1) go func() { privKey := ed25519.GenPrivKey() - nodeInfo := types.NodeInfo{NodeID: types.NodeIDFromPubKey(privKey.PubKey())} + nodeInfo := types.NodeInfo{NodeID: types.NodeIDFromPubKey(privKey.PubKey()), Channels: tmsync.NewConcurrentSlice[uint16]()} _, _, err := ba.Handshake(ctx, 0, nodeInfo, privKey) errCh <- err }() privKey := ed25519.GenPrivKey() - nodeInfo := types.NodeInfo{NodeID: types.NodeIDFromPubKey(privKey.PubKey())} + nodeInfo := types.NodeInfo{NodeID: types.NodeIDFromPubKey(privKey.PubKey()), Channels: tmsync.NewConcurrentSlice[uint16]()} _, _, err := ab.Handshake(ctx, 0, nodeInfo, privKey) require.NoError(t, err) diff --git a/node/setup.go b/node/setup.go index f78d8f4f47..0262749f13 100644 --- a/node/setup.go +++ b/node/setup.go @@ -20,6 +20,8 @@ import ( "github.com/dashpay/tenderdash/internal/eventbus" "github.com/dashpay/tenderdash/internal/evidence" tmstrings "github.com/dashpay/tenderdash/internal/libs/strings" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" + "github.com/dashpay/tenderdash/internal/mempool" "github.com/dashpay/tenderdash/internal/p2p" "github.com/dashpay/tenderdash/internal/p2p/client" @@ -363,20 +365,20 @@ func makeNodeInfo( NodeID: nodeKey.ID, Network: genDoc.ChainID, Version: version.TMCoreSemVer, - Channels: []byte{ - byte(p2p.BlockSyncChannel), - byte(consensus.StateChannel), - byte(consensus.DataChannel), - byte(consensus.VoteChannel), - byte(consensus.VoteSetBitsChannel), - byte(p2p.MempoolChannel), - byte(evidence.EvidenceChannel), - byte(statesync.SnapshotChannel), - byte(statesync.ChunkChannel), - byte(statesync.LightBlockChannel), - byte(statesync.ParamsChannel), - byte(pex.PexChannel), - }, + Channels: tmsync.NewConcurrentSlice[uint16]( + uint16(p2p.BlockSyncChannel), + uint16(consensus.StateChannel), + uint16(consensus.DataChannel), + uint16(consensus.VoteChannel), + uint16(consensus.VoteSetBitsChannel), + uint16(p2p.MempoolChannel), + uint16(evidence.EvidenceChannel), + uint16(statesync.SnapshotChannel), + uint16(statesync.ChunkChannel), + uint16(statesync.LightBlockChannel), + uint16(statesync.ParamsChannel), + uint16(pex.PexChannel), + ), Moniker: cfg.Moniker, Other: types.NodeInfoOther{ TxIndex: txIndexerStatus, @@ -405,13 +407,11 @@ func makeSeedNodeInfo( Block: state.Version.Consensus.Block, App: state.Version.Consensus.App, }, - NodeID: nodeKey.ID, - Network: genDoc.ChainID, - Version: version.TMCoreSemVer, - Channels: []byte{ - pex.PexChannel, - }, - Moniker: cfg.Moniker, + NodeID: nodeKey.ID, + Network: genDoc.ChainID, + Version: version.TMCoreSemVer, + Channels: tmsync.NewConcurrentSlice[uint16](pex.PexChannel), + Moniker: cfg.Moniker, Other: types.NodeInfoOther{ TxIndex: "off", RPCAddress: cfg.RPC.ListenAddress, diff --git a/proto/tendermint/p2p/types.pb.go b/proto/tendermint/p2p/types.pb.go index 14aeefe3cb..0f1e210ac6 100644 --- a/proto/tendermint/p2p/types.pb.go +++ b/proto/tendermint/p2p/types.pb.go @@ -98,7 +98,7 @@ type NodeInfo struct { ListenAddr string `protobuf:"bytes,3,opt,name=listen_addr,json=listenAddr,proto3" json:"listen_addr,omitempty"` Network string `protobuf:"bytes,4,opt,name=network,proto3" json:"network,omitempty"` Version string `protobuf:"bytes,5,opt,name=version,proto3" json:"version,omitempty"` - Channels []byte `protobuf:"bytes,6,opt,name=channels,proto3" json:"channels,omitempty"` + Channels []uint32 `protobuf:"varint,6,rep,packed,name=channels,proto3" json:"channels,omitempty"` Moniker string `protobuf:"bytes,7,opt,name=moniker,proto3" json:"moniker,omitempty"` Other NodeInfoOther `protobuf:"bytes,8,opt,name=other,proto3" json:"other"` ProTxHash []byte `protobuf:"bytes,9,opt,name=pro_tx_hash,json=proTxHash,proto3" json:"pro_tx_hash,omitempty"` @@ -172,7 +172,7 @@ func (m *NodeInfo) GetVersion() string { return "" } -func (m *NodeInfo) GetChannels() []byte { +func (m *NodeInfo) GetChannels() []uint32 { if m != nil { return m.Channels } @@ -899,7 +899,7 @@ func init() { func init() { proto.RegisterFile("tendermint/p2p/types.proto", fileDescriptor_c8a29e659aeca578) } var fileDescriptor_c8a29e659aeca578 = []byte{ - // 1436 bytes of a gzipped FileDescriptorProto + // 1438 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x57, 0x4b, 0x6f, 0xdb, 0x46, 0x17, 0x95, 0xe4, 0x97, 0x74, 0x65, 0x59, 0xf6, 0xc4, 0x5f, 0xc2, 0x38, 0x89, 0xe4, 0xcf, 0xe9, 0xc3, 0xe8, 0x42, 0x2a, 0x14, 0xa0, 0x48, 0x83, 0x06, 0x48, 0xe4, 0x38, 0x91, 0x0b, 0x27, 0x11, @@ -923,73 +923,73 @@ var fileDescriptor_c8a29e659aeca578 = []byte{ 0x3d, 0xf0, 0x89, 0xb9, 0x2c, 0xb7, 0x0e, 0x5c, 0xd4, 0x84, 0x6a, 0xe0, 0x73, 0x81, 0x89, 0x65, 0xbb, 0x6e, 0xa8, 0xaa, 0xab, 0x98, 0x10, 0xa5, 0x1e, 0xbb, 0x6e, 0x88, 0x0c, 0x58, 0x21, 0x58, 0xbc, 0xa6, 0xe1, 0x99, 0xb1, 0xa8, 0x36, 0xe3, 0x50, 0xee, 0xc4, 0x85, 0x2e, 0x45, 0x3b, 0x3a, - 0x44, 0x5b, 0x50, 0x76, 0x3c, 0x9b, 0x10, 0x1c, 0x70, 0x63, 0x79, 0xbb, 0xb8, 0xbb, 0x6a, 0x26, + 0x44, 0x5b, 0x50, 0x76, 0x3c, 0x9b, 0x10, 0x1c, 0x70, 0x63, 0x79, 0x7b, 0x61, 0xb7, 0x66, 0x26, 0xb1, 0x64, 0x0d, 0x29, 0xf1, 0xcf, 0x70, 0x68, 0xac, 0x44, 0x2c, 0x1d, 0xa2, 0xaf, 0x61, 0x89, 0x0a, 0x0f, 0x87, 0x46, 0x59, 0x1d, 0xfb, 0x4e, 0xfe, 0xd8, 0xb1, 0x55, 0x2f, 0x25, 0x48, 0x1f, - 0x3a, 0x62, 0xa0, 0x06, 0x54, 0x59, 0x48, 0x2d, 0x31, 0xb1, 0x3c, 0x9b, 0x7b, 0x46, 0x45, 0x3d, - 0xb3, 0xc2, 0x42, 0x7a, 0x3c, 0xe9, 0xd9, 0xdc, 0xdb, 0xf9, 0x09, 0x6a, 0x53, 0x6c, 0x74, 0x13, - 0xca, 0x62, 0x62, 0xf9, 0xc4, 0xc5, 0x13, 0xe5, 0x72, 0xc5, 0x5c, 0x11, 0x93, 0x03, 0x19, 0xa2, - 0x36, 0x54, 0x43, 0xe6, 0x28, 0x3b, 0x30, 0xe7, 0xda, 0xba, 0xb5, 0xcb, 0x8b, 0x26, 0x98, 0xfd, - 0xbd, 0xc7, 0x51, 0xd6, 0x84, 0x90, 0x39, 0x7a, 0xbd, 0xf3, 0x77, 0x11, 0xca, 0x7d, 0x8c, 0x43, - 0xd5, 0xc6, 0xeb, 0x50, 0xf2, 0xdd, 0x48, 0xb2, 0xbb, 0x7c, 0x79, 0xd1, 0x2c, 0x1d, 0x3c, 0x31, - 0x4b, 0xbe, 0x8b, 0xba, 0xb0, 0xaa, 0x15, 0x2d, 0x9f, 0x9c, 0x50, 0xa3, 0xb4, 0xbd, 0x70, 0x65, - 0x6b, 0x31, 0x0e, 0xb5, 0xae, 0x94, 0x33, 0xab, 0x76, 0x1a, 0xa0, 0x67, 0xb0, 0x16, 0xd8, 0x5c, - 0x58, 0x0e, 0x25, 0x04, 0x3b, 0x02, 0xbb, 0xaa, 0x5d, 0xd5, 0xce, 0x56, 0x2b, 0x9a, 0xdf, 0x56, - 0x3c, 0xbf, 0xad, 0xe3, 0x78, 0x7e, 0xbb, 0x8b, 0x6f, 0xfe, 0x68, 0x16, 0xcd, 0x9a, 0xe4, 0xed, - 0xc5, 0x34, 0xd9, 0x1f, 0x9f, 0xd8, 0x8e, 0xf0, 0xc7, 0x58, 0x35, 0xb5, 0x6c, 0x26, 0x71, 0xde, - 0xca, 0xa5, 0xbc, 0x95, 0xff, 0x14, 0xa1, 0x9e, 0xab, 0x52, 0xf6, 0x34, 0xb6, 0x4b, 0x9b, 0xa9, - 0x43, 0x74, 0x08, 0x1b, 0xaa, 0x64, 0xd7, 0xb7, 0x03, 0x8b, 0x8f, 0x1c, 0x27, 0xb6, 0xf4, 0x63, - 0xaa, 0xae, 0x4b, 0xea, 0x13, 0xdf, 0x0e, 0x8e, 0x22, 0xe2, 0xb4, 0xda, 0x89, 0xed, 0x07, 0xa3, - 0x10, 0x7f, 0xb4, 0x07, 0x89, 0xda, 0xd3, 0x88, 0x88, 0xee, 0x42, 0x2d, 0x2b, 0xc4, 0x95, 0x15, - 0x35, 0x73, 0xd5, 0x4d, 0x31, 0x7c, 0xe7, 0x36, 0x2c, 0xee, 0x3b, 0x1e, 0x95, 0xbf, 0xe9, 0xb1, - 0x1d, 0x8c, 0xb0, 0x3e, 0x60, 0x14, 0xec, 0xfc, 0xb6, 0x01, 0xe5, 0x7d, 0x32, 0xc6, 0x01, 0x65, - 0x18, 0xf5, 0x00, 0x6c, 0x21, 0x42, 0x7f, 0x30, 0x12, 0x58, 0x1a, 0x21, 0x1b, 0xbc, 0x9b, 0x6f, - 0x70, 0x8c, 0x6e, 0x3d, 0x4e, 0xa0, 0xfb, 0x44, 0x84, 0xe7, 0x66, 0x86, 0x8b, 0xbe, 0x80, 0x45, - 0xec, 0x78, 0x54, 0x1b, 0xb5, 0x39, 0xa3, 0xe1, 0x78, 0xb4, 0x57, 0x30, 0x15, 0x06, 0x3d, 0x84, - 0x2a, 0xc3, 0x13, 0x2b, 0xc4, 0xbf, 0x8e, 0x30, 0x17, 0x89, 0x1b, 0x33, 0x73, 0x35, 0x31, 0x23, - 0x44, 0xaf, 0x60, 0x02, 0x4b, 0x22, 0xf4, 0x08, 0x56, 0x23, 0x3a, 0x67, 0xf2, 0x5e, 0x56, 0x1e, - 0x54, 0x3b, 0xb7, 0xae, 0xe4, 0x47, 0x90, 0x5e, 0xc1, 0xac, 0xb2, 0x34, 0x44, 0xf7, 0xa1, 0x1c, - 0x5f, 0xc4, 0x6a, 0x5a, 0x72, 0x4f, 0x8f, 0x6e, 0xe0, 0x7d, 0x8d, 0xe8, 0x15, 0xcc, 0x04, 0x8d, - 0x1e, 0x40, 0x55, 0xdf, 0xd3, 0x96, 0x98, 0x44, 0x37, 0x45, 0xb5, 0x73, 0x23, 0x4b, 0xd6, 0xdb, - 0xad, 0xe3, 0x09, 0x97, 0x75, 0xeb, 0xf0, 0x78, 0xc2, 0xd1, 0x01, 0xd4, 0xd4, 0xb5, 0x9a, 0x1c, - 0x7c, 0x45, 0xb1, 0x77, 0xb2, 0xec, 0xe4, 0x5d, 0xd4, 0xea, 0xca, 0x55, 0x6a, 0xc0, 0xea, 0x20, - 0x13, 0xa3, 0x23, 0xd8, 0x20, 0xd4, 0x8a, 0xd5, 0xb4, 0x0f, 0xd1, 0x1d, 0xf4, 0xe9, 0xd5, 0x72, - 0x2f, 0xa8, 0x16, 0x4c, 0x1c, 0xa9, 0x93, 0xe9, 0x14, 0x3a, 0x84, 0xb5, 0x9c, 0x62, 0x45, 0x29, - 0xde, 0x9d, 0x5b, 0x60, 0xa2, 0x57, 0x1b, 0xe4, 0xd5, 0xe4, 0x4b, 0x71, 0xc4, 0x93, 0xe3, 0xc2, - 0x3c, 0xb5, 0x23, 0x85, 0x4d, 0xcf, 0x5b, 0xe3, 0xd9, 0x04, 0x7a, 0x09, 0xf5, 0x44, 0x4d, 0x17, - 0x57, 0x55, 0x72, 0x9f, 0xcc, 0x97, 0x4b, 0xaa, 0x5b, 0xe3, 0x53, 0x19, 0xf4, 0x1d, 0x6c, 0x70, - 0x62, 0x33, 0xee, 0x51, 0x91, 0x56, 0xb8, 0xaa, 0x24, 0x3f, 0xcb, 0x4a, 0x26, 0x2f, 0xf6, 0xd6, - 0x51, 0x0c, 0x4f, 0x8b, 0x5c, 0xe7, 0xb9, 0x1c, 0xfa, 0x1e, 0x50, 0x56, 0x56, 0x97, 0x5a, 0x53, - 0xba, 0x9f, 0x7f, 0x50, 0x37, 0xa9, 0x76, 0x83, 0xe7, 0x93, 0x72, 0x7a, 0x1c, 0x6f, 0x44, 0xd2, - 0xe9, 0x59, 0x9b, 0x9d, 0x9e, 0x54, 0x74, 0x4f, 0x42, 0x33, 0xd3, 0xe3, 0x64, 0x62, 0xd9, 0x9a, - 0x58, 0x4a, 0x17, 0x58, 0x9f, 0x6d, 0xcd, 0x8c, 0x56, 0xda, 0x68, 0x27, 0x9b, 0x40, 0x3f, 0xc0, - 0xb5, 0xc0, 0x3f, 0xf5, 0x84, 0x35, 0x3d, 0xdc, 0xeb, 0xf3, 0xce, 0x7c, 0x28, 0x09, 0xb9, 0x09, - 0xdf, 0x08, 0xf2, 0x49, 0xf4, 0x33, 0x6c, 0x4e, 0x4b, 0xeb, 0x72, 0x37, 0x94, 0xf6, 0xee, 0x87, - 0xb5, 0x93, 0x9a, 0x51, 0x30, 0x93, 0x95, 0x36, 0x30, 0x3b, 0xb4, 0x87, 0x69, 0xff, 0xd1, 0x3c, - 0x1b, 0xfa, 0x0a, 0x9b, 0x99, 0x50, 0x96, 0x4d, 0xc8, 0x09, 0x4d, 0xd4, 0x74, 0x99, 0xd7, 0x66, - 0x27, 0x74, 0x56, 0x2e, 0x9d, 0x50, 0x36, 0x95, 0x41, 0xdf, 0xc2, 0x1a, 0xc1, 0xaf, 0xad, 0x90, - 0x8e, 0x88, 0x6b, 0x71, 0x81, 0x99, 0xb1, 0x39, 0xdb, 0xf1, 0xe4, 0xcb, 0xb4, 0xf5, 0x02, 0xbf, - 0x36, 0x25, 0xf4, 0x48, 0x60, 0x26, 0x3b, 0x4e, 0x32, 0x31, 0x7a, 0x0e, 0x75, 0xa9, 0x35, 0xb6, - 0x03, 0xdf, 0x8d, 0xcc, 0x34, 0xfe, 0x37, 0x7b, 0xd6, 0x29, 0xb1, 0x57, 0x12, 0xab, 0x0c, 0x93, - 0x67, 0x25, 0xd9, 0x04, 0xfa, 0x06, 0xca, 0x2c, 0xa4, 0x8c, 0x72, 0x3b, 0x30, 0xae, 0x2b, 0x9d, - 0xc6, 0xd5, 0x3a, 0x7d, 0x8d, 0x92, 0x77, 0x68, 0xcc, 0x40, 0x4f, 0x61, 0x35, 0x5e, 0x5b, 0x8c, - 0x06, 0xc6, 0x0d, 0xa5, 0xf0, 0xff, 0xf9, 0x0a, 0xfd, 0x97, 0x87, 0xea, 0x16, 0x8f, 0x43, 0x1a, - 0xa0, 0x47, 0x00, 0xd1, 0x5c, 0x30, 0x3b, 0x14, 0x86, 0x31, 0xfb, 0xe1, 0x99, 0xaa, 0xa8, 0xb2, - 0xfb, 0x76, 0x28, 0xfb, 0x56, 0x19, 0xc4, 0x01, 0xfa, 0x12, 0x16, 0xc7, 0x54, 0x60, 0xe3, 0xe6, - 0xec, 0x3b, 0x20, 0xe5, 0xbe, 0xa2, 0x42, 0xb6, 0x47, 0x21, 0xd1, 0x03, 0x28, 0x7b, 0x36, 0xb7, - 0x14, 0x6b, 0x6b, 0xf6, 0x9b, 0x2f, 0x65, 0xf5, 0x6c, 0xae, 0x89, 0x2b, 0x5e, 0xb4, 0x94, 0x0d, - 0x95, 0x3c, 0x8b, 0x63, 0x61, 0x0d, 0xed, 0x5f, 0x3a, 0xf7, 0x8c, 0x5b, 0xf3, 0x1a, 0x2a, 0x39, - 0x47, 0x58, 0x3c, 0x97, 0x48, 0xd9, 0xd0, 0x71, 0x26, 0x46, 0xcf, 0xa0, 0x96, 0x68, 0x0d, 0x7c, - 0xc1, 0x8d, 0xdb, 0xf3, 0x4c, 0xd4, 0x52, 0x5d, 0x5f, 0xc8, 0x77, 0x52, 0x75, 0x9c, 0x86, 0xe8, - 0x2b, 0x58, 0x76, 0xe8, 0x70, 0xe8, 0x0b, 0xe3, 0x8e, 0x52, 0xb8, 0x7d, 0xb5, 0xc2, 0x9e, 0xc2, - 0xf4, 0x0a, 0xa6, 0x46, 0x4b, 0xf3, 0xa5, 0x11, 0x9a, 0xdb, 0x98, 0x67, 0x7e, 0xcf, 0xe6, 0x09, - 0xbd, 0xe2, 0xc5, 0xc1, 0xd6, 0x43, 0xa8, 0xe7, 0x3e, 0x28, 0xe4, 0xff, 0x8d, 0x33, 0x7c, 0xae, - 0xbf, 0x57, 0xe4, 0x32, 0xfd, 0x86, 0x29, 0x65, 0xbe, 0x61, 0x1e, 0x94, 0xee, 0x17, 0xbb, 0x4b, - 0xb0, 0xc0, 0x47, 0xc3, 0xee, 0xe1, 0xdb, 0xcb, 0x46, 0xf1, 0xdd, 0x65, 0xa3, 0xf8, 0xe7, 0x65, - 0xa3, 0xf8, 0xe6, 0x7d, 0xa3, 0xf0, 0xee, 0x7d, 0xa3, 0xf0, 0xfb, 0xfb, 0x46, 0xe1, 0xc7, 0xce, - 0xa9, 0x2f, 0xbc, 0xd1, 0xa0, 0xe5, 0xd0, 0x61, 0xdb, 0xb5, 0xb9, 0xc7, 0xec, 0xf3, 0x76, 0x54, - 0x9f, 0x8c, 0xa2, 0x3f, 0x4e, 0xed, 0xe9, 0xff, 0x57, 0x83, 0x65, 0x95, 0xbd, 0xf7, 0x6f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x5d, 0xf9, 0xc2, 0xa1, 0x61, 0x0e, 0x00, 0x00, + 0x3a, 0x62, 0xa0, 0x06, 0x54, 0x59, 0x48, 0x2d, 0x31, 0xb1, 0x3c, 0x9b, 0x7b, 0x46, 0x65, 0xbb, + 0xb8, 0xbb, 0x6a, 0x56, 0x58, 0x48, 0x8f, 0x27, 0x3d, 0x9b, 0x7b, 0x3b, 0x3f, 0x41, 0x6d, 0x8a, + 0x8d, 0x6e, 0x42, 0x59, 0x4c, 0x2c, 0x9f, 0xb8, 0x78, 0xa2, 0x5c, 0xae, 0x98, 0x2b, 0x62, 0x72, + 0x20, 0x43, 0xd4, 0x86, 0x6a, 0xc8, 0x1c, 0x65, 0x07, 0xe6, 0x5c, 0x5b, 0xb7, 0x76, 0x79, 0xd1, + 0x04, 0xb3, 0xbf, 0xf7, 0x38, 0xca, 0x9a, 0x10, 0x32, 0x47, 0xaf, 0x77, 0xfe, 0x2e, 0x42, 0xb9, + 0x8f, 0x71, 0xa8, 0xda, 0x78, 0x1d, 0x4a, 0xbe, 0x1b, 0x49, 0x76, 0x97, 0x2f, 0x2f, 0x9a, 0xa5, + 0x83, 0x27, 0x66, 0xc9, 0x77, 0x51, 0x17, 0x56, 0xb5, 0xa2, 0xe5, 0x93, 0x13, 0x6a, 0x94, 0xb6, + 0x17, 0xae, 0x6c, 0x2d, 0xc6, 0xa1, 0xd6, 0x95, 0x72, 0x66, 0xd5, 0x4e, 0x03, 0xf4, 0x0c, 0xd6, + 0x02, 0x9b, 0x0b, 0xcb, 0xa1, 0x84, 0x60, 0x47, 0x60, 0x57, 0xb5, 0xab, 0xda, 0xd9, 0x6a, 0x45, + 0xf3, 0xdb, 0x8a, 0xe7, 0xb7, 0x75, 0x1c, 0xcf, 0x6f, 0x77, 0xf1, 0xcd, 0x1f, 0xcd, 0xa2, 0x59, + 0x93, 0xbc, 0xbd, 0x98, 0x26, 0xfb, 0xe3, 0x13, 0xdb, 0x11, 0xfe, 0x18, 0xab, 0xa6, 0x96, 0xcd, + 0x24, 0xce, 0x5b, 0xb9, 0x94, 0xb7, 0xf2, 0x9f, 0x22, 0xd4, 0x73, 0x55, 0xca, 0x9e, 0xc6, 0x76, + 0x69, 0x33, 0x75, 0x88, 0x0e, 0x61, 0x43, 0x95, 0xec, 0xfa, 0x76, 0x60, 0xf1, 0x91, 0xe3, 0xc4, + 0x96, 0x7e, 0x4c, 0xd5, 0x75, 0x49, 0x7d, 0xe2, 0xdb, 0xc1, 0x51, 0x44, 0x9c, 0x56, 0x3b, 0xb1, + 0xfd, 0x60, 0x14, 0xe2, 0x8f, 0xf6, 0x20, 0x51, 0x7b, 0x1a, 0x11, 0xd1, 0x5d, 0xa8, 0x65, 0x85, + 0xb8, 0xb2, 0xa2, 0x66, 0xae, 0xba, 0x29, 0x86, 0xef, 0xdc, 0x86, 0xc5, 0x7d, 0xc7, 0xa3, 0xf2, + 0x37, 0x3d, 0xb6, 0x83, 0x11, 0xd6, 0x07, 0x8c, 0x82, 0x9d, 0xdf, 0x36, 0xa0, 0xbc, 0x4f, 0xc6, + 0x38, 0xa0, 0x0c, 0xa3, 0x1e, 0x80, 0x2d, 0x44, 0xe8, 0x0f, 0x46, 0x02, 0x4b, 0x23, 0x64, 0x83, + 0x77, 0xf3, 0x0d, 0x8e, 0xd1, 0xad, 0xc7, 0x09, 0x74, 0x9f, 0x88, 0xf0, 0xdc, 0xcc, 0x70, 0xd1, + 0x17, 0xb0, 0x88, 0x1d, 0x8f, 0x6a, 0xa3, 0x36, 0x67, 0x34, 0x1c, 0x8f, 0xf6, 0x0a, 0xa6, 0xc2, + 0xa0, 0x87, 0x50, 0x65, 0x78, 0x62, 0x85, 0xf8, 0xd7, 0x11, 0xe6, 0x22, 0x71, 0x63, 0x66, 0xae, + 0x26, 0x66, 0x84, 0xe8, 0x15, 0x4c, 0x60, 0x49, 0x84, 0x1e, 0xc1, 0x6a, 0x44, 0xe7, 0x4c, 0xde, + 0xcb, 0xca, 0x83, 0x6a, 0xe7, 0xd6, 0x95, 0xfc, 0x08, 0xd2, 0x2b, 0x98, 0x55, 0x96, 0x86, 0xe8, + 0x3e, 0x94, 0xe3, 0x8b, 0x58, 0x4d, 0x4b, 0xee, 0xe9, 0xd1, 0x0d, 0xbc, 0xaf, 0x11, 0xbd, 0x82, + 0x99, 0xa0, 0xd1, 0x03, 0xa8, 0xea, 0x7b, 0xda, 0x12, 0x13, 0x79, 0x53, 0x48, 0xf2, 0x8d, 0x2c, + 0x59, 0x6f, 0xb7, 0x8e, 0x27, 0x5c, 0xd6, 0xad, 0xc3, 0xe3, 0x09, 0x47, 0x07, 0x50, 0x53, 0xd7, + 0x6a, 0x72, 0xf0, 0x15, 0xc5, 0xde, 0xc9, 0xb2, 0x93, 0x77, 0x51, 0xab, 0x2b, 0x57, 0xa9, 0x01, + 0xab, 0x83, 0x4c, 0x8c, 0x8e, 0x60, 0x83, 0x50, 0x2b, 0x56, 0xd3, 0x3e, 0x44, 0x77, 0xd0, 0xa7, + 0x57, 0xcb, 0xbd, 0xa0, 0x5a, 0x30, 0x71, 0xa4, 0x4e, 0xa6, 0x53, 0xe8, 0x10, 0xd6, 0x72, 0x8a, + 0x15, 0xa5, 0x78, 0x77, 0x6e, 0x81, 0x89, 0x5e, 0x6d, 0x90, 0x57, 0x93, 0x2f, 0xc5, 0x11, 0x4f, + 0x8e, 0x0b, 0xf3, 0xd4, 0x8e, 0x14, 0x36, 0x3d, 0x6f, 0x8d, 0x67, 0x13, 0xe8, 0x25, 0xd4, 0x13, + 0x35, 0x5d, 0x5c, 0x55, 0xc9, 0x7d, 0x32, 0x5f, 0x2e, 0xa9, 0x6e, 0x8d, 0x4f, 0x65, 0xd0, 0x77, + 0xb0, 0xc1, 0x89, 0xcd, 0xb8, 0x47, 0x45, 0x5a, 0xe1, 0xaa, 0x92, 0xfc, 0x2c, 0x2b, 0x99, 0xbc, + 0xd8, 0x5b, 0x47, 0x31, 0x3c, 0x2d, 0x72, 0x9d, 0xe7, 0x72, 0xe8, 0x7b, 0x40, 0x59, 0x59, 0x5d, + 0x6a, 0x4d, 0xe9, 0x7e, 0xfe, 0x41, 0xdd, 0xa4, 0xda, 0x0d, 0x9e, 0x4f, 0xca, 0xe9, 0x71, 0xbc, + 0x11, 0x49, 0xa7, 0x67, 0x6d, 0x76, 0x7a, 0x52, 0xd1, 0x3d, 0x09, 0xcd, 0x4c, 0x8f, 0x93, 0x89, + 0x65, 0x6b, 0x62, 0x29, 0x5d, 0x60, 0x7d, 0xb6, 0x35, 0x33, 0x5a, 0x69, 0xa3, 0x9d, 0x6c, 0x02, + 0xfd, 0x00, 0xd7, 0x02, 0xff, 0xd4, 0x13, 0xd6, 0xf4, 0x70, 0xaf, 0xcf, 0x3b, 0xf3, 0xa1, 0x24, + 0xe4, 0x26, 0x7c, 0x23, 0xc8, 0x27, 0xd1, 0xcf, 0xb0, 0x39, 0x2d, 0xad, 0xcb, 0xdd, 0x50, 0xda, + 0xbb, 0x1f, 0xd6, 0x4e, 0x6a, 0x46, 0xc1, 0x4c, 0x56, 0xda, 0xc0, 0xec, 0xd0, 0x1e, 0xa6, 0xfd, + 0x47, 0xf3, 0x6c, 0xe8, 0x2b, 0x6c, 0x66, 0x42, 0x59, 0x36, 0x21, 0x27, 0x34, 0x51, 0xd3, 0x65, + 0x5e, 0x9b, 0x9d, 0xd0, 0x59, 0xb9, 0x74, 0x42, 0xd9, 0x54, 0x06, 0x7d, 0x0b, 0x6b, 0x04, 0xbf, + 0xb6, 0x42, 0x3a, 0x22, 0xae, 0xc5, 0x05, 0x66, 0xc6, 0xe6, 0x6c, 0xc7, 0x93, 0x2f, 0xd3, 0xd6, + 0x0b, 0xfc, 0xda, 0x94, 0xd0, 0x23, 0x81, 0x99, 0xec, 0x38, 0xc9, 0xc4, 0xe8, 0x39, 0xd4, 0xa5, + 0xd6, 0xd8, 0x0e, 0x7c, 0x37, 0x32, 0xd3, 0xf8, 0xdf, 0xec, 0x59, 0xa7, 0xc4, 0x5e, 0x49, 0xac, + 0x32, 0x4c, 0x9e, 0x95, 0x64, 0x13, 0xe8, 0x1b, 0x28, 0xb3, 0x90, 0x32, 0xca, 0xed, 0xc0, 0xb8, + 0xae, 0x74, 0x1a, 0x57, 0xeb, 0xf4, 0x35, 0x4a, 0xde, 0xa1, 0x31, 0x03, 0x3d, 0x85, 0xd5, 0x78, + 0x6d, 0x31, 0x1a, 0x18, 0x37, 0x94, 0xc2, 0xff, 0xe7, 0x2b, 0xf4, 0x5f, 0x1e, 0xaa, 0x5b, 0x3c, + 0x0e, 0x69, 0x80, 0x1e, 0x01, 0x44, 0x73, 0xc1, 0xec, 0x50, 0x18, 0xc6, 0xec, 0x87, 0x67, 0xaa, + 0xa2, 0xca, 0xee, 0xdb, 0xa1, 0xec, 0x5b, 0x65, 0x10, 0x07, 0xe8, 0x4b, 0x58, 0x1c, 0x53, 0x81, + 0x8d, 0x9b, 0xb3, 0xef, 0x80, 0x94, 0xfb, 0x8a, 0x0a, 0xd9, 0x1e, 0x85, 0x44, 0x0f, 0xa0, 0xec, + 0xd9, 0xdc, 0x52, 0xac, 0xad, 0xd9, 0x6f, 0xbe, 0x94, 0xd5, 0xb3, 0xb9, 0x26, 0xae, 0x78, 0xd1, + 0x52, 0x36, 0x54, 0xf2, 0x2c, 0x8e, 0x85, 0x35, 0xb4, 0x7f, 0xe9, 0xdc, 0x33, 0x6e, 0xcd, 0x6b, + 0xa8, 0xe4, 0x1c, 0x61, 0xf1, 0x5c, 0x22, 0x65, 0x43, 0xc7, 0x99, 0x18, 0x3d, 0x83, 0x5a, 0xa2, + 0x35, 0xf0, 0x05, 0x37, 0x6e, 0xcf, 0x33, 0x51, 0x4b, 0x75, 0x7d, 0x21, 0xdf, 0x49, 0xd5, 0x71, + 0x1a, 0xa2, 0xaf, 0x60, 0xd9, 0xa1, 0xc3, 0xa1, 0x2f, 0x8c, 0x3b, 0x4a, 0xe1, 0xf6, 0xd5, 0x0a, + 0x7b, 0x0a, 0xd3, 0x2b, 0x98, 0x1a, 0x2d, 0xcd, 0x97, 0x46, 0x68, 0x6e, 0x63, 0x9e, 0xf9, 0x3d, + 0x9b, 0x27, 0xf4, 0x8a, 0x17, 0x07, 0x5b, 0x0f, 0xa1, 0x9e, 0xfb, 0xa0, 0x90, 0xff, 0x37, 0xce, + 0xf0, 0xb9, 0xfe, 0x5e, 0x91, 0xcb, 0xf4, 0x1b, 0xa6, 0x94, 0xf9, 0x86, 0x79, 0x50, 0xba, 0x5f, + 0xec, 0x2e, 0xc1, 0x02, 0x1f, 0x0d, 0xbb, 0x87, 0x6f, 0x2f, 0x1b, 0xc5, 0x77, 0x97, 0x8d, 0xe2, + 0x9f, 0x97, 0x8d, 0xe2, 0x9b, 0xf7, 0x8d, 0xc2, 0xbb, 0xf7, 0x8d, 0xc2, 0xef, 0xef, 0x1b, 0x85, + 0x1f, 0x3b, 0xa7, 0xbe, 0xf0, 0x46, 0x83, 0x96, 0x43, 0x87, 0x6d, 0xd7, 0xe6, 0x1e, 0xb3, 0xcf, + 0xdb, 0x51, 0x7d, 0x32, 0x8a, 0xfe, 0x38, 0xb5, 0xa7, 0xff, 0x5f, 0x0d, 0x96, 0x55, 0xf6, 0xde, + 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x4c, 0xe1, 0x61, 0x1a, 0x61, 0x0e, 0x00, 0x00, } func (m *ProtocolVersion) Marshal() (dAtA []byte, err error) { @@ -1075,9 +1075,20 @@ func (m *NodeInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x3a } if len(m.Channels) > 0 { - i -= len(m.Channels) - copy(dAtA[i:], m.Channels) - i = encodeVarintTypes(dAtA, i, uint64(len(m.Channels))) + dAtA3 := make([]byte, len(m.Channels)*10) + var j2 int + for _, num := range m.Channels { + for num >= 1<<7 { + dAtA3[j2] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j2++ + } + dAtA3[j2] = uint8(num) + j2++ + } + i -= j2 + copy(dAtA[i:], dAtA3[:j2]) + i = encodeVarintTypes(dAtA, i, uint64(j2)) i-- dAtA[i] = 0x32 } @@ -1197,12 +1208,12 @@ func (m *PeerInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x20 } if m.LastConnected != nil { - n3, err3 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastConnected, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastConnected):]) - if err3 != nil { - return 0, err3 + n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastConnected, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastConnected):]) + if err5 != nil { + return 0, err5 } - i -= n3 - i = encodeVarintTypes(dAtA, i, uint64(n3)) + i -= n5 + i = encodeVarintTypes(dAtA, i, uint64(n5)) i-- dAtA[i] = 0x1a } @@ -1256,22 +1267,22 @@ func (m *PeerAddressInfo) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x20 } if m.LastDialFailure != nil { - n4, err4 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastDialFailure, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastDialFailure):]) - if err4 != nil { - return 0, err4 + n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastDialFailure, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastDialFailure):]) + if err6 != nil { + return 0, err6 } - i -= n4 - i = encodeVarintTypes(dAtA, i, uint64(n4)) + i -= n6 + i = encodeVarintTypes(dAtA, i, uint64(n6)) i-- dAtA[i] = 0x1a } if m.LastDialSuccess != nil { - n5, err5 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastDialSuccess, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastDialSuccess):]) - if err5 != nil { - return 0, err5 + n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.LastDialSuccess, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.LastDialSuccess):]) + if err7 != nil { + return 0, err7 } - i -= n5 - i = encodeVarintTypes(dAtA, i, uint64(n5)) + i -= n7 + i = encodeVarintTypes(dAtA, i, uint64(n7)) i-- dAtA[i] = 0x12 } @@ -2058,9 +2069,12 @@ func (m *NodeInfo) Size() (n int) { if l > 0 { n += 1 + l + sovTypes(uint64(l)) } - l = len(m.Channels) - if l > 0 { - n += 1 + l + sovTypes(uint64(l)) + if len(m.Channels) > 0 { + l = 0 + for _, e := range m.Channels { + l += sovTypes(uint64(e)) + } + n += 1 + sovTypes(uint64(l)) + l } l = len(m.Moniker) if l > 0 { @@ -2832,39 +2846,81 @@ func (m *NodeInfo) Unmarshal(dAtA []byte) error { m.Version = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowTypes + if wireType == 0 { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } } - if iNdEx >= l { + m.Channels = append(m.Channels, v) + } else if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + packedLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if packedLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + packedLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { return io.ErrUnexpectedEOF } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break + var elementCount int + var count int + for _, integer := range dAtA[iNdEx:postIndex] { + if integer < 128 { + count++ + } } + elementCount = count + if elementCount != 0 && len(m.Channels) == 0 { + m.Channels = make([]uint32, 0, elementCount) + } + for iNdEx < postIndex { + var v uint32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.Channels = append(m.Channels, v) + } + } else { + return fmt.Errorf("proto: wrong wireType = %d for field Channels", wireType) } - if byteLen < 0 { - return ErrInvalidLengthTypes - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthTypes - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Channels = append(m.Channels[:0], dAtA[iNdEx:postIndex]...) - if m.Channels == nil { - m.Channels = []byte{} - } - iNdEx = postIndex case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Moniker", wireType) diff --git a/proto/tendermint/p2p/types.proto b/proto/tendermint/p2p/types.proto index 9baa663eca..2b37dcdde8 100644 --- a/proto/tendermint/p2p/types.proto +++ b/proto/tendermint/p2p/types.proto @@ -24,7 +24,7 @@ message NodeInfo { string listen_addr = 3; string network = 4; string version = 5; - bytes channels = 6; + repeated uint32 channels = 6; string moniker = 7; NodeInfoOther other = 8 [(gogoproto.nullable) = false]; bytes pro_tx_hash = 9; diff --git a/types/node_info.go b/types/node_info.go index eb3720f890..ffb14e2157 100644 --- a/types/node_info.go +++ b/types/node_info.go @@ -9,7 +9,7 @@ import ( "github.com/dashpay/tenderdash/crypto" tmstrings "github.com/dashpay/tenderdash/internal/libs/strings" - tmbytes "github.com/dashpay/tenderdash/libs/bytes" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" tmp2p "github.com/dashpay/tenderdash/proto/tendermint/p2p" ) @@ -48,8 +48,8 @@ type NodeInfo struct { // Channels are HexBytes so easier to read as JSON Network string `json:"network"` // network/chain ID Version string `json:"version"` // major.minor.revision - // FIXME: This should be changed to uint16 to be consistent with the updated channel type - Channels tmbytes.HexBytes `json:"channels"` // channels this node knows about + // Channels supported by this node. Use GetChannels() as a getter. + Channels *tmsync.ConcurrentSlice[uint16] `json:"channels"` // channels this node knows about // ASCIIText fields Moniker string `json:"moniker"` // arbitrary moniker @@ -97,11 +97,15 @@ func (info NodeInfo) Validate() error { } // Validate Channels - ensure max and check for duplicates. - if len(info.Channels) > maxNumChannels { - return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels) + if info.Channels == nil { + return fmt.Errorf("info.Channels is nil") } - channels := make(map[byte]struct{}) - for _, ch := range info.Channels { + + if info.Channels.Len() > maxNumChannels { + return fmt.Errorf("info.Channels is too long (%v). Max is %v", info.Channels.Len(), maxNumChannels) + } + channels := make(map[uint16]struct{}) + for _, ch := range info.Channels.ToSlice() { _, ok := channels[ch] if ok { return fmt.Errorf("info.Channels contains duplicate channel id %v", ch) @@ -147,15 +151,15 @@ func (info NodeInfo) CompatibleWith(other NodeInfo) error { } // if we have no channels, we're just testing - if len(info.Channels) == 0 { + if info.Channels.Len() == 0 { return nil } // for each of our channels, check if they have it found := false OUTER_LOOP: - for _, ch1 := range info.Channels { - for _, ch2 := range other.Channels { + for _, ch1 := range info.Channels.ToSlice() { + for _, ch2 := range other.Channels.ToSlice() { if ch1 == ch2 { found = true break OUTER_LOOP // only need one @@ -171,23 +175,24 @@ OUTER_LOOP: // AddChannel is used by the router when a channel is opened to add it to the node info func (info *NodeInfo) AddChannel(channel uint16) { // check that the channel doesn't already exist - for _, ch := range info.Channels { - if ch == byte(channel) { + for _, ch := range info.Channels.ToSlice() { + if ch == channel { return } } - info.Channels = append(info.Channels, byte(channel)) + info.Channels.Append(channel) } func (info NodeInfo) Copy() NodeInfo { + chans := info.Channels.Copy() return NodeInfo{ ProtocolVersion: info.ProtocolVersion, NodeID: info.NodeID, ListenAddr: info.ListenAddr, Network: info.Network, Version: info.Version, - Channels: info.Channels, + Channels: &chans, Moniker: info.Moniker, Other: info.Other, ProTxHash: info.ProTxHash.Copy(), @@ -203,11 +208,14 @@ func (info NodeInfo) ToProto() *tmp2p.NodeInfo { App: info.ProtocolVersion.App, } + for _, ch := range info.Channels.ToSlice() { + dni.Channels = append(dni.Channels, uint32(ch)) + } + dni.NodeID = string(info.NodeID) dni.ListenAddr = info.ListenAddr dni.Network = info.Network dni.Version = info.Version - dni.Channels = info.Channels dni.Moniker = info.Moniker dni.ProTxHash = info.ProTxHash.Copy() dni.Other = tmp2p.NodeInfoOther{ @@ -232,7 +240,7 @@ func NodeInfoFromProto(pb *tmp2p.NodeInfo) (NodeInfo, error) { ListenAddr: pb.ListenAddr, Network: pb.Network, Version: pb.Version, - Channels: pb.Channels, + Channels: tmsync.NewConcurrentSlice[uint16](), Moniker: pb.Moniker, Other: NodeInfoOther{ TxIndex: pb.Other.TxIndex, @@ -240,6 +248,11 @@ func NodeInfoFromProto(pb *tmp2p.NodeInfo) (NodeInfo, error) { }, ProTxHash: pb.ProTxHash, } + + for _, ch := range pb.Channels { + dni.Channels.Append(uint16(ch)) + } + return dni, nil } diff --git a/types/node_info_test.go b/types/node_info_test.go index b600df5820..d16b1e9850 100644 --- a/types/node_info_test.go +++ b/types/node_info_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/require" "github.com/dashpay/tenderdash/crypto/ed25519" + tmsync "github.com/dashpay/tenderdash/internal/libs/sync" tmnet "github.com/dashpay/tenderdash/libs/net" "github.com/dashpay/tenderdash/version" ) @@ -20,13 +21,13 @@ func TestNodeInfoValidate(t *testing.T) { ni := NodeInfo{} assert.Error(t, ni.Validate()) - channels := make([]byte, maxNumChannels) - for i := 0; i < maxNumChannels; i++ { - channels[i] = byte(i) + channels := tmsync.NewConcurrentSlice[uint16]() + for i := uint16(0); i < maxNumChannels; i++ { + channels.Append(i) } - dupChannels := make([]byte, 5) - copy(dupChannels, channels[:5]) - dupChannels = append(dupChannels, testCh) + + dupChannels := tmsync.NewConcurrentSlice[uint16](channels.ToSlice()[:5]...) + dupChannels.Append(testCh) nonASCII := "¢§µ" emptyTab := "\t" @@ -39,11 +40,14 @@ func TestNodeInfoValidate(t *testing.T) { }{ { "Too Many Channels", - func(ni *NodeInfo) { ni.Channels = append(channels, byte(maxNumChannels)) }, + func(ni *NodeInfo) { + ni.Channels = ref(channels.Copy()) + ni.Channels.Append(maxNumChannels) + }, true, }, {"Duplicate Channel", func(ni *NodeInfo) { ni.Channels = dupChannels }, true}, - {"Good Channels", func(ni *NodeInfo) { ni.Channels = ni.Channels[:5] }, false}, + {"Good Channels", func(ni *NodeInfo) { ni.Channels = tmsync.NewConcurrentSlice(ni.Channels.ToSlice()[:5]...) }, false}, {"Invalid NetAddress", func(ni *NodeInfo) { ni.ListenAddr = "not-an-address" }, true}, {"Good NetAddress", func(ni *NodeInfo) { ni.ListenAddr = "0.0.0.0:26656" }, false}, @@ -97,6 +101,10 @@ func TestNodeInfoValidate(t *testing.T) { } +func ref[T any](t T) *T { + return &t +} + func testNodeID() NodeID { return NodeIDFromPubKey(ed25519.GenPrivKey().PubKey()) } @@ -117,7 +125,7 @@ func testNodeInfoWithNetwork(t *testing.T, id NodeID, name, network string) Node ListenAddr: fmt.Sprintf("127.0.0.1:%d", getFreePort(t)), Network: network, Version: "1.2.3-rc0-deadbeef", - Channels: []byte{testCh}, + Channels: tmsync.NewConcurrentSlice[uint16](testCh), Moniker: name, Other: NodeInfoOther{ TxIndex: "on", @@ -146,7 +154,7 @@ func TestNodeInfoCompatible(t *testing.T) { assert.NoError(t, ni1.CompatibleWith(ni2)) // add another channel; still compatible - ni2.Channels = []byte{newTestChannel, testCh} + ni2.Channels = tmsync.NewConcurrentSlice[uint16](testCh) assert.NoError(t, ni1.CompatibleWith(ni2)) testCases := []struct { @@ -155,7 +163,7 @@ func TestNodeInfoCompatible(t *testing.T) { }{ {"Wrong block version", func(ni *NodeInfo) { ni.ProtocolVersion.Block++ }}, {"Wrong network", func(ni *NodeInfo) { ni.Network += "-wrong" }}, - {"No common channels", func(ni *NodeInfo) { ni.Channels = []byte{newTestChannel} }}, + {"No common channels", func(ni *NodeInfo) { ni.Channels = tmsync.NewConcurrentSlice[uint16](uint16(newTestChannel)) }}, } for _, tc := range testCases { @@ -167,15 +175,15 @@ func TestNodeInfoCompatible(t *testing.T) { func TestNodeInfoAddChannel(t *testing.T) { nodeInfo := testNodeInfo(t, testNodeID(), "testing") - nodeInfo.Channels = []byte{} + nodeInfo.Channels = tmsync.NewConcurrentSlice[uint16]() require.Empty(t, nodeInfo.Channels) nodeInfo.AddChannel(2) - require.Contains(t, nodeInfo.Channels, byte(0x02)) + require.Contains(t, nodeInfo.Channels.ToSlice(), uint16(2)) // adding the same channel again shouldn't be a problem nodeInfo.AddChannel(2) - require.Contains(t, nodeInfo.Channels, byte(0x02)) + require.Contains(t, nodeInfo.Channels.ToSlice(), uint16(2)) } func TestParseAddressString(t *testing.T) { diff --git a/version/version.go b/version/version.go index 9b66f99f6c..900ecf4957 100644 --- a/version/version.go +++ b/version/version.go @@ -19,7 +19,7 @@ const ( var ( // P2PProtocol versions all p2p behavior and msgs. // This includes proposer selection. - P2PProtocol uint64 = 8 + P2PProtocol uint64 = 9 // BlockProtocol versions all block data structures and processing. // This includes validity of blocks and state updates.