/// <summary> /// Requests PIT for the specified block from a random connected neighbor node. /// Nominally, only the transactions included in `txids` need to be verifiable with the PIT, but /// due to how Cuckoo filtering works, some false positives will also be included. This helps with anonymization, if the false positive rate is high enough. /// </summary> /// <param name="block_num">Block number for which the PIT should be included.</param> /// <param name="txids">List of interesting transactions, which we wish to verify.</param> private void requestPITForBlock(ulong block_num, List <string> txids) { lock (pitCache) { long currentTime = Clock.getTimestamp(); // Request might already have been sent. In that case, we re-send it we have been waiting for too long. if (!pitCache.ContainsKey(block_num) || currentTime - pitCache[block_num].requestSent > pitRequestTimeout) { Cuckoo filter = new Cuckoo(txids.Count); foreach (var tx in txids) { filter.Add(Encoding.UTF8.GetBytes(tx)); } byte[] filter_bytes = filter.getFilterBytes(); MemoryStream m = new MemoryStream(filter_bytes.Length + 12); using (BinaryWriter w = new BinaryWriter(m, Encoding.UTF8, true)) { w.Write(block_num); w.Write(filter_bytes.Length); w.Write(filter_bytes); } CoreProtocolMessage.broadcastProtocolMessageToSingleRandomNode(new char[] { 'M' }, ProtocolMessageCode.getPIT, m.ToArray(), 0); PITCacheItem ci = new PITCacheItem() { pit = null, requestedForTXIDs = txids, requestSent = Clock.getTimestamp() }; pitCache.AddOrReplace(block_num, ci); } } }
/// <summary> /// Verifies that the given remote endpoint is reachable by connecting to it and sending a short message. /// </summary> /// <remarks> /// This function is used to ensure that the remote endpoing has listed the correct IP and port information for their `PresenceList` entry. /// </remarks> /// <param name="endpoint">Target endpoint to verify for connectivity.</param> /// <returns>True, if the endpoing is connectable.</returns> public static bool checkNodeConnectivity(RemoteEndpoint endpoint) { // TODO TODO TODO TODO we should put this in a separate thread string hostname = endpoint.getFullAddress(true); if (CoreNetworkUtils.PingAddressReachable(hostname) == false) { Logging.warn("Node {0} was not reachable on the advertised address.", hostname); CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.notConnectable, "External " + hostname + " not reachable!", ""); return(false); } return(true); }
private void requestBlockHeaders(ulong from, ulong to) { Logging.info("Requesting block headers from {0} to {1}", from, to); using (MemoryStream mOut = new MemoryStream()) { using (BinaryWriter writer = new BinaryWriter(mOut)) { writer.Write(from); writer.Write(to); } // Request from all nodes //NetworkClientManager.broadcastData(new char[] { 'M', 'H' }, ProtocolMessageCode.getBlockHeaders, mOut.ToArray(), null); // Request from a single random node CoreProtocolMessage.broadcastProtocolMessageToSingleRandomNode(new char[] { 'M' }, ProtocolMessageCode.getBlockHeaders, mOut.ToArray(), 0); } }
// 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, RemoteEndpoint endpoint) { address = null; // Get the current timestamp long currentTime = Clock.getNetworkTimestamp(); try { using (MemoryStream m = new MemoryStream(bytes)) { using (BinaryReader reader = new BinaryReader(m)) { int keepAliveVersion = reader.ReadInt32(); int walletLen = reader.ReadInt32(); byte[] wallet = reader.ReadBytes(walletLen); // Assign the out address parameter address = wallet; string deviceid = reader.ReadString(); long timestamp = reader.ReadInt64(); string hostname = reader.ReadString(); char node_type = '0'; node_type = reader.ReadChar(); int sigLen = reader.ReadInt32(); byte[] signature = reader.ReadBytes(sigLen); //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(bytes.Take(bytes.Length - sigLen - 4).ToArray(), 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 == 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); }
// Sends perioding keepalive network messages private static void keepAlive() { forceSendKeepAlive = true; while (autoKeepalive) { TLC.Report(); int keepalive_interval = CoreConfig.serverKeepAliveInterval; if (curNodePresenceAddress.type == 'C') { keepalive_interval = CoreConfig.clientKeepAliveInterval; } // Wait x seconds before rechecking for (int i = 0; i < keepalive_interval; i++) { if (autoKeepalive == false) { return; } if (IxianHandler.publicIP == "") { // do not send KA i = 0; } else { if (forceSendKeepAlive) { Thread.Sleep(1000); forceSendKeepAlive = false; break; } } // Sleep for one second Thread.Sleep(1000); } if (curNodePresenceAddress.type == 'W') { continue; // no need to send PL for worker nodes } try { byte[] ka_bytes = null; ka_bytes = keepAlive_v1(); byte[] address = null; // Update self presence PresenceList.receiveKeepAlive(ka_bytes, out address, null); // Send this keepalive to all connected non-clients CoreProtocolMessage.broadcastProtocolMessage(new char[] { 'M', 'H', 'W' }, ProtocolMessageCode.keepAlivePresence, ka_bytes, address); // Send this keepalive message to all connected clients CoreProtocolMessage.broadcastEventDataMessage(NetworkEvents.Type.keepAlive, address, ProtocolMessageCode.keepAlivePresence, ka_bytes, address); } catch (Exception e) { Logging.error("Exception occured while generating keepalive: " + e); } } }
/// <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 = Clock.getTimestamp() + endpoint.calculateTimeDifference(); 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!"); CoreProtocolMessage.sendBye(endpoint, ProtocolByeCode.notReady, string.Format("The node isn't ready yet, please try again later."), "", true); 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()); } } } }
/// <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 = null; if (pkLen > 0) { 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, Clock.getNetworkTimestamp() - 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, Clock.getNetworkTimestamp() - 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); }