public override string ToString() { return(RemoteEndpoint.ToString()); }
// Broadcasts protocol message to a single random node with block height higher than the one specified with parameter block_num /// <summary> /// Sends the specified protocol message to one of the connected remote endpoints, chosen randomly. /// </summary> /// <remarks> /// The payload `data` should be properly formatted for the given `code` - this function will not ensure that this is so and /// the caller must provide a valid message to this function. /// The `skipEndpoint` parameter is useful when re-broadcasting a message received from a specific endpoint and do not wish to echo the same /// data back to the sender. /// The `block_num` parameter is used to filter the remote endpoints based on their latest known block height. /// </remarks> /// <param name="types">Types of the nodes where the message should be sent.</param> /// <param name="code">Ixian protocol code.</param> /// <param name="data">Payload data.</param> /// <param name="block_num">Block which should be searched for the endpoint addresses.</param> /// <param name="skipEndpoint">Minimum block height for endpoints which should receive this message.</param> /// <returns>True, if at least one message was sent to at least one remote endpoint. False if no messages were sent.</returns> public static bool broadcastProtocolMessageToSingleRandomNode(char[] types, ProtocolMessageCode code, byte[] data, ulong block_num, RemoteEndpoint skipEndpoint = null) { if (data == null) { Logging.warn(string.Format("Invalid protocol message data for {0}", code)); return(false); } lock (NetworkClientManager.networkClients) { lock (NetworkServer.connectedClients) { int serverCount = 0; int clientCount = 0; List <NetworkClient> servers = null; List <RemoteEndpoint> clients = null; if (types == null) { servers = NetworkClientManager.networkClients.FindAll(x => x.blockHeight > block_num && x.isConnected() && x.helloReceived); clients = NetworkServer.connectedClients.FindAll(x => x.blockHeight > block_num && x.isConnected() && x.helloReceived); serverCount = servers.Count(); clientCount = clients.Count(); if (serverCount == 0 && clientCount == 0) { servers = NetworkClientManager.networkClients.FindAll(x => x.blockHeight == block_num && x.isConnected() && x.helloReceived); clients = NetworkServer.connectedClients.FindAll(x => x.blockHeight == block_num && x.isConnected() && x.helloReceived); } } else { servers = NetworkClientManager.networkClients.FindAll(x => x.blockHeight > block_num && x.presenceAddress != null && types.Contains(x.presenceAddress.type) && x.isConnected() && x.helloReceived); clients = NetworkServer.connectedClients.FindAll(x => x.blockHeight > block_num && x.presenceAddress != null && types.Contains(x.presenceAddress.type) && x.isConnected() && x.helloReceived); serverCount = servers.Count(); clientCount = clients.Count(); if (serverCount == 0 && clientCount == 0) { servers = NetworkClientManager.networkClients.FindAll(x => x.blockHeight == block_num && x.presenceAddress != null && types.Contains(x.presenceAddress.type) && x.isConnected() && x.helloReceived); clients = NetworkServer.connectedClients.FindAll(x => x.blockHeight == block_num && x.presenceAddress != null && types.Contains(x.presenceAddress.type) && x.isConnected() && x.helloReceived); } } serverCount = servers.Count(); clientCount = clients.Count(); if (serverCount == 0 && clientCount == 0) { return(false); } Random r = new Random(); int rIdx = r.Next(serverCount + clientCount); RemoteEndpoint re = null; if (rIdx < serverCount) { re = servers[rIdx]; } else { re = clients[rIdx - serverCount]; } if (re == skipEndpoint && serverCount + clientCount > 1) { if (rIdx + 1 < serverCount) { re = servers[rIdx + 1]; } else if (rIdx + 1 < serverCount + clientCount) { re = clients[rIdx + 1 - serverCount]; } else if (serverCount > 0) { re = servers[0]; } else if (clientCount > 0) { re = clients[0]; } } if (re != null && re.isConnected()) { re.sendData(code, data); return(true); } return(false); } } }
// Called when receiving a keepalive network message. The PresenceList will update the appropriate entry based on the timestamp. // Returns TRUE if it updated an entry in the PL // Sets the out address parameter to be the KA wallet's address or null if an error occured public static bool receiveKeepAlive(byte[] bytes, out byte[] address, out long last_seen, out byte[] device_id, RemoteEndpoint endpoint) { address = null; last_seen = 0; device_id = null; // Get the current timestamp long currentTime = Clock.getNetworkTimestamp(); try { using (MemoryStream m = new MemoryStream(bytes)) { using (BinaryReader reader = new BinaryReader(m)) { int keepAliveVersion = 0; if (bytes[0] == 1) { // TODO temporary, remove after network upgrade keepAliveVersion = reader.ReadInt32(); } else { keepAliveVersion = (int)reader.ReadIxiVarInt(); } byte[] wallet; byte[] deviceid; long timestamp; string hostname; char node_type = '0'; int sigLen; byte[] signature; long checksum_data_len = 0; byte[] data_to_verify; if (keepAliveVersion == 1) { // TODO temporary, remove after network upgrade int walletLen = reader.ReadInt32(); wallet = reader.ReadBytes(walletLen); // Assign the out address parameter address = wallet; string device_id_str = reader.ReadString(); device_id = deviceid = System.Guid.Parse(device_id_str).ToByteArray(); last_seen = timestamp = reader.ReadInt64(); hostname = reader.ReadString(); node_type = reader.ReadChar(); checksum_data_len = m.Position; sigLen = reader.ReadInt32(); signature = reader.ReadBytes(sigLen); data_to_verify = bytes.Take((int)checksum_data_len).ToArray(); } else { int walletLen = (int)reader.ReadIxiVarUInt(); wallet = reader.ReadBytes(walletLen); // Assign the out address parameter address = wallet; int deviceid_len = (int)reader.ReadIxiVarUInt(); device_id = deviceid = reader.ReadBytes(deviceid_len); last_seen = timestamp = reader.ReadIxiVarInt(); hostname = reader.ReadString(); node_type = reader.ReadChar(); checksum_data_len = m.Position; sigLen = (int)reader.ReadIxiVarUInt(); signature = reader.ReadBytes(sigLen); byte[] checksum = Crypto.sha512sq(bytes.Take((int)checksum_data_len).ToArray()); data_to_verify = new byte[ConsensusConfig.ixianChecksumLock.Length + checksum.Length]; Array.Copy(ConsensusConfig.ixianChecksumLock, data_to_verify, ConsensusConfig.ixianChecksumLock.Length); Array.Copy(checksum, 0, data_to_verify, ConsensusConfig.ixianChecksumLock.Length, checksum.Length); } //Logging.info(String.Format("[PL] KEEPALIVE request from {0}", hostname)); if (node_type == 'C' || node_type == 'R') { // all good, continue } else if (node_type == 'M' || node_type == 'H') { if (myPresenceType == 'M' || myPresenceType == 'H') { // check balance if (IxianHandler.getWalletBalance(wallet) < ConsensusConfig.minimumMasterNodeFunds) { return(false); } } } else { // reject everything else return(false); } lock (presences) { Presence listEntry = presences.Find(x => x.wallet.SequenceEqual(wallet)); if (listEntry == null && wallet.SequenceEqual(IxianHandler.getWalletStorage().getPrimaryAddress())) { Logging.warn("My entry was removed from local PL, readding."); curNodePresence.addresses.Clear(); curNodePresence.addresses.Add(curNodePresenceAddress); updateEntry(curNodePresence); listEntry = presences.Find(x => x.wallet.SequenceEqual(wallet)); } // Check if no such wallet found in presence list if (listEntry == null) { // request for additional data using (MemoryStream mw = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(mw)) { writer.Write(wallet.Length); writer.Write(wallet); if (endpoint != null && endpoint.isConnected()) { endpoint.sendData(ProtocolMessageCode.getPresence, mw.ToArray(), wallet); } else { CoreProtocolMessage.broadcastProtocolMessageToSingleRandomNode(new char[] { 'M', 'R', 'H' }, ProtocolMessageCode.getPresence, mw.ToArray(), 0, null); } } } return(false); } // Verify the signature if (CryptoManager.lib.verifySignature(data_to_verify, listEntry.pubkey, signature) == false) { Logging.warn(string.Format("[PL] KEEPALIVE tampering for {0} {1}, incorrect Sig.", Base58Check.Base58CheckEncoding.EncodePlain(listEntry.wallet), hostname)); return(false); } PresenceAddress pa = listEntry.addresses.Find(x => x.address == hostname && x.device.SequenceEqual(deviceid)); if (pa != null) { // Check the node type if (pa.lastSeenTime != timestamp) { // Check for outdated timestamp if (timestamp < pa.lastSeenTime) { // We already have a newer timestamp for this entry return(false); } int expiration_time = CoreConfig.serverPresenceExpiration; if (pa.type == 'C') { expiration_time = CoreConfig.clientPresenceExpiration; } // Check for tampering. Includes a +300, -30 second synchronization zone if ((currentTime - timestamp) > expiration_time) { Logging.warn(string.Format("[PL] Received expired KEEPALIVE for {0} {1}. Timestamp {2}", Base58Check.Base58CheckEncoding.EncodePlain(listEntry.wallet), pa.address, timestamp)); return(false); } if ((currentTime - timestamp) < -30) { Logging.warn(string.Format("[PL] Potential KEEPALIVE tampering for {0} {1}. Timestamp {2}", Base58Check.Base58CheckEncoding.EncodePlain(listEntry.wallet), pa.address, timestamp)); return(false); } // Update the timestamp pa.lastSeenTime = timestamp; pa.signature = signature; pa.version = keepAliveVersion; if (pa.type != node_type) { lock (presenceCount) { presenceCount[pa.type]--; if (!presenceCount.ContainsKey(node_type)) { presenceCount.Add(node_type, 0); } presenceCount[node_type]++; } } pa.type = node_type; //Console.WriteLine("[PL] LASTSEEN for {0} - {1} set to {2}", hostname, deviceid, pa.lastSeenTime); return(true); } } else { if (wallet.SequenceEqual(IxianHandler.getWalletStorage().getPrimaryAddress())) { curNodePresence.addresses.Clear(); curNodePresence.addresses.Add(curNodePresenceAddress); updateEntry(curNodePresence); return(true); } else { using (MemoryStream mw = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(mw)) { writer.Write(wallet.Length); writer.Write(wallet); if (endpoint != null && endpoint.isConnected()) { endpoint.sendData(ProtocolMessageCode.getPresence, mw.ToArray(), wallet); } else { CoreProtocolMessage.broadcastProtocolMessageToSingleRandomNode(new char[] { 'M', 'R', 'H' }, ProtocolMessageCode.getPresence, mw.ToArray(), 0, null); } } } return(false); } } } } } } catch (Exception e) { Logging.error("Exception occured in receiveKeepAlive: " + e); return(false); } return(false); }
/// <summary> /// Processes a Hello Ixian protocol message and updates the `PresenceList` as appropriate. /// </summary> /// <remarks> /// This function should normally be called from `NetworkProtocol.parseProtocolMessage()` /// </remarks> /// <param name="endpoint">Remote endpoint from which the message was received.</param> /// <param name="reader">Reader object placed at the beginning of the hello message data.</param> /// <returns>True if the message was formatted properly and accepted.</returns> public static bool processHelloMessage(RemoteEndpoint endpoint, BinaryReader reader) { // Node already has a presence if (endpoint.presence != null) { // Ignore the hello message in this case return(false); } // Another layer to catch any incompatible node exceptions for the hello message try { int protocol_version = reader.ReadInt32(); Logging.info(string.Format("Received Hello: Node version {0}", protocol_version)); // Check for incompatible nodes if (protocol_version < CoreConfig.protocolVersion) { Logging.warn(String.Format("Hello: Connected node version ({0}) is too old! Upgrade the node.", protocol_version)); sendBye(endpoint, ProtocolByeCode.deprecated, string.Format("Your node version is too old. Should be at least {0} is {1}", CoreConfig.protocolVersion, protocol_version), CoreConfig.protocolVersion.ToString(), true); return(false); } int addrLen = reader.ReadInt32(); byte[] addr = reader.ReadBytes(addrLen); bool test_net = reader.ReadBoolean(); char node_type = reader.ReadChar(); string node_version = reader.ReadString(); string device_id = reader.ReadString(); int pkLen = reader.ReadInt32(); byte[] pubkey = reader.ReadBytes(pkLen); endpoint.serverPubKey = pubkey; int port = reader.ReadInt32(); long timestamp = reader.ReadInt64(); int sigLen = reader.ReadInt32(); byte[] signature = reader.ReadBytes(sigLen); // Check the testnet designator and disconnect on mismatch if (test_net != CoreConfig.isTestNet) { Logging.warn(string.Format("Rejected node {0} due to incorrect testnet designator: {1}", endpoint.fullAddress, test_net)); sendBye(endpoint, ProtocolByeCode.incorrectNetworkType, string.Format("Incorrect testnet designator: {0}. Should be {1}", test_net, CoreConfig.isTestNet), test_net.ToString(), true); return(false); } // Check the address and pubkey and disconnect on mismatch if (!addr.SequenceEqual((new Address(pubkey)).address)) { Logging.warn(string.Format("Pubkey and address do not match.")); sendBye(endpoint, ProtocolByeCode.authFailed, "Pubkey and address do not match.", "", true); return(false); } endpoint.incomingPort = port; // Verify the signature if (node_type == 'C') { // TODO: verify if the client is connectable, then if connectable, check if signature verifies /*if (CryptoManager.lib.verifySignature(Encoding.UTF8.GetBytes(ConsensusConfig.ixianChecksumLockString + "-" + device_id + "-" + timestamp + "-" + endpoint.getFullAddress(true)), pubkey, signature) == false) * { * CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.incorrectIp, "Verify signature failed in hello message, likely an incorrect IP was specified. Detected IP:", endpoint.address); * Logging.warn(string.Format("Connected node used an incorrect signature in hello message, likely an incorrect IP was specified. Detected IP: {0}", endpoint.address)); * return false; * }*/ // TODO store the full address if connectable // Store the presence address for this remote endpoint endpoint.presenceAddress = new PresenceAddress(device_id, "", node_type, node_version, Core.getCurrentTimestamp() - CoreConfig.clientKeepAliveInterval, null); } else { if (!CryptoManager.lib.verifySignature(Encoding.UTF8.GetBytes(ConsensusConfig.ixianChecksumLockString + "-" + device_id + "-" + timestamp + "-" + endpoint.getFullAddress(true)), pubkey, signature)) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.incorrectIp, "Verify signature failed in hello message, likely an incorrect IP was specified. Detected IP:", endpoint.address); Logging.warn(string.Format("Connected node used an incorrect signature in hello message, likely an incorrect IP was specified. Detected IP: {0}", endpoint.address)); return(false); } // Store the presence address for this remote endpoint endpoint.presenceAddress = new PresenceAddress(device_id, endpoint.getFullAddress(true), node_type, node_version, Core.getCurrentTimestamp() - CoreConfig.serverKeepAliveInterval, null); } // if we're a client update the network time difference if (endpoint.GetType() == typeof(NetworkClient)) { long timeDiff = endpoint.calculateTimeDifference(); // amortize +- 2 seconds if (timeDiff >= -2 && timeDiff <= 2) { timeDiff = 0; } ((NetworkClient)endpoint).timeDifference = timeDiff; // Check the address and local address and disconnect on mismatch if (endpoint.serverWalletAddress != null && !addr.SequenceEqual(endpoint.serverWalletAddress)) { Logging.warn(string.Format("Local address mismatch, possible Man-in-the-middle attack.")); sendBye(endpoint, ProtocolByeCode.addressMismatch, "Local address mismatch.", "", true); return(false); } } // Create a temporary presence with the client's address and device id Presence presence = new Presence(addr, pubkey, null, endpoint.presenceAddress); if (endpoint.GetType() != typeof(NetworkClient)) { // we're the server if (node_type == 'M' || node_type == 'H' || node_type == 'R') { if (node_type != 'R') { // Check the wallet balance for the minimum amount of coins IxiNumber balance = IxianHandler.getWalletBalance(addr); if (balance < ConsensusConfig.minimumMasterNodeFunds) { Logging.warn(string.Format("Rejected master node {0} due to insufficient funds: {1}", endpoint.getFullAddress(), balance.ToString())); sendBye(endpoint, ProtocolByeCode.insufficientFunds, string.Format("Insufficient funds. Minimum is {0}", ConsensusConfig.minimumMasterNodeFunds), balance.ToString(), true); return(false); } } // Limit to one IP per masternode // TODO TODO TODO - think about this and do it properly /*string[] hostname_split = hostname.Split(':'); * if (PresenceList.containsIP(hostname_split[0], 'M')) * { * using (MemoryStream m2 = new MemoryStream()) * { * using (BinaryWriter writer = new BinaryWriter(m2)) * { * writer.Write(string.Format("This IP address ( {0} ) already has a masternode connected.", hostname_split[0])); * Logging.info(string.Format("Rejected master node {0} due to duplicate IP address", hostname)); * socket.Send(prepareProtocolMessage(ProtocolMessageCode.bye, m2.ToArray()), SocketFlags.None); * socket.Disconnect(true); * return; * } * } * }*/ if (!checkNodeConnectivity(endpoint)) { return(false); } } } // Retrieve the final presence entry from the list (or create a fresh one) endpoint.presence = PresenceList.updateEntry(presence); } catch (Exception e) { // Disconnect the node in case of any reading errors Logging.warn(string.Format("Exception occured in Hello Message {0}", e.ToString())); sendBye(endpoint, ProtocolByeCode.deprecated, "Something went wrong during hello, make sure you're running the latest version of Ixian DLT.", ""); return(false); } if (NetworkClientManager.getConnectedClients().Count() == 1) { PresenceList.forceSendKeepAlive = true; } return(true); }
/// <summary> /// Prepares and broadcasts an Ixian protocol message to all connected nodes, filtered by `types`. /// </summary> /// <remarks> /// The payload `data` should be properly formatted for the given `code` - this function will not ensure that this is so and /// the caller must provide a valid message to this function. /// The `skipEndpoint` parameter is useful when re-broadcasting a message received from a specific endpoint and do not wish to echo the same /// data back to the sender. /// </remarks> /// <param name="types">Types of nodes to send this message to.</param> /// <param name="code">Protocol code.</param> /// <param name="data">Message payload</param> /// <param name="helper_data">Additional information, as required by the protocol message</param> /// <param name="skipEndpoint">Remote endpoint which should be skipped (data should not be sent to it).</param> /// <returns>True, if at least one message was sent to at least one other node. False if no messages were sent.</returns> public static bool broadcastProtocolMessage(char[] types, ProtocolMessageCode code, byte[] data, byte[] helper_data, RemoteEndpoint skipEndpoint = null) { if (data == null) { Logging.warn(string.Format("Invalid protocol message data for {0}", code)); return(false); } bool c_result = NetworkClientManager.broadcastData(types, code, data, helper_data, skipEndpoint); bool s_result = NetworkServer.broadcastData(types, code, data, helper_data, skipEndpoint); if (!c_result && !s_result) { return(false); } return(true); }
public static void onChat(byte[] raw_message, StreamMessage message, int channel, RemoteEndpoint endpoint) { if (channel == 0) { return; } if (!Node.users.hasUser(endpoint.presence.wallet) || Node.users.getUser(endpoint.presence.wallet).nickData == null) { requestNickname(endpoint.presence.wallet); requestAvatar(endpoint.presence.wallet); } if (message.id == null) { return; } if (Messages.getMessage(message.id, channel) != null) { return; } IxiNumber price = getMessagePrice(message.sender, message.data.Length); if (price > 0) { // TODO TODO resend payment request after a while // TODO TODO remove pending message after a while StreamTransactionRequest str = new StreamTransactionRequest(message.id, price); if (pendingMessages.Find(x => x.id.SequenceEqual(message.id)) != null) { sendBotAction(message.sender, SpixiBotActionCode.getPayment, str.getBytes(), channel); return; } pendingMessages.Add(message); sendBotAction(message.sender, SpixiBotActionCode.getPayment, str.getBytes(), channel); } else { Messages.addMessage(message, channel); QuotaManager.addActivity(endpoint.presence.wallet, false); // Relay certain messages without transaction NetworkServer.forwardMessage(ProtocolMessageCode.s2data, raw_message); } }
abstract protected bool sendInventoryRequest(InventoryItem item, RemoteEndpoint endpoint);
public static void processBye(byte[] data, RemoteEndpoint endpoint) { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { endpoint.stop(); bool byeV1 = false; try { ProtocolByeCode byeCode = (ProtocolByeCode)reader.ReadInt32(); string byeMessage = reader.ReadString(); string byeData = reader.ReadString(); byeV1 = true; switch (byeCode) { case ProtocolByeCode.bye: // all good break; case ProtocolByeCode.incorrectIp: // incorrect IP if (IxiUtils.validateIPv4(byeData)) { if (NetworkClientManager.getConnectedClients(true).Length < 2) { IxianHandler.publicIP = byeData; Logging.warn("Changed internal IP Address to " + byeData + ", reconnecting"); } } break; case ProtocolByeCode.notConnectable: // not connectable from the internet NetworkServer.connectable = false; if (!NetworkServer.isConnectable()) { Logging.error("This node must be connectable from the internet, to connect to the network."); Logging.error("Please setup uPNP and/or port forwarding on your router for port " + IxianHandler.publicPort + "."); } break; default: Logging.warn("Disconnected with message: {0} {1} {2}", byeCode.ToString(), byeMessage, byeData); break; } } catch (Exception) { } if (byeV1) { return; } reader.BaseStream.Seek(0, SeekOrigin.Begin); // Retrieve the message string message = reader.ReadString(); if (message.Length > 0) { Logging.warn("Disconnected with v0 message: {0}", message); } else { Logging.warn("Disconnected v0"); } } } }
// Called when an encryption key is received from the S2 server, as per step 4 of the WhitePaper /*private static void sendRsaEncryptedMessage(StreamMessage msg, string key, RemoteEndpoint endpoint) * { * // TODO TODO use transaction code for S2 * using (MemoryStream m = new MemoryStream()) * { * using (BinaryWriter writer = new BinaryWriter(m)) * { * writer.Write(msg.getID()); * writer.Write(msg.recipientAddress); * writer.Write(msg.transactionID); * } * } * Console.WriteLine("Sending encrypted message with key {0}", key); * * using (MemoryStream m = new MemoryStream()) * { * using (BinaryWriter writer = new BinaryWriter(m)) * { * writer.Write(msg.getID()); * writer.Write(msg.recipientAddress); * writer.Write(msg.transactionID); * * byte[] encrypted_message = CryptoManager.lib.encryptDataS2(msg.data, key); * int encrypted_count = encrypted_message.Count(); * * writer.Write(encrypted_count); * writer.Write(encrypted_message); * * byte[] ba = ProtocolMessage.prepareProtocolMessage(ProtocolMessageCode.s2data, m.ToArray()); * socket.Send(ba, SocketFlags.None); * * * // Update the DLT transaction as well * Transaction transaction = new Transaction(0, msg.recipientAddress, Node.walletStorage.address); * transaction.id = msg.transactionID; * //transaction.data = Encoding.UTF8.GetString(checksum); * //ProtocolMessage.broadcastProtocolMessage(ProtocolMessageCode.updateTransaction, transaction.getBytes()); * * } * } * }*/ // Called when receiving S2 data from clients public static void receiveData(byte[] bytes, RemoteEndpoint endpoint) { StreamMessage message = new StreamMessage(bytes); if (message.data == null) { Logging.error(string.Format("Null message data.")); return; } Logging.info("Received S2 data from {0} for {1}", Base58Check.Base58CheckEncoding.EncodePlain(message.sender), Base58Check.Base58CheckEncoding.EncodePlain(message.recipient)); Friend friend = null; // decrypt the message if necessary // TODO TODO TODO add message receive queue for when the keys aren't available yet if (message.encryptionType != StreamMessageEncryptionCode.none) { byte[] aes_key = null; byte[] chacha_key = null; friend = FriendList.getFriend(message.sender); if (friend != null) { aes_key = friend.aesKey; chacha_key = friend.chachaKey; } if (!message.decrypt(Node.walletStorage.getPrimaryPrivateKey(), aes_key, chacha_key)) { Logging.error("Could not decrypt message from {0}", Base58Check.Base58CheckEncoding.EncodePlain(friend.walletAddress)); return; } } // Extract the Spixi message SpixiMessage spixi_message = new SpixiMessage(message.data); switch (spixi_message.type) { case SpixiMessageCode.chat: { // Add the message to the friend list FriendList.addMessage(spixi_message.id, message.sender, Encoding.UTF8.GetString(spixi_message.data)); } break; case SpixiMessageCode.getNick: { // Send the nickname to the sender as requested handleGetNick(message.sender, Encoding.UTF8.GetString(spixi_message.data)); } break; case SpixiMessageCode.nick: { // Set the nickname for the corresponding address if (spixi_message.data != null) { FriendList.setNickname(message.sender, Encoding.UTF8.GetString(spixi_message.data)); } else { FriendList.setNickname(message.sender, Base58Check.Base58CheckEncoding.EncodePlain(message.sender)); } } break; case SpixiMessageCode.requestAdd: { // Friend request handleRequestAdd(spixi_message.id, message.sender, spixi_message.data); } break; case SpixiMessageCode.acceptAdd: { // Friend accepted request handleAcceptAdd(message.sender, spixi_message.data); } break; case SpixiMessageCode.sentFunds: { // Friend requested funds handleSentFunds(spixi_message.id, message.sender, Encoding.UTF8.GetString(spixi_message.data)); } break; case SpixiMessageCode.requestFunds: { // Friend requested funds handleRequestFunds(spixi_message.id, message.sender, Encoding.UTF8.GetString(spixi_message.data)); } break; case SpixiMessageCode.requestFundsResponse: { handleRequestFundsResponse(spixi_message.id, message.sender, Encoding.UTF8.GetString(spixi_message.data)); } break; case SpixiMessageCode.keys: { handleReceivedKeys(message.sender, spixi_message.data); } break; case SpixiMessageCode.msgReceived: { handleMsgReceived(message.sender, spixi_message); // don't send confirmation back, so just return return; } case SpixiMessageCode.msgRead: { handleMsgRead(message.sender, spixi_message); // don't send confirmation back, so just return return; } case SpixiMessageCode.fileHeader: { handleFileHeader(message.sender, spixi_message); } break; } if (friend == null) { friend = FriendList.getFriend(message.sender); } if (friend == null) { Logging.error("Cannot send received confirmation, friend is null"); return; } // Send received confirmation StreamMessage msg_received = new StreamMessage(); msg_received.type = StreamMessageCode.info; msg_received.sender = IxianHandler.getWalletStorage().getPrimaryAddress(); msg_received.recipient = message.sender; msg_received.data = new SpixiMessage(spixi_message.id, SpixiMessageCode.msgReceived, null).getBytes(); msg_received.transaction = new byte[1]; msg_received.sigdata = new byte[1]; msg_received.encryptionType = StreamMessageEncryptionCode.none; sendMessage(friend, msg_received, true); }
/// <summary> /// Reads a protocol message from the specified byte-field and calls appropriate methods to process this message. /// </summary> /// <remarks> /// This function checks all applicable checksums and validates that the message is complete before calling one of the specialized /// methods to handle actual decoding and processing. /// </remarks> /// <param name="recv_buffer">Byte-field with an Ixian protocol message.</param> /// <param name="endpoint">Remote endpoint from where the message was received.</param> public static void readProtocolMessage(QueueMessageRaw raw_message, MessagePriority priority, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. readProtocolMessage"); return; } ProtocolMessageCode code = raw_message.code; // Filter messages if (endpoint.presence == null) { // Check for presence and only accept hello and bye messages if there is no presence. if (code != ProtocolMessageCode.hello && code != ProtocolMessageCode.helloData && code != ProtocolMessageCode.bye) { return; } } if (raw_message.legacyChecksum != null) { // Compute checksum of received data byte[] local_checksum = Crypto.sha512sqTrunc(raw_message.data, 0, 0, 32); // Verify the checksum before proceeding if (local_checksum.SequenceEqual(raw_message.legacyChecksum) == false) { Logging.error("Dropped message (invalid legacy checksum)"); return; } } else { // Compute checksum of received data uint local_checksum = Crc32CAlgorithm.Compute(raw_message.data); // Verify the checksum before proceeding if (local_checksum != raw_message.checksum) { Logging.error("Dropped message (invalid checksum)"); return; } } // Can proceed to parse the data parameter based on the protocol message code. // Data can contain multiple elements. //parseProtocolMessage(code, data, socket, endpoint); NetworkQueue.receiveProtocolMessage(code, raw_message.data, Crc32CAlgorithm.Compute(raw_message.data), priority, endpoint); }
/// <summary> /// Prepares and sends an Ixian protocol 'Hello' message to the specified remote endpoint. /// </summary> /// <remarks> /// A valid Ixian 'Hello' message includes certain Node data, verified by a public-key signature, which this function prepares using /// the primary wallet's keypair. If this message is a reply to the other endpoint's hello message, then /// </remarks> /// <param name="endpoint">Remote endpoint to send the message to.</param> /// <param name="sendHelloData">True if the message is the first hello sent to the remote node, false if it is a reply to the challenge.</param> /// <param name="challenge_response">Response byte-field to the other node's hello challenge</param> public static void sendHelloMessageV6(RemoteEndpoint endpoint, bool sendHelloData, int challenge) { using (MemoryStream m = new MemoryStream(1856)) { using (BinaryWriter writer = new BinaryWriter(m)) { string publicHostname = IxianHandler.getFullPublicAddress(); // Send the node version writer.WriteIxiVarInt(6); // Send the public node address byte[] address = IxianHandler.getWalletStorage().getPrimaryAddress(); writer.WriteIxiVarInt(address.Length); writer.Write(address); // Send the testnet designator writer.Write(IxianHandler.isTestNet); char node_type = PresenceList.myPresenceType; writer.Write(node_type); // Send the version writer.Write(CoreConfig.productVersion); // Send the node device id writer.WriteIxiVarInt(CoreConfig.device_id.Length); writer.Write(CoreConfig.device_id); // Send the wallet public key writer.WriteIxiVarInt(IxianHandler.getWalletStorage().getPrimaryPublicKey().Length); writer.Write(IxianHandler.getWalletStorage().getPrimaryPublicKey()); // Send listening port writer.WriteIxiVarInt(IxianHandler.publicPort); // Send timestamp long timestamp = Clock.getTimestamp() + endpoint.calculateTimeDifference(); writer.WriteIxiVarInt(timestamp); // generate signature using (MemoryStream mSig = new MemoryStream(1024)) { using (BinaryWriter sigWriter = new BinaryWriter(mSig)) { sigWriter.Write(ConsensusConfig.ixianChecksumLock); sigWriter.Write(CoreConfig.device_id); sigWriter.Write(timestamp); sigWriter.Write(publicHostname); sigWriter.Write(challenge); } byte[] signature = CryptoManager.lib.getSignature(mSig.ToArray(), IxianHandler.getWalletStorage().getPrimaryPrivateKey()); writer.WriteIxiVarInt(signature.Length); writer.Write(signature); } if (sendHelloData) { Block block = IxianHandler.getLastBlock(); if (block == null) { Logging.warn("Clients are connecting, but we have no blocks yet to send them!"); sendBye(endpoint, ProtocolByeCode.notReady, string.Format("The node isn't ready yet, please try again later."), "", true); return; } writer.WriteIxiVarInt(block.blockNum); writer.WriteIxiVarInt(block.blockChecksum.Length); writer.Write(block.blockChecksum); writer.WriteIxiVarInt(block.version); writer.Write(endpoint.getFullAddress(true)); #if TRACE_MEMSTREAM_SIZES Logging.info(String.Format("CoreProtocolMessage::sendHelloMessage: {0}", m.Length)); #endif endpoint.sendData(ProtocolMessageCode.helloData, m.ToArray()); } else { byte[] challenge_bytes = IxiVarInt.GetIxiVarIntBytes(challenge); endpoint.challenge = BitConverter.GetBytes(challenge); writer.Write(challenge_bytes); #if TRACE_MEMSTREAM_SIZES Logging.info(String.Format("CoreProtocolMessage::sendHelloMessage: {0}", m.Length)); #endif endpoint.sendData(ProtocolMessageCode.hello, m.ToArray()); } } } }
static void handleInventory2(byte[] data, RemoteEndpoint endpoint) { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { ulong item_count = reader.ReadIxiVarUInt(); if (item_count > (ulong)CoreConfig.maxInventoryItems) { Logging.warn("Received {0} inventory items, max items is {1}", item_count, CoreConfig.maxInventoryItems); item_count = (ulong)CoreConfig.maxInventoryItems; } ulong last_block_height = IxianHandler.getLastBlockHeight(); Dictionary <ulong, List <InventoryItemSignature> > sig_lists = new Dictionary <ulong, List <InventoryItemSignature> >(); List <InventoryItemKeepAlive> ka_list = new List <InventoryItemKeepAlive>(); List <byte[]> tx_list = new List <byte[]>(); bool request_next_block = false; for (ulong i = 0; i < item_count; i++) { ulong len = reader.ReadIxiVarUInt(); byte[] item_bytes = reader.ReadBytes((int)len); InventoryItem item = InventoryCache.decodeInventoryItem(item_bytes); if (item.type == InventoryItemTypes.transaction) { PendingTransactions.increaseReceivedCount(item.hash, endpoint.presence.wallet); } PendingInventoryItem pii = Node.inventoryCache.add(item, endpoint); if (!pii.processed && pii.lastRequested == 0) { // first time we're seeing this inventory item switch (item.type) { case InventoryItemTypes.keepAlive: ka_list.Add((InventoryItemKeepAlive)item); pii.lastRequested = Clock.getTimestamp(); break; case InventoryItemTypes.transaction: tx_list.Add(item.hash); pii.lastRequested = Clock.getTimestamp(); break; case InventoryItemTypes.blockSignature: var iis = (InventoryItemSignature)item; if (iis.blockNum < last_block_height - 5 && iis.blockNum > last_block_height + 6) { continue; } if (!sig_lists.ContainsKey(iis.blockNum)) { sig_lists.Add(iis.blockNum, new List <InventoryItemSignature>()); } sig_lists[iis.blockNum].Add(iis); pii.lastRequested = Clock.getTimestamp(); break; case InventoryItemTypes.block: var iib = ((InventoryItemBlock)item); if (iib.blockNum <= last_block_height) { Node.inventoryCache.processInventoryItem(pii); } else { pii.lastRequested = Clock.getTimestamp(); request_next_block = true; if (iib.blockNum > endpoint.blockHeight) { endpoint.blockHeight = iib.blockNum; } if (iib.blockNum > Node.blockProcessor.highestNetworkBlockNum) { Node.blockProcessor.highestNetworkBlockNum = iib.blockNum; } } break; default: Node.inventoryCache.processInventoryItem(pii); break; } } } PresenceProtocolMessages.broadcastGetKeepAlives(ka_list, endpoint); if (Node.blockSync.synchronizing) { return; } TransactionProtocolMessages.broadcastGetTransactions(tx_list, 0, endpoint); if (request_next_block) { byte include_tx = 2; if (Node.isMasterNode()) { include_tx = 0; } BlockProtocolMessages.broadcastGetBlock(last_block_height + 1, null, endpoint, include_tx, true); } foreach (var sig_list in sig_lists) { SignatureProtocolMessages.broadcastGetSignatures(sig_list.Key, sig_list.Value, endpoint); } } } }
// Unified protocol message parsing public static void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. parseProtocolMessage"); return; } try { switch (code) { case ProtocolMessageCode.hello: handleHello(data, endpoint); break; case ProtocolMessageCode.helloData: handleHelloData(data, endpoint); break; case ProtocolMessageCode.getBlock: BlockProtocolMessages.handleGetBlock(data, endpoint); break; case ProtocolMessageCode.getBalance: WalletStateProtocolMessages.handleGetBalance(data, endpoint); break; case ProtocolMessageCode.getTransaction: TransactionProtocolMessages.handleGetTransaction(data, endpoint); break; case ProtocolMessageCode.getTransaction2: TransactionProtocolMessages.handleGetTransaction2(data, endpoint); break; case ProtocolMessageCode.getTransaction3: TransactionProtocolMessages.handleGetTransaction3(data, endpoint); break; case ProtocolMessageCode.newTransaction: case ProtocolMessageCode.transactionData: TransactionProtocolMessages.handleTransactionData(data, endpoint); break; case ProtocolMessageCode.bye: CoreProtocolMessage.processBye(data, endpoint); break; case ProtocolMessageCode.newBlock: case ProtocolMessageCode.blockData: BlockProtocolMessages.handleBlockData(data, endpoint); break; case ProtocolMessageCode.syncWalletState: WalletStateProtocolMessages.handleSyncWalletState(data, endpoint); break; case ProtocolMessageCode.walletState: WalletStateProtocolMessages.handleWalletState(data, endpoint); break; case ProtocolMessageCode.getWalletStateChunk: WalletStateProtocolMessages.handleGetWalletStateChunk(data, endpoint); break; case ProtocolMessageCode.walletStateChunk: WalletStateProtocolMessages.handleWalletStateChunk(data, endpoint); break; case ProtocolMessageCode.updatePresence: PresenceProtocolMessages.handleUpdatePresence(data, endpoint); break; case ProtocolMessageCode.keepAlivePresence: PresenceProtocolMessages.handleKeepAlivePresence(data, endpoint); break; case ProtocolMessageCode.getPresence: PresenceProtocolMessages.handleGetPresence(data, endpoint); break; case ProtocolMessageCode.getPresence2: PresenceProtocolMessages.handleGetPresence2(data, endpoint); break; case ProtocolMessageCode.getKeepAlives: PresenceProtocolMessages.handleGetKeepAlives(data, endpoint); break; case ProtocolMessageCode.keepAlivesChunk: PresenceProtocolMessages.handleKeepAlivesChunk(data, endpoint); break; // return 10 random presences of the selected type case ProtocolMessageCode.getRandomPresences: PresenceProtocolMessages.handleGetRandomPresences(data, endpoint); break; case ProtocolMessageCode.getUnappliedTransactions: TransactionProtocolMessages.handleGetUnappliedTransactions(data, endpoint); break; case ProtocolMessageCode.blockTransactionsChunk: BlockProtocolMessages.handleBlockTransactionsChunk(data, endpoint); break; case ProtocolMessageCode.attachEvent: NetworkEvents.handleAttachEventMessage(data, endpoint); break; case ProtocolMessageCode.detachEvent: NetworkEvents.handleDetachEventMessage(data, endpoint); break; case ProtocolMessageCode.blockSignature: SignatureProtocolMessages.handleBlockSignature(data, endpoint); break; case ProtocolMessageCode.getBlockSignatures: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { ulong block_num = reader.ReadUInt64(); int checksum_len = reader.ReadInt32(); byte[] checksum = reader.ReadBytes(checksum_len); SignatureProtocolMessages.handleGetBlockSignatures(block_num, checksum, endpoint); } } } break; case ProtocolMessageCode.blockSignatures: SignatureProtocolMessages.handleSigfreezedBlockSignatures(data, endpoint); break; case ProtocolMessageCode.getNextSuperBlock: BlockProtocolMessages.handleGetNextSuperBlock(data, endpoint); break; case ProtocolMessageCode.getBlockHeaders: BlockProtocolMessages.handleGetBlockHeaders(data, endpoint); break; case ProtocolMessageCode.getPIT: BlockProtocolMessages.handleGetPIT(data, endpoint); break; case ProtocolMessageCode.inventory: handleInventory(data, endpoint); break; case ProtocolMessageCode.inventory2: handleInventory2(data, endpoint); break; case ProtocolMessageCode.getSignatures: SignatureProtocolMessages.handleGetSignatures(data, endpoint); break; case ProtocolMessageCode.signaturesChunk: SignatureProtocolMessages.handleSignaturesChunk(data, endpoint); break; case ProtocolMessageCode.getTransactions: TransactionProtocolMessages.handleGetTransactions(data, endpoint); break; case ProtocolMessageCode.getTransactions2: TransactionProtocolMessages.handleGetTransactions2(data, endpoint); break; case ProtocolMessageCode.transactionsChunk: TransactionProtocolMessages.handleTransactionsChunk(data, endpoint); break; case ProtocolMessageCode.transactionsChunk2: TransactionProtocolMessages.handleTransactionsChunk2(data, endpoint); break; case ProtocolMessageCode.getBlockHeaders2: BlockProtocolMessages.handleGetBlockHeaders2(data, endpoint); break; case ProtocolMessageCode.getPIT2: BlockProtocolMessages.handleGetPIT2(data, endpoint); break; case ProtocolMessageCode.getBlock2: BlockProtocolMessages.handleGetBlock2(data, endpoint); break; case ProtocolMessageCode.getBlock3: BlockProtocolMessages.handleGetBlock3(data, endpoint); break; case ProtocolMessageCode.getBalance2: WalletStateProtocolMessages.handleGetBalance2(data, endpoint); break; case ProtocolMessageCode.getBlockSignatures2: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { ulong block_num = reader.ReadIxiVarUInt(); int checksum_len = (int)reader.ReadIxiVarUInt(); byte[] checksum = reader.ReadBytes(checksum_len); SignatureProtocolMessages.handleGetBlockSignatures2(block_num, checksum, endpoint); } } } break; case ProtocolMessageCode.blockSignature2: SignatureProtocolMessages.handleBlockSignature2(data, endpoint); break; default: break; } } catch (Exception e) { Logging.error("Error parsing network message. Details: {0}", e.ToString()); } }
// Unified protocol message parsing public static void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. parseProtocolMessage"); return; } try { switch (code) { case ProtocolMessageCode.hello: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (CoreProtocolMessage.processHelloMessage(endpoint, reader)) { byte[] challenge_response = null; int challenge_len = reader.ReadInt32(); byte[] challenge = reader.ReadBytes(challenge_len); challenge_response = CryptoManager.lib.getSignature(challenge, IxianHandler.getWalletStorage().getPrimaryPrivateKey()); CoreProtocolMessage.sendHelloMessage(endpoint, true, challenge_response); endpoint.helloReceived = true; return; } } } } break; case ProtocolMessageCode.helloData: using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (!CoreProtocolMessage.processHelloMessage(endpoint, reader)) { return; } ulong last_block_num = reader.ReadUInt64(); int bcLen = reader.ReadInt32(); byte[] block_checksum = reader.ReadBytes(bcLen); int wsLen = reader.ReadInt32(); byte[] walletstate_checksum = reader.ReadBytes(wsLen); int consensus = reader.ReadInt32(); // deprecated endpoint.blockHeight = last_block_num; int block_version = reader.ReadInt32(); // Check for legacy level ulong legacy_level = reader.ReadUInt64(); // deprecated int challenge_response_len = reader.ReadInt32(); byte[] challenge_response = reader.ReadBytes(challenge_response_len); if (!CryptoManager.lib.verifySignature(endpoint.challenge, endpoint.serverPubKey, challenge_response)) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.authFailed, string.Format("Invalid challenge response."), "", true); return; } ulong highest_block_height = IxianHandler.getHighestKnownNetworkBlockHeight(); if (last_block_num + 10 < highest_block_height) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.tooFarBehind, string.Format("Your node is too far behind, your block height is {0}, highest network block height is {1}.", last_block_num, highest_block_height), highest_block_height.ToString(), true); return; } // Process the hello data endpoint.helloReceived = true; NetworkClientManager.recalculateLocalTimeDifference(); if (endpoint.presenceAddress.type == 'R') { string[] connected_servers = StreamClientManager.getConnectedClients(true); if (connected_servers.Count() == 1 || !connected_servers.Contains(StreamClientManager.primaryS2Address)) { if (StreamClientManager.primaryS2Address == "") { FriendList.requestAllFriendsPresences(); } // TODO set the primary s2 host more efficiently, perhaps allow for multiple s2 primary hosts StreamClientManager.primaryS2Address = endpoint.getFullAddress(true); // TODO TODO do not set if directly connectable IxianHandler.publicIP = endpoint.address; IxianHandler.publicPort = endpoint.incomingPort; PresenceList.forceSendKeepAlive = true; Logging.info("Forcing KA from networkprotocol"); } } else if (endpoint.presenceAddress.type == 'C') { Friend f = FriendList.getFriend(endpoint.presence.wallet); if (f != null && f.bot) { StreamProcessor.sendGetMessages(f); } } if (endpoint.presenceAddress.type == 'M') { Node.setNetworkBlock(last_block_num, block_checksum, block_version); // Get random presences endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'R' }); endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'M' }); subscribeToEvents(endpoint); } } } break; case ProtocolMessageCode.s2data: { StreamProcessor.receiveData(data, endpoint); } break; case ProtocolMessageCode.updatePresence: { Logging.info("NET: Receiving presence list update"); // Parse the data and update entries in the presence list Presence p = PresenceList.updateFromBytes(data); } break; case ProtocolMessageCode.keepAlivePresence: { byte[] address = null; bool updated = PresenceList.receiveKeepAlive(data, out address, endpoint); } break; case ProtocolMessageCode.getPresence: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int walletLen = reader.ReadInt32(); byte[] wallet = reader.ReadBytes(walletLen); Presence p = PresenceList.getPresenceByAddress(wallet); if (p != null) { lock (p) { byte[][] presence_chunks = p.getByteChunks(); foreach (byte[] presence_chunk in presence_chunks) { endpoint.sendData(ProtocolMessageCode.updatePresence, presence_chunk, null); } } } else { // TODO blacklisting point Logging.warn(string.Format("Node has requested presence information about {0} that is not in our PL.", Base58Check.Base58CheckEncoding.EncodePlain(wallet))); } } } } break; case ProtocolMessageCode.balance: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int address_length = reader.ReadInt32(); byte[] address = reader.ReadBytes(address_length); // Retrieve the latest balance IxiNumber balance = reader.ReadString(); if (address.SequenceEqual(Node.walletStorage.getPrimaryAddress())) { // Retrieve the blockheight for the balance ulong block_height = reader.ReadUInt64(); if (block_height > Node.balance.blockHeight && (Node.balance.balance != balance || Node.balance.blockHeight == 0)) { byte[] block_checksum = reader.ReadBytes(reader.ReadInt32()); Node.balance.address = address; Node.balance.balance = balance; Node.balance.blockHeight = block_height; Node.balance.blockChecksum = block_checksum; Node.balance.lastUpdate = Clock.getTimestamp(); Node.balance.verified = false; } } } } } break; case ProtocolMessageCode.newTransaction: case ProtocolMessageCode.transactionData: { // TODO: check for errors/exceptions Transaction transaction = new Transaction(data, true); PendingTransactions.increaseReceivedCount(transaction.id); TransactionCache.addUnconfirmedTransaction(transaction); Node.tiv.receivedNewTransaction(transaction); } break; case ProtocolMessageCode.bye: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { endpoint.stop(); bool byeV1 = false; try { ProtocolByeCode byeCode = (ProtocolByeCode)reader.ReadInt32(); string byeMessage = reader.ReadString(); string byeData = reader.ReadString(); byeV1 = true; switch (byeCode) { case ProtocolByeCode.bye: // all good break; case ProtocolByeCode.forked: // forked node disconnected Logging.info(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; case ProtocolByeCode.deprecated: // deprecated node disconnected Logging.info(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; case ProtocolByeCode.incorrectIp: // incorrect IP if (IxiUtils.validateIPv4(byeData)) { if (NetworkClientManager.getConnectedClients(true).Length < 2) { // TODO TODO do not set if not directly connectable IxianHandler.publicIP = byeData; Logging.info("Changed internal IP Address to " + byeData + ", reconnecting"); } } break; case ProtocolByeCode.notConnectable: // not connectable from the internet Logging.error("This node must be connectable from the internet, to connect to the network."); Logging.error("Please setup uPNP and/or port forwarding on your router for port " + IxianHandler.publicPort + "."); NetworkServer.connectable = false; break; default: Logging.warn(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; } } catch (Exception) { } if (byeV1) { return; } reader.BaseStream.Seek(0, SeekOrigin.Begin); // Retrieve the message string message = reader.ReadString(); if (message.Length > 0) { Logging.info(string.Format("Disconnected with message: {0}", message)); } else { Logging.info("Disconnected"); } } } } break; case ProtocolMessageCode.blockHeaders: { // Forward the block headers to the TIV handler Node.tiv.receivedBlockHeaders(data, endpoint); } break; case ProtocolMessageCode.pitData: { Node.tiv.receivedPIT(data, endpoint); } break; default: break; } } catch (Exception e) { Logging.error(string.Format("Error parsing network message. Details: {0}", e.ToString())); } if (waitingFor == code) { blocked = false; } }
private static void sendReceivedConfirmation(byte[] recipientAddress, byte[] messageId, int channel, RemoteEndpoint endpoint) { // Send received confirmation StreamMessage msg_received = new StreamMessage(); msg_received.type = StreamMessageCode.info; msg_received.sender = IxianHandler.getWalletStorage().getPrimaryAddress(); msg_received.recipient = recipientAddress; msg_received.data = new SpixiMessage(SpixiMessageCode.msgReceived, messageId, channel).getBytes(); msg_received.encryptionType = StreamMessageEncryptionCode.none; sendMessage(endpoint.presence.wallet, msg_received); }
// Unified protocol message parsing public static void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. parseProtocolMessage"); return; } try { switch (code) { case ProtocolMessageCode.hello: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (data[0] == 5) { CoreProtocolMessage.processHelloMessageV5(endpoint, reader); } else { CoreProtocolMessage.processHelloMessageV6(endpoint, reader); } } } } break; case ProtocolMessageCode.helloData: using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (data[0] == 5) { if (!CoreProtocolMessage.processHelloMessageV5(endpoint, reader)) { return; } ulong last_block_num = reader.ReadUInt64(); int bcLen = reader.ReadInt32(); byte[] block_checksum = reader.ReadBytes(bcLen); int wsLen = reader.ReadInt32(); byte[] walletstate_checksum = reader.ReadBytes(wsLen); int consensus = reader.ReadInt32(); // deprecated endpoint.blockHeight = last_block_num; int block_version = reader.ReadInt32(); // Check for legacy level ulong legacy_level = reader.ReadUInt64(); // deprecated int challenge_response_len = reader.ReadInt32(); byte[] challenge_response = reader.ReadBytes(challenge_response_len); if (!CryptoManager.lib.verifySignature(endpoint.challenge, endpoint.serverPubKey, challenge_response)) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.authFailed, string.Format("Invalid challenge response."), "", true); return; } if (endpoint.presenceAddress.type != 'C') { ulong highest_block_height = IxianHandler.getHighestKnownNetworkBlockHeight(); if (last_block_num + 10 < highest_block_height) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.tooFarBehind, string.Format("Your node is too far behind, your block height is {0}, highest network block height is {1}.", last_block_num, highest_block_height), highest_block_height.ToString(), true); return; } } // Process the hello data endpoint.helloReceived = true; NetworkClientManager.recalculateLocalTimeDifference(); if (endpoint.presenceAddress.type == 'R') { string[] connected_servers = StreamClientManager.getConnectedClients(true); if (connected_servers.Count() == 1 || !connected_servers.Contains(StreamClientManager.primaryS2Address)) { if (StreamClientManager.primaryS2Address == "") { FriendList.requestAllFriendsPresences(); } // TODO set the primary s2 host more efficiently, perhaps allow for multiple s2 primary hosts StreamClientManager.primaryS2Address = endpoint.getFullAddress(true); // TODO TODO do not set if directly connectable IxianHandler.publicIP = endpoint.address; IxianHandler.publicPort = endpoint.incomingPort; PresenceList.forceSendKeepAlive = true; Logging.info("Forcing KA from networkprotocol"); } } else if (endpoint.presenceAddress.type == 'C') { Friend f = FriendList.getFriend(endpoint.presence.wallet); if (f != null && f.bot) { StreamProcessor.sendGetBotInfo(f); } } if (endpoint.presenceAddress.type == 'M' || endpoint.presenceAddress.type == 'H') { Node.setNetworkBlock(last_block_num, block_checksum, block_version); // Get random presences endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'R' }); endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'M' }); endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'H' }); subscribeToEvents(endpoint); } } else { if (!CoreProtocolMessage.processHelloMessageV6(endpoint, reader)) { return; } ulong last_block_num = reader.ReadIxiVarUInt(); int bcLen = (int)reader.ReadIxiVarUInt(); byte[] block_checksum = reader.ReadBytes(bcLen); endpoint.blockHeight = last_block_num; int block_version = (int)reader.ReadIxiVarUInt(); if (endpoint.presenceAddress.type != 'C') { ulong highest_block_height = IxianHandler.getHighestKnownNetworkBlockHeight(); if (last_block_num + 10 < highest_block_height) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.tooFarBehind, string.Format("Your node is too far behind, your block height is {0}, highest network block height is {1}.", last_block_num, highest_block_height), highest_block_height.ToString(), true); return; } } // Process the hello data endpoint.helloReceived = true; NetworkClientManager.recalculateLocalTimeDifference(); if (endpoint.presenceAddress.type == 'R') { string[] connected_servers = StreamClientManager.getConnectedClients(true); if (connected_servers.Count() == 1 || !connected_servers.Contains(StreamClientManager.primaryS2Address)) { if (StreamClientManager.primaryS2Address == "") { FriendList.requestAllFriendsPresences(); } // TODO set the primary s2 host more efficiently, perhaps allow for multiple s2 primary hosts StreamClientManager.primaryS2Address = endpoint.getFullAddress(true); // TODO TODO do not set if directly connectable IxianHandler.publicIP = endpoint.address; IxianHandler.publicPort = endpoint.incomingPort; PresenceList.forceSendKeepAlive = true; Logging.info("Forcing KA from networkprotocol"); } } else if (endpoint.presenceAddress.type == 'C') { Friend f = FriendList.getFriend(endpoint.presence.wallet); if (f != null && f.bot) { StreamProcessor.sendGetBotInfo(f); } } if (endpoint.presenceAddress.type == 'M' || endpoint.presenceAddress.type == 'H') { Node.setNetworkBlock(last_block_num, block_checksum, block_version); // Get random presences endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'R' }); endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'M' }); endpoint.sendData(ProtocolMessageCode.getRandomPresences, new byte[1] { (byte)'H' }); subscribeToEvents(endpoint); } } } } break; case ProtocolMessageCode.s2data: { StreamProcessor.receiveData(data, endpoint); } break; case ProtocolMessageCode.updatePresence: { Logging.info("NET: Receiving presence list update"); // Parse the data and update entries in the presence list Presence p = PresenceList.updateFromBytes(data); if (p == null) { return; } Friend f = FriendList.getFriend(p.wallet); if (f != null) { f.relayIP = p.addresses[0].address; } } break; case ProtocolMessageCode.keepAlivePresence: { byte[] address = null; long last_seen = 0; byte[] device_id = null; bool updated = PresenceList.receiveKeepAlive(data, out address, out last_seen, out device_id, endpoint); Presence p = PresenceList.getPresenceByAddress(address); if (p == null) { return; } Friend f = FriendList.getFriend(p.wallet); if (f != null) { f.relayIP = p.addresses[0].address; } } break; case ProtocolMessageCode.getPresence: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int walletLen = reader.ReadInt32(); byte[] wallet = reader.ReadBytes(walletLen); Presence p = PresenceList.getPresenceByAddress(wallet); if (p != null) { lock (p) { byte[][] presence_chunks = p.getByteChunks(); foreach (byte[] presence_chunk in presence_chunks) { endpoint.sendData(ProtocolMessageCode.updatePresence, presence_chunk, null); } } } else { // TODO blacklisting point Logging.warn(string.Format("Node has requested presence information about {0} that is not in our PL.", Base58Check.Base58CheckEncoding.EncodePlain(wallet))); } } } } break; case ProtocolMessageCode.getPresence2: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int walletLen = (int)reader.ReadIxiVarUInt(); byte[] wallet = reader.ReadBytes(walletLen); Presence p = PresenceList.getPresenceByAddress(wallet); if (p != null) { lock (p) { byte[][] presence_chunks = p.getByteChunks(); foreach (byte[] presence_chunk in presence_chunks) { endpoint.sendData(ProtocolMessageCode.updatePresence, presence_chunk, null); } } } else { // TODO blacklisting point Logging.warn(string.Format("Node has requested presence information about {0} that is not in our PL.", Base58Check.Base58CheckEncoding.EncodePlain(wallet))); } } } } break; case ProtocolMessageCode.balance: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int address_length = reader.ReadInt32(); byte[] address = reader.ReadBytes(address_length); // Retrieve the latest balance IxiNumber balance = reader.ReadString(); if (address.SequenceEqual(Node.walletStorage.getPrimaryAddress())) { // Retrieve the blockheight for the balance ulong block_height = reader.ReadUInt64(); if (block_height > Node.balance.blockHeight && (Node.balance.balance != balance || Node.balance.blockHeight == 0)) { byte[] block_checksum = reader.ReadBytes(reader.ReadInt32()); Node.balance.address = address; Node.balance.balance = balance; Node.balance.blockHeight = block_height; Node.balance.blockChecksum = block_checksum; Node.balance.verified = false; } Node.balance.lastUpdate = Clock.getTimestamp(); } } } } break; case ProtocolMessageCode.balance2: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int address_length = (int)reader.ReadIxiVarUInt(); byte[] address = reader.ReadBytes(address_length); // Retrieve the latest balance IxiNumber balance = new IxiNumber(new BigInteger(reader.ReadBytes((int)reader.ReadIxiVarUInt()))); if (address.SequenceEqual(Node.walletStorage.getPrimaryAddress())) { // Retrieve the blockheight for the balance ulong block_height = reader.ReadIxiVarUInt(); if (block_height > Node.balance.blockHeight && (Node.balance.balance != balance || Node.balance.blockHeight == 0)) { byte[] block_checksum = reader.ReadBytes((int)reader.ReadIxiVarUInt()); Node.balance.address = address; Node.balance.balance = balance; Node.balance.blockHeight = block_height; Node.balance.blockChecksum = block_checksum; Node.balance.verified = false; } Node.balance.lastUpdate = Clock.getTimestamp(); } } } } break; case ProtocolMessageCode.newTransaction: case ProtocolMessageCode.transactionData: { // TODO: check for errors/exceptions Transaction transaction = new Transaction(data, true); if (endpoint.presenceAddress.type == 'M' || endpoint.presenceAddress.type == 'H') { PendingTransactions.increaseReceivedCount(transaction.id, endpoint.presence.wallet); } TransactionCache.addUnconfirmedTransaction(transaction); Node.tiv.receivedNewTransaction(transaction); } break; case ProtocolMessageCode.bye: CoreProtocolMessage.processBye(data, endpoint); break; case ProtocolMessageCode.blockHeaders2: { // Forward the block headers to the TIV handler Node.tiv.receivedBlockHeaders2(data, endpoint); } break; case ProtocolMessageCode.pitData2: { Node.tiv.receivedPIT2(data, endpoint); } break; default: break; } } catch (Exception e) { Logging.error("Error parsing network message. Details: {0}", e.ToString()); } }
public static void onMsgReaction(StreamMessage reaction_msg, byte[] msg_reaction_data, int channel, RemoteEndpoint endpoint) { SpixiMessageReaction smr = new SpixiMessageReaction(msg_reaction_data); StreamMessage msg = Messages.getMessage(smr.msgId, channel); if (msg == null) { return; } Messages.addMessage(reaction_msg, channel, false); NetworkServer.forwardMessage(ProtocolMessageCode.s2data, reaction_msg.getBytes()); }
public static void handleGetPIT2(byte[] data, RemoteEndpoint endpoint) { MemoryStream ms = new MemoryStream(data); using (BinaryReader r = new BinaryReader(ms)) { ulong block_num = r.ReadIxiVarUInt(); int filter_len = (int)r.ReadIxiVarUInt(); byte[] filter = r.ReadBytes(filter_len); Cuckoo cf; try { cf = new Cuckoo(filter); } catch (Exception) { Logging.warn("The Cuckoo filter in the getPIT message was invalid or corrupted!"); return; } Block b = Node.blockChain.getBlock(block_num, true, true); if (b is null) { return; } if (b.version < BlockVer.v6) { Logging.warn("Neighbor {0} requested PIT information for block {0}, which was below the minimal PIT version.", endpoint.fullAddress, block_num); return; } PrefixInclusionTree pit = new PrefixInclusionTree(44, 3); List <byte[]> interesting_transactions = new List <byte[]>(); foreach (var tx in b.transactions) { if (b.version < BlockVer.v8) { pit.add(UTF8Encoding.UTF8.GetBytes(Transaction.txIdV8ToLegacy(tx))); if (cf.Contains(tx)) { interesting_transactions.Add(UTF8Encoding.UTF8.GetBytes(Transaction.txIdV8ToLegacy(tx))); } } else { pit.add(tx); if (cf.Contains(tx)) { interesting_transactions.Add(tx); } } } // make sure we ended up with the correct PIT if (!b.pitChecksum.SequenceEqual(pit.calculateTreeHash())) { // This is a serious error, but I am not sure how to respond to it right now. Logging.error("Reconstructed PIT for block {0} does not match the checksum in block header!", block_num); return; } byte[] minimal_pit = pit.getMinimumTreeTXList(interesting_transactions); MemoryStream mOut = new MemoryStream(minimal_pit.Length + 12); using (BinaryWriter w = new BinaryWriter(mOut, Encoding.UTF8, true)) { w.WriteIxiVarInt(block_num); w.WriteIxiVarInt(minimal_pit.Length); w.Write(minimal_pit); } endpoint.sendData(ProtocolMessageCode.pitData2, mOut.ToArray()); } }
public static void onBotAction(byte[] action_data, RemoteEndpoint endpoint, int channel = 0) { SpixiBotAction sba = new SpixiBotAction(action_data); switch (sba.action) { case SpixiBotActionCode.getChannels: sendChannels(endpoint); break; case SpixiBotActionCode.getInfo: Node.users.setPubKey(endpoint.presence.wallet, endpoint.serverPubKey, false); sendInfo(endpoint.presence.wallet); break; case SpixiBotActionCode.getUsers: sendUsers(endpoint); break; case SpixiBotActionCode.getUser: sendUser(endpoint.presence.wallet, Node.users.getUser(sba.data)); break; case SpixiBotActionCode.payment: StreamTransaction stream_tx = new StreamTransaction(sba.data); if (!stream_tx.transaction.toList.Keys.First().SequenceEqual(IxianHandler.getWalletStorage().getPrimaryAddress())) { Logging.warn("Received transaction txid " + stream_tx.transaction.id + " from " + Base58Check.Base58CheckEncoding.EncodePlain(endpoint.presence.wallet) + " that's not for this node."); return; } StreamMessage sm = pendingMessages.Find(x => x.id.SequenceEqual(stream_tx.messageID)); if (sm == null) { // TODO TODO TODO send get message request to the client Logging.warn("Received transaction txid " + stream_tx.transaction.id + " from " + Base58Check.Base58CheckEncoding.EncodePlain(endpoint.presence.wallet) + " but have no message for this transaction."); return; } IxiNumber price = getMessagePrice(sm.sender, sm.data.Length); if (stream_tx.transaction.amount < price) { Logging.warn("Received transaction txid " + stream_tx.transaction.id + " from " + Base58Check.Base58CheckEncoding.EncodePlain(endpoint.presence.wallet) + " that has lower than expected amount."); return; } CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.transactionData, stream_tx.transaction.getBytes(), null); CoreProtocolMessage.broadcastGetTransaction(stream_tx.transaction.id, 0, null, false); PendingTransactions.addPendingLocalTransaction(stream_tx.transaction, stream_tx.messageID); break; case SpixiBotActionCode.enableNotifications: bool send_notifications = false; if (sba.data[0] == 1) { send_notifications = true; } Node.users.getUser(endpoint.presence.wallet).sendNotification = send_notifications; Node.users.writeContactsToFile(); break; } }
public static bool broadcastGetBlock(ulong block_num, RemoteEndpoint skipEndpoint = null, RemoteEndpoint endpoint = null, byte include_transactions = 0, bool full_header = false) { using (MemoryStream mw = new MemoryStream()) { using (BinaryWriter writerw = new BinaryWriter(mw)) { writerw.WriteIxiVarInt(block_num); writerw.Write(include_transactions); writerw.Write(full_header); #if TRACE_MEMSTREAM_SIZES Logging.info(String.Format("NetworkProtocol::broadcastGetBlock: {0}", mw.Length)); #endif if (endpoint != null) { if (endpoint.isConnected()) { endpoint.sendData(ProtocolMessageCode.getBlock3, mw.ToArray()); return(true); } } return(CoreProtocolMessage.broadcastProtocolMessageToSingleRandomNode(new char[] { 'M', 'H' }, ProtocolMessageCode.getBlock3, mw.ToArray(), block_num, skipEndpoint)); } } }
/// <summary> /// Reads a protocol message from the specified byte-field and calls appropriate methods to process this message. /// </summary> /// <remarks> /// This function checks all applicable checksums and validates that the message is complete before calling one of the specialized /// methods to handle actual decoding and processing. /// </remarks> /// <param name="recv_buffer">Byte-field with an Ixian protocol message.</param> /// <param name="endpoint">Remote endpoint from where the message was received.</param> public static void readProtocolMessage(byte[] recv_buffer, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. readProtocolMessage"); return; } ProtocolMessageCode code = ProtocolMessageCode.hello; byte[] data = null; using (MemoryStream m = new MemoryStream(recv_buffer)) { using (BinaryReader reader = new BinaryReader(m)) { // Check for multi-message packets. One packet can contain multiple network messages. while (reader.BaseStream.Position < reader.BaseStream.Length) { byte[] data_checksum; try { byte startByte = reader.ReadByte(); int message_code = reader.ReadInt32(); code = (ProtocolMessageCode)message_code; int data_length = reader.ReadInt32(); // If this is a connected client, filter messages if (endpoint.GetType() == typeof(RemoteEndpoint)) { if (endpoint.presence == null) { // Check for presence and only accept hello and syncPL messages if there is no presence. if (code == ProtocolMessageCode.hello || code == ProtocolMessageCode.getPresenceList || code == ProtocolMessageCode.getBalance || code == ProtocolMessageCode.newTransaction) { } else { // Ignore anything else return; } } } data_checksum = reader.ReadBytes(32); // sha512qu, 32 bytes byte header_checksum = reader.ReadByte(); byte endByte = reader.ReadByte(); data = reader.ReadBytes(data_length); } catch (Exception e) { Logging.error(String.Format("NET: dropped packet. {0}", e)); return; } // Compute checksum of received data byte[] local_checksum = Crypto.sha512sqTrunc(data, 0, 0, 32); // Verify the checksum before proceeding if (local_checksum.SequenceEqual(data_checksum) == false) { Logging.error("Dropped message (invalid checksum)"); continue; } // Can proceed to parse the data parameter based on the protocol message code. // Data can contain multiple elements. //parseProtocolMessage(code, data, socket, endpoint); NetworkQueue.receiveProtocolMessage(code, data, data_checksum, endpoint); } } } }
public static bool broadcastNewBlock(Block b, RemoteEndpoint skipEndpoint = null, RemoteEndpoint endpoint = null, bool force_broadcast = false) { if (!Node.isMasterNode()) { return(true); } if (endpoint != null) { if (endpoint.isConnected()) { endpoint.sendData(ProtocolMessageCode.blockData, b.getBytes(false), BitConverter.GetBytes(b.blockNum)); return(true); } return(false); } else { if (force_broadcast) { return(CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.blockData, b.getBytes(false), BitConverter.GetBytes(b.blockNum), skipEndpoint)); } else { return(CoreProtocolMessage.addToInventory(new char[] { 'M', 'H' }, new InventoryItemBlock(b.blockChecksum, b.blockNum), skipEndpoint, ProtocolMessageCode.blockData, b.getBytes(false), BitConverter.GetBytes(b.blockNum))); } } }
/// <summary> /// Prepares and sends an Ixian protocol 'Hello' message to the specified remote endpoint. /// </summary> /// <remarks> /// A valid Ixian 'Hello' message includes certain Node data, verified by a public-key signature, which this function prepares using /// the primary wallet's keypair. If this message is a reply to the other endpoint's hello message, then /// </remarks> /// <param name="endpoint">Remote endpoint to send the message to.</param> /// <param name="sendHelloData">True if the message is the first hello sent to the remote node, false if it is a reply to the challenge.</param> /// <param name="challenge_response">Response byte-field to the other node's hello challenge</param> public static void sendHelloMessage(RemoteEndpoint endpoint, bool sendHelloData, byte[] challenge_response) { using (MemoryStream m = new MemoryStream(1856)) { using (BinaryWriter writer = new BinaryWriter(m)) { string publicHostname = IxianHandler.getFullPublicAddress(); // Send the node version writer.Write(CoreConfig.protocolVersion); // Send the public node address byte[] address = IxianHandler.getWalletStorage().getPrimaryAddress(); writer.Write(address.Length); writer.Write(address); // Send the testnet designator writer.Write(CoreConfig.isTestNet); char node_type = PresenceList.myPresenceType; writer.Write(node_type); // Send the version writer.Write(CoreConfig.productVersion); // Send the node device id writer.Write(CoreConfig.device_id); // Send the wallet public key writer.Write(IxianHandler.getWalletStorage().getPrimaryPublicKey().Length); writer.Write(IxianHandler.getWalletStorage().getPrimaryPublicKey()); // Send listening port writer.Write(IxianHandler.publicPort); // Send timestamp long timestamp = Core.getCurrentTimestamp(); writer.Write(timestamp); // send signature byte[] signature = CryptoManager.lib.getSignature(Encoding.UTF8.GetBytes(ConsensusConfig.ixianChecksumLockString + "-" + CoreConfig.device_id + "-" + timestamp + "-" + publicHostname), IxianHandler.getWalletStorage().getPrimaryPrivateKey()); writer.Write(signature.Length); writer.Write(signature); if (sendHelloData) { Block block = IxianHandler.getLastBlock(); if (block == null) { Logging.warn("Clients are connecting, but we have no blocks yet to send them!"); return; } ulong lastBlock = block.blockNum; writer.Write(lastBlock); writer.Write(block.blockChecksum.Length); writer.Write(block.blockChecksum); writer.Write(block.walletStateChecksum.Length); writer.Write(block.walletStateChecksum); writer.Write((int)0); // deprecated, can be replaced with something else of type int32 writer.Write(block.version); // Write the legacy level writer.Write((ulong)0); // deprecated, can be replaced with something else of type UInt64 writer.Write(challenge_response.Length); writer.Write(challenge_response); #if TRACE_MEMSTREAM_SIZES Logging.info(String.Format("CoreProtocolMessage::sendHelloMessage: {0}", m.Length)); #endif endpoint.sendData(ProtocolMessageCode.helloData, m.ToArray()); } else { List <byte> challenge = new List <byte>(); challenge.AddRange(IxianHandler.getWalletStorage().getPrimaryAddress()); Random rnd = new Random(); challenge.AddRange(BitConverter.GetBytes(rnd.Next(20000))); byte[] challenge_bytes = challenge.ToArray(); endpoint.challenge = challenge_bytes; writer.Write(challenge_bytes.Length); writer.Write(challenge_bytes); #if TRACE_MEMSTREAM_SIZES Logging.info(String.Format("CoreProtocolMessage::sendHelloMessage: {0}", m.Length)); #endif endpoint.sendData(ProtocolMessageCode.hello, m.ToArray()); } } } }
public static void handleGetBlock3(byte[] data, RemoteEndpoint endpoint) { if (!Node.isMasterNode()) { Logging.warn("Block data was requested, but this node isn't a master node"); return; } if (Node.blockSync.synchronizing) { return; } using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { ulong block_number = reader.ReadIxiVarUInt(); byte include_transactions = reader.ReadByte(); bool full_header = false; try { full_header = reader.ReadBoolean(); } catch (Exception) { } //Logging.info(String.Format("Block #{0} has been requested.", block_number)); ulong last_block_height = IxianHandler.getLastBlockHeight() + 1; if (block_number > last_block_height) { return; } Block block = null; if (block_number == last_block_height) { bool haveLock = false; try { Monitor.TryEnter(Node.blockProcessor.localBlockLock, 1000, ref haveLock); if (!haveLock) { throw new TimeoutException(); } Block tmp = Node.blockProcessor.getLocalBlock(); if (tmp != null && tmp.blockNum == last_block_height) { block = tmp; } } finally { if (haveLock) { Monitor.Exit(Node.blockProcessor.localBlockLock); } } } else { block = Node.blockChain.getBlock(block_number, Config.storeFullHistory); } if (block == null) { Logging.warn("Unable to find block #{0} in the chain!", block_number); return; } //Logging.info(String.Format("Block #{0} ({1}) found, transmitting...", block_number, Crypto.hashToString(block.blockChecksum.Take(4).ToArray()))); // Send the block if (include_transactions == 1) { TransactionProtocolMessages.handleGetBlockTransactions3(block_number, false, endpoint); } else if (include_transactions == 2) { TransactionProtocolMessages.handleGetBlockTransactions3(block_number, true, endpoint); } if (!Node.blockProcessor.verifySigFreezedBlock(block)) { Logging.warn("Sigfreezed block {0} was requested. but we don't have the correct sigfreeze!", block.blockNum); } bool frozen_sigs_only = true; if (block_number + 5 > IxianHandler.getLastBlockHeight()) { if (block.getFrozenSignatureCount() < Node.blockChain.getRequiredConsensus(block_number)) { frozen_sigs_only = false; } } endpoint.sendData(ProtocolMessageCode.blockData, block.getBytes(full_header, frozen_sigs_only), BitConverter.GetBytes(block.blockNum), 0, MessagePriority.high); } } }
// Broadcast an event-specific protocol message across subscribed clients // Returns true if it sent the message to at least one endpoint. Returns false if the message couldn't be sent to any endpoints /// <summary> /// Broadcasts an event message to all clients who are subscribed to receive the specific event type and wallet address. /// </summary> /// <remarks> /// Events are filtered by type and address. A client must subscribe to the specifif type for specific addresses in order to receive this data. /// The payload `data` should be properly formatted for the given `code` - this function will not ensure that this is so and /// the caller must provide a valid message to this function. /// The `skipEndpoint` parameter is useful when re-broadcasting a message received from a specific endpoint and do not wish to echo the same /// data back to the sender. /// </remarks> /// <param name="type">Type of the event message - used to filter subscribers</param> /// <param name="address">Address, which triggered the event.</param> /// <param name="code">Ixian protocol code.</param> /// <param name="data">Payload data.</param> /// <param name="helper_data">Optional additional data, as required by `code`.</param> /// <param name="skipEndpoint">Endpoint to skip when broadcasting.</param> /// <returns>True, if at least one message was sent to at least one remote endpoint. False if no messages were sent.</returns> public static bool broadcastEventDataMessage(NetworkEvents.Type type, byte[] address, ProtocolMessageCode code, byte[] data, byte[] helper_data, RemoteEndpoint skipEndpoint = null) { // Send it to subscribed C nodes bool f_result = NetworkServer.broadcastEventData(type, code, data, address, helper_data, skipEndpoint); return(f_result); }
public static void handleGetBlockHeaders(byte[] data, RemoteEndpoint endpoint) { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { ulong from = reader.ReadUInt64(); ulong to = reader.ReadUInt64(); ulong totalCount = to - from; if (totalCount < 1) { return; } ulong lastBlockNum = Node.blockChain.getLastBlockNum(); if (from > lastBlockNum - 1) { return; } if (to > lastBlockNum) { to = lastBlockNum; } // Adjust total count if necessary totalCount = to - from; if (totalCount < 1) { return; } // Cap total block headers sent if (totalCount > (ulong)CoreConfig.maximumBlockHeadersPerChunk) { totalCount = (ulong)CoreConfig.maximumBlockHeadersPerChunk; } if (endpoint == null) { return; } if (!endpoint.isConnected()) { return; } // TODO TODO TODO block headers should be read from a separate storage and every node should keep a full copy for (ulong i = 0; i < totalCount;) { bool found = false; using (MemoryStream mOut = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(mOut)) { for (int j = 0; j < CoreConfig.maximumBlockHeadersPerChunk && i < totalCount; j++) { Block block = Node.blockChain.getBlock(from + i, true, true); i++; if (block == null) { break; } long rollback_len = mOut.Length; found = true; BlockHeader header = new BlockHeader(block); byte[] headerBytes = header.getBytes(); writer.Write(headerBytes.Length); writer.Write(headerBytes); if (mOut.Length > CoreConfig.maxMessageSize) { mOut.SetLength(rollback_len); i--; break; } broadcastBlockHeaderTransactions(block, endpoint); } } if (!found) { break; } endpoint.sendData(ProtocolMessageCode.blockHeaders, mOut.ToArray()); } } } } }
// Unified protocol message parsing public static void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint) { if (endpoint == null) { Logging.error("Endpoint was null. parseProtocolMessage"); return; } try { switch (code) { case ProtocolMessageCode.hello: using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (CoreProtocolMessage.processHelloMessage(endpoint, reader)) { byte[] challenge_response = null; try { // TODO TODO TODO TODO TODO try/catch wrapper will be removed when everybody upgrades int challenge_len = reader.ReadInt32(); byte[] challenge = reader.ReadBytes(challenge_len); challenge_response = CryptoManager.lib.getSignature(challenge, Node.walletStorage.getPrimaryPrivateKey()); } catch (Exception e) { } CoreProtocolMessage.sendHelloMessage(endpoint, true, challenge_response); endpoint.helloReceived = true; return; } } } break; case ProtocolMessageCode.helloData: using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { if (CoreProtocolMessage.processHelloMessage(endpoint, reader)) { char node_type = endpoint.presenceAddress.type; if (node_type != 'M' && node_type != 'H') { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.expectingMaster, string.Format("Expecting master node."), "", true); return; } ulong last_block_num = reader.ReadUInt64(); int bcLen = reader.ReadInt32(); byte[] block_checksum = reader.ReadBytes(bcLen); int wsLen = reader.ReadInt32(); byte[] walletstate_checksum = reader.ReadBytes(wsLen); int consensus = reader.ReadInt32(); endpoint.blockHeight = last_block_num; int block_version = reader.ReadInt32(); Node.setLastBlock(last_block_num, block_checksum, walletstate_checksum, block_version); Node.setRequiredConsensus(consensus); // Check for legacy level ulong legacy_level = reader.ReadUInt64(); // Check for legacy node if (Legacy.isLegacy(legacy_level)) { // TODO TODO TODO TODO check this out //endpoint.setLegacy(true); } int challenge_response_len = reader.ReadInt32(); byte[] challenge_response = reader.ReadBytes(challenge_response_len); if (!CryptoManager.lib.verifySignature(endpoint.challenge, endpoint.serverPubKey, challenge_response)) { CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.authFailed, string.Format("Invalid challenge response."), "", true); return; } // Process the hello data endpoint.helloReceived = true; NetworkClientManager.recalculateLocalTimeDifference(); } } } break; case ProtocolMessageCode.s2data: { StreamProcessor.receiveData(data, endpoint); } break; case ProtocolMessageCode.s2failed: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { Logging.error("Failed to send s2 data"); } } } break; case ProtocolMessageCode.s2signature: { StreamProcessor.receivedTransactionSignature(data, endpoint); } break; case ProtocolMessageCode.newTransaction: { // Forward the new transaction message to the DLT network CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H' }, ProtocolMessageCode.newTransaction, data, null); } break; case ProtocolMessageCode.syncPresenceList: { byte[] pdata = PresenceList.getBytes(); byte[] ba = CoreProtocolMessage.prepareProtocolMessage(ProtocolMessageCode.presenceList, pdata); endpoint.sendData(ProtocolMessageCode.presenceList, pdata); } break; case ProtocolMessageCode.presenceList: { Logging.info("Receiving complete presence list"); PresenceList.syncFromBytes(data); } break; case ProtocolMessageCode.updatePresence: { // Parse the data and update entries in the presence list PresenceList.updateFromBytes(data); } break; case ProtocolMessageCode.keepAlivePresence: { byte[] address = null; bool updated = PresenceList.receiveKeepAlive(data, out address); // If a presence entry was updated, broadcast this message again if (updated) { CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'R', 'H', 'W' }, ProtocolMessageCode.keepAlivePresence, data, address, endpoint); // Send this keepalive message to all connected clients CoreProtocolMessage.broadcastEventDataMessage(NetworkEvents.Type.keepAlive, address, ProtocolMessageCode.keepAlivePresence, data, address, endpoint); } } break; case ProtocolMessageCode.getPresence: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int walletLen = reader.ReadInt32(); byte[] wallet = reader.ReadBytes(walletLen); lock (PresenceList.presences) { // TODO re-verify this Presence p = PresenceList.presences.Find(x => x.wallet.SequenceEqual(wallet)); if (p != null) { byte[][] presence_chunks = p.getByteChunks(); int i = 0; foreach (byte[] presence_chunk in presence_chunks) { endpoint.sendData(ProtocolMessageCode.updatePresence, presence_chunk); i++; } } else { // TODO blacklisting point Logging.warn(string.Format("Node has requested presence information about {0} that is not in our PL.", Base58Check.Base58CheckEncoding.EncodePlain(wallet))); } } } } } break; case ProtocolMessageCode.balance: { // TODO: make sure this is received from a DLT node only. using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { int address_length = reader.ReadInt32(); byte[] address = reader.ReadBytes(address_length); // Retrieve the latest balance IxiNumber balance = reader.ReadString(); if (address.SequenceEqual(Node.walletStorage.getPrimaryAddress())) { Node.balance = balance; } // Retrieve the blockheight for the balance ulong blockheight = reader.ReadUInt64(); Node.blockHeight = blockheight; } } } break; case ProtocolMessageCode.bye: { using (MemoryStream m = new MemoryStream(data)) { using (BinaryReader reader = new BinaryReader(m)) { endpoint.stop(); bool byeV1 = false; try { ProtocolByeCode byeCode = (ProtocolByeCode)reader.ReadInt32(); string byeMessage = reader.ReadString(); string byeData = reader.ReadString(); byeV1 = true; switch (byeCode) { case ProtocolByeCode.bye: // all good break; case ProtocolByeCode.forked: // forked node disconnected Logging.info(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; case ProtocolByeCode.deprecated: // deprecated node disconnected Logging.info(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; case ProtocolByeCode.incorrectIp: // incorrect IP if (IxiUtils.validateIPv4(byeData)) { if (NetworkClientManager.getConnectedClients().Length < 2) { Config.publicServerIP = byeData; Logging.info("Changed internal IP Address to " + byeData + ", reconnecting"); } } break; case ProtocolByeCode.notConnectable: // not connectable from the internet Logging.error("This node must be connectable from the internet, to connect to the network."); Logging.error("Please setup uPNP and/or port forwarding on your router for port " + Config.serverPort + "."); NetworkServer.connectable = false; break; case ProtocolByeCode.insufficientFunds: break; default: Logging.warn(string.Format("Disconnected with message: {0} {1}", byeMessage, byeData)); break; } } catch (Exception) { } if (byeV1) { return; } reader.BaseStream.Seek(0, SeekOrigin.Begin); // Retrieve the message string message = reader.ReadString(); if (message.Length > 0) { Logging.info(string.Format("Disconnected with message: {0}", message)); } else { Logging.info("Disconnected"); } } } } break; case ProtocolMessageCode.extend: { if (Config.isTestClient) { TestClientNode.handleExtendProtocol(data); } } break; default: break; } } catch (Exception e) { Logging.error(string.Format("Error parsing network message. Details: {0}", e.ToString())); } }
// Called when receiving S2 data from clients public static void receiveData(byte[] bytes, RemoteEndpoint endpoint) { // TODO Verify signature for all relevant messages string endpoint_wallet_string = Base58Check.Base58CheckEncoding.EncodePlain(endpoint.presence.wallet); Logging.info(string.Format("Receiving S2 data from {0}", endpoint_wallet_string)); StreamMessage message = new StreamMessage(bytes); // Don't allow clients to send error stream messages, as it's reserved for S2 nodes only if (message.type == StreamMessageCode.error) { Logging.warn(string.Format("Discarding error message type from {0}", endpoint_wallet_string)); return; } // Discard messages not sent to this node if (!IxianHandler.getWalletStorage().isMyAddress(message.recipient)) { Logging.warn(string.Format("Discarding message that wasn't sent to this node from {0}", endpoint_wallet_string)); return; } if (message.encryptionType != StreamMessageEncryptionCode.none && !message.decrypt(IxianHandler.getWalletStorage().getPrimaryPrivateKey(), null, null)) { Logging.error("Could not decrypt message from {0}", Base58Check.Base58CheckEncoding.EncodePlain(message.sender)); return; } SpixiMessage spixi_msg = new SpixiMessage(message.data); int channel = 0; if (spixi_msg != null) { channel = spixi_msg.channel; } if (message.requireRcvConfirmation) { switch (spixi_msg.type) { case SpixiMessageCode.msgReceived: case SpixiMessageCode.msgRead: case SpixiMessageCode.requestFileData: case SpixiMessageCode.fileData: case SpixiMessageCode.appData: case SpixiMessageCode.msgTyping: // do not send received confirmation break; case SpixiMessageCode.chat: sendReceivedConfirmation(message.sender, message.id, channel, endpoint); break; default: sendReceivedConfirmation(message.sender, message.id, -1, endpoint); break; } } switch (spixi_msg.type) { case SpixiMessageCode.requestAdd: // Friend request if (!new Address(spixi_msg.data).address.SequenceEqual(message.sender) || !message.verifySignature(spixi_msg.data)) { Logging.error("Unable to verify signature for message type: {0}, id: {1}, from: {2}.", message.type, Crypto.hashToString(message.id), Base58Check.Base58CheckEncoding.EncodePlain(message.sender)); } else { sendAcceptAdd(endpoint.presence.wallet, endpoint.presence.pubkey); sendAvatar(endpoint.presence.wallet, null); } break; case SpixiMessageCode.getPubKey: if (Node.users.hasUser(spixi_msg.data)) { StreamMessage sm = new StreamMessage(); sm.type = StreamMessageCode.info; sm.sender = IxianHandler.getWalletStorage().getPrimaryAddress(); sm.recipient = message.sender; sm.data = new SpixiMessage(SpixiMessageCode.pubKey, Node.users.getUser(spixi_msg.data).publicKey).getBytes(); sm.encryptionType = StreamMessageEncryptionCode.none; sendMessage(endpoint.presence.wallet, sm); } break; case SpixiMessageCode.getNick: sendNickname(endpoint.presence.wallet, spixi_msg.data); break; case SpixiMessageCode.getAvatar: sendAvatar(endpoint.presence.wallet, spixi_msg.data); break; case SpixiMessageCode.nick: Node.users.setPubKey(endpoint.presence.wallet, endpoint.serverPubKey, false); Node.users.setNick(endpoint.presence.wallet, message.getBytes()); break; case SpixiMessageCode.avatar: Node.users.setPubKey(endpoint.presence.wallet, endpoint.serverPubKey, false); if (message.data.Length < 500000) { if (message.data == null) { Node.users.setAvatar(endpoint.presence.wallet, null); } else { Node.users.setAvatar(endpoint.presence.wallet, message.getBytes()); } } break; case SpixiMessageCode.chat: onChat(bytes, message, channel, endpoint); break; case SpixiMessageCode.botGetMessages: Messages.sendMessages(endpoint.presence.wallet, channel, spixi_msg.data); break; case SpixiMessageCode.msgReceived: { // don't send confirmation back, so just return return; } case SpixiMessageCode.msgRead: { // don't send confirmation back, so just return return; } case SpixiMessageCode.botAction: onBotAction(spixi_msg.data, endpoint); break; case SpixiMessageCode.msgDelete: onMsgDelete(spixi_msg.data, channel, endpoint); break; case SpixiMessageCode.msgReaction: onMsgReaction(message, spixi_msg.data, channel, endpoint); break; case SpixiMessageCode.leave: onLeave(message.sender); break; default: Logging.warn("Received message type that isn't handled {0}", spixi_msg.type); break; } // TODO: commented for development purposes ONLY! /* * // Extract the transaction * Transaction transaction = new Transaction(message.transaction); * * // Validate transaction sender * if(transaction.from.SequenceEqual(message.sender) == false) * { * Logging.error(string.Format("Relayed message transaction mismatch for {0}", endpoint_wallet_string)); * sendError(message.sender); * return; * } * * // Validate transaction amount and fee * if(transaction.amount < CoreConfig.relayPriceInitial || transaction.fee < CoreConfig.transactionPrice) * { * Logging.error(string.Format("Relayed message transaction amount too low for {0}", endpoint_wallet_string)); * sendError(message.sender); * return; * } * * // Validate transaction receiver * if (transaction.toList.Keys.First().SequenceEqual(IxianHandler.getWalletStorage().address) == false) * { * Logging.error("Relayed message transaction receiver is not this S2 node"); * sendError(message.sender); * return; * } * * // Update the recipient dictionary * if (dataRelays.ContainsKey(message.recipient)) * { * dataRelays[message.recipient]++; * if(dataRelays[message.recipient] > Config.relayDataMessageQuota) * { * Logging.error(string.Format("Exceeded amount of unpaid data relay messages for {0}", endpoint_wallet_string)); * sendError(message.sender); * return; * } * } * else * { * dataRelays.Add(message.recipient, 1); * } * * * // Store the transaction * StreamTransaction streamTransaction = new StreamTransaction(); * streamTransaction.messageID = message.getID(); * streamTransaction.transaction = transaction; * lock (transactions) * { * transactions.Add(streamTransaction); * } * * // For testing purposes, allow the S2 node to receive relay data itself * if (message.recipient.SequenceEqual(IxianHandler.getWalletStorage().getWalletAddress())) * { * string test = Encoding.UTF8.GetString(message.data); * Logging.info(test); * * return; * } * * Logging.info("NET: Forwarding S2 data"); * NetworkStreamServer.forwardMessage(message.recipient, DLT.Network.ProtocolMessageCode.s2data, bytes); */ }
public override void parseProtocolMessage(ProtocolMessageCode code, byte[] data, RemoteEndpoint endpoint) { ProtocolMessage.parseProtocolMessage(code, data, endpoint); }
internal static void LoadRabbitEndpoints() { try { string connString = _localhostConnString; var remoteEndpoints = new Dictionary <int, RemoteEndpoint>(); var removedEndpoints = new List <int>(); var newEndpoints = new List <int>(); var oldEndpoints = _remoteEndpoints.Keys.ToList(); using (SqlConnection conn = new SqlConnection(connString)) { using (SqlCommand cmd = conn.CreateCommand()) { cmd.CommandText = "rmq.pr_GetRabbitEndpoints"; cmd.CommandType = CommandType.StoredProcedure; conn.Open(); var dr = cmd.ExecuteReader(); if (dr.HasRows) { while (dr.Read()) { var re = new RemoteEndpoint(dr); remoteEndpoints.Add(re.EndpointId, re); _remoteEndpoints.Add(re.EndpointId, re); } } } //tear down rabbit publishers removedEndpoints = _remoteEndpoints.Keys.Except(remoteEndpoints.Keys).ToList(); if (removedEndpoints.Any()) { foreach (var id in removedEndpoints) { RemoteEndpoint e1; RabbitPublisher rp1; if (_rabbitPublishers.TryGetValue(id, out rp1)) { rp1.Shutdown(); _rabbitPublishers.Remove(id); } _remoteEndpoints.Remove(id); } } newEndpoints = _remoteEndpoints.Keys.Except(oldEndpoints).ToList(); if (newEndpoints.Any()) { foreach (var id in newEndpoints) { var rep = _remoteEndpoints.Where(ex => ex.Key == id).Select(r => r.Value).FirstOrDefault(); var rp = new RabbitPublisher(rep.ConnectionString, id); _rabbitPublishers.Add(id, rp); rp.InternalConnect(); } } } if (remoteEndpoints.Count == 0) { //TearDownConnections(); throw new ApplicationException("No enabled rabbit endpoints exists"); } } catch (Exception ex) { throw new ApplicationException(string.Format("Error in: RabbitMQSqlServer.LoadRabbitEndpoints. The error is: Error rabbit endpoints: {0}", ex.Message)); } }