Be aware that if the pubkey already exists in the inventory, the metods will not request it and the callback
for freshly received pubkeys will not be called. Instead the pubkey is added to the contact and stored on DB.
337436
public void requestPubkey(final BitmessageAddress contact) {
        BitmessageAddress stored = addressRepository.getAddress(contact.getAddress());

        tryToFindMatchingPubkey(contact);
        if (contact.getPubkey() != null) {
            if (stored != null) {
                stored.setPubkey(contact.getPubkey());
                addressRepository.save(stored);
            } else {
                addressRepository.save(contact);
            }
            return;
        }

        if (stored == null) {
            addressRepository.save(contact);
        }

        long expires = UnixTime.now(TTL.getpubkey());
        LOG.info("Expires at " + expires);
        final ObjectMessage request = new ObjectMessage.Builder()
            .stream(contact.getStream())
            .expiresTime(expires)
            .payload(new GetPubkey(contact))
            .build();
        proofOfWorkService.doProofOfWork(request);
    }
118469
public boolean isCommitted() {
		Transaction tx = getTransaction();
		// Issue 1571 : http://code.google.com/p/mobicents/issues/detail?id=1571 
		// NullPointerException in SipServletResponseImpl.isCommitted
		if(tx != null) {
			//the message is an incoming non-reliable provisional response received by a servlet acting as a UAC
			if(tx instanceof ClientTransaction && getStatus() >= 101 && getStatus() <= 199 && getHeader("RSeq") == null) {
				if(this.proxyBranch == null) { // Make sure this is not a proxy. Proxies are allowed to modify headers.
					return true;
				} else {
					return false;
				}
			}
			//the message is an incoming reliable provisional response for which PRACK has already been generated. (Note that this scenario applies to containers that support the 100rel extension.)
			if(tx instanceof ClientTransaction && getStatus() >= 101 && getStatus() <= 199 && getHeader("RSeq") != null && TransactionState.TERMINATED.equals(tx.getState())) {
				if(this.proxyBranch == null) { // Make sure this is not a proxy. Proxies are allowed to modify headers.
					return true;
				} else {
					return false;
				}
			}
			//the message is an incoming final response received by a servlet acting as a UAC for a Non INVITE transaction
			if(tx instanceof ClientTransaction && getStatus() >= 200 && getStatus() <= 999 && !Request.INVITE.equals(((SIPClientTransaction)tx).getMethod())) {
				if(this.proxyBranch == null) { // Make sure this is not a proxy. Proxies are allowed to modify headers.
					return true;
				} else {
					return false;
				}
			}
			//the message is a response which has been forwarded upstream
			if(isResponseForwardedUpstream) {
				return true;
			}
			//message is an incoming final response to an INVITE transaction and an ACK has been generated
			if(tx instanceof ClientTransaction && getStatus() >= 200 && getStatus() <= 999 && TransactionState.TERMINATED.equals(tx.getState()) && isAckGenerated) {
				return true;
			}
		}
		return false;
	}
1050727
async def put(self, public_key):
		
		if settings.SIGNATURE_VERIFICATION:
			super().verify()

		# Check if message contains required data
		try:
			body = json.loads(self.request.body)
		except:
			self.set_status(400)
			self.write({"error":400, "reason":"Unexpected data format. JSON required"})
			raise tornado.web.Finish
			
		if isinstance(body["message"], str):
			message = json.loads(body["message"])
		elif isinstance(body["message"], dict):
			message = body["message"]
		cid = int(message["offer_id"].get("cid", 0))
		buyer_address = message["offer_id"].get("buyer_address")
		coinid = message.get("coinid")

		if not all([cid, buyer_address, coinid]):
			self.set_status(400)
			self.write({"error":400, "reason": "Missed required fields."})
			raise tornado.web.Finish

		if coinid in settings.bridges.keys():
			self.account.blockchain.setendpoint(settings.bridges[coinid])
		else:
			self.set_status(400)
			self.write({"error":400, "reason":"Invalid coin ID"})
			raise tornado.web.Finish

		check = self.account.validator[coinid](public_key)

		account = await self.account.getaccountdata(public_key=public_key)
		if "error" in account.keys():
			error_code = account["error"]
			self.set_status(error_code)
			self.write(account)
			raise tornado.web.Finish
	
		# Check if one of sellers or buyers rejects offer
		owneraddr = await self.account.blockchain.ownerbycid(cid=cid)
		hex_ = check
		if buyer_address != hex_ and owneraddr != hex_:
			# Avoid rejecting offer
			self.set_status(403)
			self.write({"error": 403, "reason":"Forbidden. Offer does not belong to user."})
		
		# Reject offer
		response = await self.account.blockchain.rejectoffer(coinid=coinid, cid=cid, 
															buyer_address=buyer_address)
		if "error" in response.keys():
			self.set_status(response["error"])
			self.write(response)
			raise tornado.web.Finish

		# Get buyer for email sending
		buyer = await self.account.getaccountbywallet(wallet=buyer_address)
		if "error" in buyer.keys():
			self.set_status(buyer["error"])
			self.write(buyer)
			raise tornado.web.Finish
	
		if buyer.get("email"):
			emaildata = {
				"to": buyer.get("email"),
				"subject": "Robin8 support",
     			"optional": "Your offer with cid %s was rejected." % cid
			}
			await self.account.mailer.sendmail(**emaildata)
		
		# Undeposit balance
		price = await self.account.blockchain.getwriteprice(cid=cid)
		coinid = "PUT"
		await self.account.balance.unfreeze(uid=buyer["id"],coinid=coinid, 
													amount=price)

		del response["result"]
		self.write(response)
423190
public function submitMissingKeys() {
        if ($this->missing_keys_by_sources == null)
            return;

        $params = array();
        $source_keys = array();
        foreach($this->missing_keys_by_sources as $source => $keys) {
            array_push($source_keys, $source);

            $keys_data = array();
            foreach($keys as $key) {
                /** @var $key TranslationKey */
                $json = array(
                    "key"           => $key->key,
                    "label"         => $key->label,
                    "description"   => $key->description,
                    "locale"        => $key->locale,
                    "level"         => $key->level
                );
                array_push($keys_data, $json);
            }
            array_push($params, array("source" => $source, "keys" => $keys_data));
        }

        $params = \Tr8n\Utils\ArrayUtils::trim($params);
        $this->apiClient()->post('source/register_keys', array("source_keys" => json_encode($params)));
        $this->missing_keys_by_sources = null;

        // All source caches must be reset for all languages, since the keys have changed
        foreach ($this->languages_by_locale as $locale => $language) {
            foreach ($source_keys as $source_key) {
                Cache::delete(Source::cacheKey($source_key, $locale));
            }
        }
    }
1150772
def add_on_nr(key, to_add)
      result = req_list(new_req_list().add_add_on_nr(key, to_add))[0]
      process_result_add_on_nr(result)
    end
282107
def add(self, pubkey):
        
        f = get_fingerprint_from_public_key(pubkey)
        if f not in self._keys:
            self._order[len(self._keys)] = f
            self._keys[f] = pubkey
259751
function(args, connectionInfo) {
  // args.consignmentIdToProductIdMap.value MUST already be set by the caller
  // args.consignmentProductId.value MUST be set for the very first call
  args.arrayIndex = {value: 0};
  args.consignmentProductId = {value: args.consignmentIdToProductIdMap.value[args.arrayIndex.value].productId};
  args.getArray = function(){
    return this.consignmentIdToProductIdMap.value;
  };
  args.getArrayIndex = function(){
    return this.arrayIndex.value;
  };

  // iterate serially through a promise chain for all args.consignmentIds.value
  return utils.processPromisesSerially(
    args.getArray(),
    args.getArrayIndex(),
    args,
    function mergeStrategy(newData, previousData, args){
      log.debug('resolveMissingSuppliers - inside mergeStrategy()');
      var product = newData.products[0];
      //log.silly('newData: ', newData);
      //log.silly('product: ', product);
      var updateMe = args.getArray()[args.getArrayIndex()];
      updateMe.supplier = product.supplier_name || product.supplier_code;
      log.debug('updated consignmentIdToProductIdMap: ', args.getArray()[args.getArrayIndex()]);

      previousData = args.getArray();
      return Promise.resolve(previousData); // why do we need a promise?
    },
    function setupNext(updateArgs){
      log.debug('resolveMissingSuppliers - inside setupNext()');
      updateArgs.arrayIndex.value = updateArgs.getArrayIndex() + 1;
      if (updateArgs.getArrayIndex() < updateArgs.getArray().length) {
        updateArgs.consignmentProductId.value = updateArgs.getArray()[updateArgs.getArrayIndex()].productId;
        log.debug('resolveMissingSuppliers - next is consignmentId: ' + updateArgs.consignmentProductId.value);
      }
      else {
        updateArgs.consignmentProductId.value = null;
        log.debug('resolveMissingSuppliers - finished iterating through all the consignmentIds');
      }
      return updateArgs;
    },
    function executeNext(updatedArgs){
      log.debug('resolveMissingSuppliers - inside executeNext()');
      log.debug('resolveMissingSuppliers - executing for consignmentProductId: ' + updatedArgs.consignmentProductId.value);
      //log.silly('updatedArgs: ', updatedArgs);
      var args = argsForInput.products.fetchById();
      args.apiId.value = updatedArgs.consignmentProductId.value;
      return product.endpoints.fetchProduct(args, connectionInfo);
    }
  );
}
1054485
function addDeviceXPubKey(wallet, device_address, xPubKey, onDone){
	db.query(
		"INSERT "+db.getIgnore()+" INTO extended_pubkeys (wallet, device_address) VALUES(?,?)",
		[wallet, device_address],
		function(){
			db.query(
				"UPDATE extended_pubkeys SET extended_pubkey=?, approval_date="+db.getNow()+" WHERE wallet=? AND device_address=?", 
				[xPubKey, wallet, device_address],
				function(){
					eventBus.emit('wallet_approved', wallet, device_address);
					checkAndFullyApproveWallet(wallet, onDone);
				}
			);
		}
	);
}
554174
private boolean checkIfKeyExists(String key)
    {
        if (aggregationsKeySet == null)
        {
            aggregationsKeySet = new HashSet<String>();
        }
        return aggregationsKeySet.add(key);
    }
449293
def is_ready(self, key):
        """"""
        if self.pending_callbacks is None or key not in self.pending_callbacks:
            raise UnknownKeyError("key %r is not pending" % (key,))
        return key in self.results
482944
def key_exists(self, key):
        
        assert isinstance(key, str)
        cursor = self.conn.cursor()
        with contextlib.closing(cursor):
            cursor.execute('SELECT COUNT(*) FROM events WHERE uuid=?', (key,))
            res = cursor.fetchone()
            count = res[0]
        if count == 0:
            return False
        else:
            assert count in (0, 1), \
                "Duplicate event ids detected: {0}".format(count)
            return True
930231
function(callback) {

    if (typeof this.publisher !== 'function') {
      var pubErrMsg = 'publisher not injected!';
      debug(pubErrMsg);
      if (callback) callback(new Error(pubErrMsg));
      return;
    }

    if (!this.store || typeof this.store.getUndispatchedEvents !== 'function'
                    || typeof this.store.setEventToDispatched !== 'function') {
      var storeErrMsg = 'store not injected!';
      debug(storeErrMsg);
      if (callback) callback(new Error(storeErrMsg))
      return;
    }

    var self = this;

    // Get all undispatched events from store and queue them
    // before all other events passed in by the addUndispatchedEvents function.
    this.store.getUndispatchedEvents(function(err, events) {

      if (err) {
        debug(err);
        if (callback) callback(err);
        return;
      }

      var triggered = false;
      if (events) {
        for (var i = 0, len = events.length; i < len; i++) {
          self.undispatchedEventsQueue.push(events[i]);
          // If there are a lot of events then we can hit issues with the call stack size when processing in one go
          triggered = false;
          if (i % 1000 === 0){
            triggered = true;
            trigger(self);
          }
        }
      }

      if (!triggered) {
        trigger(self);
      }

      if (callback) callback(null);
    });
  }
163887
public boolean isKeyReady()
  {
    if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
      SibTr.entry(tc, "isKeyReady");
    
    boolean returnValue = false;
    if(keyGroup == null)
      returnValue = ready;
    else
      //check if the ordering context is ready
      returnValue = keyGroup.isKeyReady();
    
    if (TraceComponent.isAnyTracingEnabled() && tc.isEntryEnabled())
      SibTr.exit(tc, "isKeyReady", Boolean.valueOf(returnValue));

    return returnValue;
  }
437073
@Override
	// TODO This is executed for each client connected. Should we have only one client connected?
	// TODO Call queryPurchases() at least twice in your code:
	// - Every time your app launches so that you can restore any purchases that a user has made since the app last stopped.
	// - In your onResume() method because a user can make a purchase when your app is in the background (for example, redeeming a promo code in Play Store app).
	// Calling queryPurchases() on startup and resume guarantees that your app finds out about all purchases and redemptions
	// the user may have made while the app wasn't running. Furthermore, if a user makes a purchase while the app is running
	// and your app misses it for any reason, your app still finds out about the purchase the next time the activity resumes and calls queryPurchases().
	// The simplest approach is to call queryPurchases() in your activity's onResume() method, since that callback fires
	// when the activity is created, as well as when the activity is unpaused.
	public void onPurchasesUpdated(@BillingClient.BillingResponse int resultCode, @Nullable List<Purchase> purchases) {
		InAppBillingErrorCode inAppBillingErrorCode = InAppBillingErrorCode.findByErrorResponseCode(resultCode);
		if (inAppBillingErrorCode == null) {
			if (purchases != null) {
				for (Purchase purchase : purchases) {
					String productId = purchase.getSku();
					Product product = inventory.getProduct(productId);
					try {
						if (product != null) {
							product.setPurchase(signatureBase64, purchase.getOriginalJson(), purchase.getSignature(), InAppBillingAppModule.get().getDeveloperPayloadVerificationStrategy());
							InAppBillingAppModule.get().getInAppBillingContext().addPurchasedProductType(product.getProductType());
							
							// TODO Add logic here to see if we are replacing the same purchase. If yes, dont track
							InAppBillingAppModule.get().getModuleAnalyticsSender().trackInAppBillingPurchase(product);
							
							if (listener != null) {
								listener.onPurchaseFinished(product);
							}
							if (product.isWaitingToConsume()) {
								consume(product);
							} else if (listener != null) {
								listener.onProvideProduct(product);
							}
						} else {
							AbstractApplication.get().getExceptionHandler().logWarningException(
									"The purchased product [" + productId + "] is not supported by the app, so it is ignored");
						}
					} catch (ErrorCodeException e) {
						AbstractApplication.get().getExceptionHandler().logHandledException(e);
						if (listener != null) {
							listener.onPurchaseFailed(e);
						}
					} catch (JSONException e) {
						AbstractApplication.get().getExceptionHandler().logHandledException(e);
						if (listener != null) {
							listener.onPurchaseFailed(InAppBillingErrorCode.BAD_PURCHASE_DATA.newErrorCodeException(e));
						}
					}
				}
			}
		} else if (inAppBillingErrorCode.equals(InAppBillingErrorCode.USER_CANCELED)) {
			LOGGER.warn("User cancelled the purchase flow.");
			if (listener != null) {
				listener.onPurchaseFailed(InAppBillingErrorCode.USER_CANCELED.newErrorCodeException("User cancelled the purchase flow."));
			}
		} else {
			AbstractApplication.get().getExceptionHandler().logHandledException(inAppBillingErrorCode.newErrorCodeException());
			if (listener != null) {
				listener.onPurchaseFailed(inAppBillingErrorCode.newErrorCodeException("Purchase failed."));
			}
			
		}
	}
303116
function handleMessageFromHub(ws, json, device_pubkey, bIndirectCorrespondent, callbacks){
	var subject = json.subject;
	var body = json.body;
	if (!subject || typeof body == "undefined")
		return callbacks.ifError("no subject or body");
	//if (bIndirectCorrespondent && ["cancel_new_wallet", "my_xpubkey", "new_wallet_address"].indexOf(subject) === -1)
	//    return callbacks.ifError("you're indirect correspondent, cannot trust "+subject+" from you");
	var from_address = objectHash.getDeviceAddress(device_pubkey);
	
	switch (subject){
		case "pairing":
			device.handlePairingMessage(json, device_pubkey, callbacks);
			break;
		
		case "text":
			message_counter++;
			if (!ValidationUtils.isNonemptyString(body))
				return callbacks.ifError("text body must be string");
			// the wallet should have an event handler that displays the text to the user
			eventBus.emit("text", from_address, body, message_counter);
			callbacks.ifOk();
			break;

		case "object":
			message_counter++;
			if(typeof body !== 'object')
				return callbacks.ifError("body must be object");

			eventBus.emit("object", from_address, body, message_counter);
			callbacks.ifOk();
			break;

		case "removed_paired_device":
		//	if(conf.bIgnoreUnpairRequests) {
		//		// unpairing is ignored
		//		callbacks.ifError("removed_paired_device ignored: "+from_address);
		//	} else {
				determineIfDeviceCanBeRemoved(from_address, function(bRemovable){
					if (!bRemovable)
						return callbacks.ifError("device "+from_address+" is not removable");
					if (conf.bIgnoreUnpairRequests){
						db.query("UPDATE correspondent_devices SET is_blackhole=1 WHERE device_address=?", [from_address]);
						return callbacks.ifOk();
					}
					device.removeCorrespondentDevice(from_address, function(){
						eventBus.emit("removed_paired_device", from_address);
						callbacks.ifOk();
					});
				});
		//	}
			break;

		case "chat_recording_pref":
			message_counter++;
			eventBus.emit("chat_recording_pref", from_address, body, message_counter);
			callbacks.ifOk();
			break;
		
		case "create_new_wallet":
			// {wallet: "base64", wallet_definition_template: [...]}
			walletDefinedByKeys.handleOfferToCreateNewWallet(body, from_address, callbacks);
			break;
		
		case "cancel_new_wallet":
			// {wallet: "base64"}
			if (!ValidationUtils.isNonemptyString(body.wallet))
				return callbacks.ifError("no wallet");
			walletDefinedByKeys.deleteWallet(body.wallet, from_address, callbacks.ifOk);
			break;
		
		case "my_xpubkey": // allowed from non-correspondents
			// {wallet: "base64", my_xpubkey: "base58"}
			if (!ValidationUtils.isNonemptyString(body.wallet))
				return callbacks.ifError("no wallet");
			if (!ValidationUtils.isNonemptyString(body.my_xpubkey))
				return callbacks.ifError("no my_xpubkey");
			if (body.my_xpubkey.length > 112)
				return callbacks.ifError("my_xpubkey too long");
			walletDefinedByKeys.addDeviceXPubKey(body.wallet, from_address, body.my_xpubkey, callbacks.ifOk);
			break;
		
		case "wallet_fully_approved": // allowed from non-correspondents
			// {wallet: "base64"}
			if (!ValidationUtils.isNonemptyString(body.wallet))
				return callbacks.ifError("no wallet");
			walletDefinedByKeys.handleNotificationThatWalletFullyApproved(body.wallet, from_address, callbacks.ifOk);
			break;
		
		case "new_wallet_address":
			// {wallet: "base64", is_change: (0|1), address_index: 1234, address: "BASE32"}
			if (!ValidationUtils.isNonemptyString(body.wallet))
				return callbacks.ifError("no wallet");
			if (!(body.is_change === 0 || body.is_change === 1))
				return callbacks.ifError("bad is_change");
			if (!ValidationUtils.isNonnegativeInteger(body.address_index))
				return callbacks.ifError("bad address_index");
			if (!ValidationUtils.isValidAddress(body.address))
				return callbacks.ifError("no address or bad address");
			walletDefinedByKeys.addNewAddress(body.wallet, body.is_change, body.address_index, body.address, function(err){
				if (err)
					return callbacks.ifError(err);
				callbacks.ifOk();
			});
			break;
			
		case "create_new_shared_address":
			// {address_definition_template: [...]}
			if (!ValidationUtils.isArrayOfLength(body.address_definition_template, 2))
				return callbacks.ifError("no address definition template");
			walletDefinedByAddresses.validateAddressDefinitionTemplate(
				body.address_definition_template, from_address, 
				function(err, assocMemberDeviceAddressesBySigningPaths){
					if (err)
						return callbacks.ifError(err);
					// this event should trigger a confirmatin dialog, user needs to approve creation of the shared address and choose his 
					// own address that is to become a member of the shared address
					eventBus.emit("create_new_shared_address", body.address_definition_template, assocMemberDeviceAddressesBySigningPaths);
					callbacks.ifOk();
				}
			);
			break;
		
		case "approve_new_shared_address":
			// {address_definition_template_chash: "BASE32", address: "BASE32", device_addresses_by_relative_signing_paths: {...}}
			if (!ValidationUtils.isValidAddress(body.address_definition_template_chash))
				return callbacks.ifError("invalid addr def c-hash");
			if (!ValidationUtils.isValidAddress(body.address))
				return callbacks.ifError("invalid address");
			if (typeof body.device_addresses_by_relative_signing_paths !== "object" 
					|| Object.keys(body.device_addresses_by_relative_signing_paths).length === 0)
				return callbacks.ifError("invalid device_addresses_by_relative_signing_paths");
			walletDefinedByAddresses.approvePendingSharedAddress(body.address_definition_template_chash, from_address, 
				body.address, body.device_addresses_by_relative_signing_paths);
			callbacks.ifOk();
			break;
			
		case "reject_new_shared_address":
			// {address_definition_template_chash: "BASE32"}
			if (!ValidationUtils.isValidAddress(body.address_definition_template_chash))
				return callbacks.ifError("invalid addr def c-hash");
			walletDefinedByAddresses.deletePendingSharedAddress(body.address_definition_template_chash);
			callbacks.ifOk();
			break;
			
		case "new_shared_address":
			// {address: "BASE32", definition: [...], signers: {...}}
			walletDefinedByAddresses.handleNewSharedAddress(body, {
				ifError: callbacks.ifError,
				ifOk: function(){
					callbacks.ifOk();
					eventBus.emit('maybe_new_transactions');
				}
			});
			break;
			
		// request to sign a unit created on another device
		// two use cases:
		// 1. multisig: same address hosted on several devices
		// 2. multilateral signing: different addresses signing the same message, such as a (dumb) contract
		case "sign":
			// {address: "BASE32", signing_path: "r.1.2.3", unsigned_unit: {...}}
			if (!ValidationUtils.isValidAddress(body.address))
				return callbacks.ifError("no address or bad address");
			if (!ValidationUtils.isNonemptyString(body.signing_path) || body.signing_path.charAt(0) !== 'r')
				return callbacks.ifError("bad signing path");
			var objUnit = body.unsigned_unit;
			if (typeof objUnit !== "object")
				return callbacks.ifError("no unsigned unit");
			var bJsonBased = (objUnit.version !== constants.versionWithoutTimestamp);
			// replace all existing signatures with placeholders so that signing requests sent to us on different stages of signing become identical,
			// hence the hashes of such unsigned units are also identical
			objUnit.authors.forEach(function(author){
				var authentifiers = author.authentifiers;
				for (var path in authentifiers)
					authentifiers[path] = authentifiers[path].replace(/./, '-'); 
			});
			var assocPrivatePayloads = body.private_payloads;
			if ("private_payloads" in body){
				if (typeof assocPrivatePayloads !== "object" || !assocPrivatePayloads)
					return callbacks.ifError("bad private payloads");
				for (var payload_hash in assocPrivatePayloads){
					var payload = assocPrivatePayloads[payload_hash];
					var hidden_payload = _.cloneDeep(payload);
					if (payload.denomination) // indivisible asset.  In this case, payload hash is calculated based on output_hash rather than address and blinding
						hidden_payload.outputs.forEach(function(o){
							delete o.address;
							delete o.blinding;
						});
					var calculated_payload_hash = objectHash.getBase64Hash(hidden_payload, bJsonBased);
					if (payload_hash !== calculated_payload_hash)
						return callbacks.ifError("private payload hash does not match");
					if (!ValidationUtils.isNonemptyArray(objUnit.messages))
						return callbacks.ifError("no messages in unsigned unit");
					if (objUnit.messages.filter(function(objMessage){ return (objMessage.payload_hash === payload_hash); }).length !== 1)
						return callbacks.ifError("no such payload hash in the messages");
				}
			}
			if (objUnit.messages){
				var arrMessages = objUnit.messages;
				if (!Array.isArray(arrMessages))
					return callbacks.ifError("bad type of messages");
				for (var i=0; i<arrMessages.length; i++){
					if (arrMessages[i].payload === undefined)
						continue;
					var calculated_payload_hash = objectHash.getBase64Hash(arrMessages[i].payload, bJsonBased);
					if (arrMessages[i].payload_hash !== calculated_payload_hash)
						return callbacks.ifError("payload hash does not match");
				}
			}
			else if (objUnit.signed_message){
				// ok
			}
			else
				return callbacks.ifError("neither messages nor signed_message");
			// findAddress handles both types of addresses
			findAddress(body.address, body.signing_path, {
				ifError: callbacks.ifError,
				ifLocal: function(objAddress){
					// the commented check would make multilateral signing impossible
					//db.query("SELECT 1 FROM extended_pubkeys WHERE wallet=? AND device_address=?", [row.wallet, from_address], function(sender_rows){
					//    if (sender_rows.length !== 1)
					//        return callbacks.ifError("sender is not cosigner of this address");
						callbacks.ifOk();
						if (objUnit.signed_message && !ValidationUtils.hasFieldsExcept(objUnit, ["signed_message", "authors"])){
							objUnit.unit = objectHash.getBase64Hash(objUnit); // exact value doesn't matter, it just needs to be there
							return eventBus.emit("signing_request", objAddress, body.address, objUnit, assocPrivatePayloads, from_address, body.signing_path);
						}
						objUnit.unit = objectHash.getUnitHash(objUnit);
						var objJoint = {unit: objUnit, unsigned: true};
						eventBus.once("validated-"+objUnit.unit, function(bValid){
							if (!bValid){
								console.log("===== unit in signing request is invalid");
								return;
							}
							// This event should trigger a confirmation dialog.
							// If we merge coins from several addresses of the same wallet, we'll fire this event multiple times for the same unit.
							// The event handler must lock the unit before displaying a confirmation dialog, then remember user's choice and apply it to all
							// subsequent requests related to the same unit
							eventBus.emit("signing_request", objAddress, body.address, objUnit, assocPrivatePayloads, from_address, body.signing_path);
						});
						// if validation is already under way, handleOnlineJoint will quickly exit because of assocUnitsInWork.
						// as soon as the previously started validation finishes, it will trigger our event handler (as well as its own)
						network.handleOnlineJoint(ws, objJoint);
					//});
				},
				ifRemote: function(device_address){
					if (device_address === from_address){
						callbacks.ifError("looping signing request for address "+body.address+", path "+body.signing_path);
						throw Error("looping signing request for address "+body.address+", path "+body.signing_path);
					}
					var text_to_sign = objectHash.getUnitHashToSign(body.unsigned_unit).toString("base64");
					// I'm a proxy, wait for response from the actual signer and forward to the requestor
					eventBus.once("signature-"+device_address+"-"+body.address+"-"+body.signing_path+"-"+text_to_sign, function(sig){
						sendSignature(from_address, text_to_sign, sig, body.signing_path, body.address);
					});
					// forward the offer to the actual signer
					device.sendMessageToDevice(device_address, subject, body);
					callbacks.ifOk();
				},
				ifMerkle: function(bLocal){
					callbacks.ifError("there is merkle proof at signing path "+body.signing_path);
				},
				ifUnknownAddress: function(){
					callbacks.ifError("not aware of address "+body.address+" but will see if I learn about it later");
					eventBus.once("new_address-"+body.address, function(){
						// rewrite callbacks to avoid duplicate unlocking of mutex
						handleMessageFromHub(ws, json, device_pubkey, bIndirectCorrespondent, { ifOk: function(){}, ifError: function(){} });
					});
				}
			});
			break;
		
		case "signature":
			// {signed_text: "base64 of sha256", signing_path: "r.1.2.3", signature: "base64"}
			if (!ValidationUtils.isStringOfLength(body.signed_text, constants.HASH_LENGTH)) // base64 of sha256
				return callbacks.ifError("bad signed text");
			if (!ValidationUtils.isStringOfLength(body.signature, constants.SIG_LENGTH) && body.signature !== '[refused]')
				return callbacks.ifError("bad signature length");
			if (!ValidationUtils.isNonemptyString(body.signing_path) || body.signing_path.charAt(0) !== 'r')
				return callbacks.ifError("bad signing path");
			if (!ValidationUtils.isValidAddress(body.address))
				return callbacks.ifError("bad address");
			eventBus.emit("signature-" + from_address + "-" + body.address + "-" + body.signing_path + "-" + body.signed_text, body.signature);
			callbacks.ifOk();
			break;
			
		case 'private_payments':
			handlePrivatePaymentChains(ws, body, from_address, callbacks);
			break;
			
		case 'payment_notification':
			// note that since the payments are public, an evil user might notify us about a payment sent by someone else 
			// (we'll be fooled to believe it was sent by the evil user).  It is only possible if he learns our address, e.g. if we make it public.
			// Normally, we generate a one-time address and share it in chat session with the future payer only.
			var current_message_counter = ++message_counter;
			var unit = body;
			if (!ValidationUtils.isStringOfLength(unit, constants.HASH_LENGTH))
				return callbacks.ifError("invalid unit in payment notification");
			var bEmitted = false;
			var emitPn = function(objJoint){
				if (bEmitted)
					return;
				bEmitted = true;
				emitNewPublicPaymentReceived(from_address, objJoint.unit, current_message_counter);
			};
			eventBus.once('saved_unit-'+unit, emitPn);
			storage.readJoint(db, unit, {
				ifNotFound: function(){
					console.log("received payment notification for unit "+unit+" which is not known yet, will wait for it");
					callbacks.ifOk();
				},
				ifFound: function(objJoint){
					emitPn(objJoint);
					eventBus.removeListener('saved_unit-'+unit, emitPn);
					callbacks.ifOk();
				}
			});
			break;
		
		case 'prosaic_contract_offer':
			body.peer_device_address = from_address;
			if (!body.title || !body.text || !body.creation_date)
				return callbacks.ifError("not all contract fields submitted");
			if (!ValidationUtils.isValidAddress(body.peer_address) || !ValidationUtils.isValidAddress(body.my_address))
				return callbacks.ifError("either peer_address or address is not valid in contract");
			if (body.hash !== prosaic_contract.getHash(body))
				return callbacks.ifError("wrong contract hash");
			if (!/^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/.test(body.creation_date))
				return callbacks.ifError("wrong contract creation date");
			prosaic_contract.store(body);
			var chat_message = "(prosaic-contract:" + Buffer.from(JSON.stringify(body), 'utf8').toString('base64') + ")";
			eventBus.emit("text", from_address, chat_message, ++message_counter);
			callbacks.ifOk();
			break;

		case 'prosaic_contract_shared':
			if (!body.title || !body.text || !body.creation_date)
				return callbacks.ifError("not all contract fields submitted");
			if (!ValidationUtils.isValidAddress(body.peer_address) || !ValidationUtils.isValidAddress(body.my_address))
				return callbacks.ifError("either peer_address or address is not valid in contract");
			if (body.hash !== prosaic_contract.getHash(body))
				return callbacks.ifError("wrong contract hash");
			if (!/^\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}$/.test(body.creation_date))
				return callbacks.ifError("wrong contract creation date");
			db.query("SELECT 1 FROM my_addresses \n\
					JOIN wallet_signing_paths USING(wallet)\n\
					WHERE my_addresses.address=? AND wallet_signing_paths.device_address=?",[body.my_address, from_address],
				function(rows) {
					if (!rows.length)
						return callbacks.ifError("contract does not contain my address");
					prosaic_contract.store(body);
					callbacks.ifOk();
				}
			);
			break;

		case 'prosaic_contract_response':
			var validation = require('./validation.js');

			if (body.status !== "accepted" && body.status !== "declined")
				return callbacks.ifError("wrong status supplied");

			prosaic_contract.getByHash(body.hash, function(objContract){
				if (!objContract)
					return callbacks.ifError("wrong contract hash");
				if (body.status === "accepted" && !body.signed_message)
					return callbacks.ifError("response is not signed");
				var processResponse = function(objSignedMessage) {
					if (body.authors && body.authors.length) {
						if (body.authors.length !== 1)
							return callbacks.ifError("wrong number of authors received");
						var author = body.authors[0];
						if (author.definition && (author.address !== objectHash.getChash160(author.definition)))
							return callbacks.ifError("incorrect definition received");
						if (!ValidationUtils.isValidAddress(author.address) || author.address !== objContract.peer_address)
							return callbacks.ifError("incorrect author address");
						// this can happen when acceptor and offerer have same device in cosigners
						db.query('SELECT 1 FROM my_addresses WHERE address=? \n\
							UNION SELECT 1 FROM shared_addresses WHERE shared_address=?', [author.address, author.address], function(rows) {
								if (rows.length)
									return;
								db.query("INSERT "+db.getIgnore()+" INTO peer_addresses (address, device_address, signing_paths, definition) VALUES (?, ?, ?, ?)",
									[author.address, from_address, JSON.stringify(Object.keys(objSignedMessage.authors[0].authentifiers)), JSON.stringify(author.definition)],
									function(res) {
										if (res.affectedRows == 0)
											db.query("UPDATE peer_addresses SET signing_paths=?, definition=? WHERE address=?", [JSON.stringify(Object.keys(objSignedMessage.authors[0].authentifiers)), JSON.stringify(author.definition), author.address]);
									}
								);
							}
						);
					}
					if (objContract.status !== 'pending')
						return callbacks.ifError("contract is not active, current status: " + objContract.status);
					var objDateCopy = new Date(objContract.creation_date_obj);
					if (objDateCopy.setHours(objDateCopy.getHours() + objContract.ttl) < Date.now())
						return callbacks.ifError("contract already expired");
					prosaic_contract.setField(objContract.hash, "status", body.status);
					eventBus.emit("text", from_address, "contract \""+objContract.title+"\" " + body.status, ++message_counter);
					eventBus.emit("prosaic_contract_response_received" + body.hash, (body.status === "accepted"), body.authors);
					callbacks.ifOk();
				};
				if (body.signed_message) {
					var signedMessageJson = Buffer.from(body.signed_message, 'base64').toString('utf8');
					try{
						var objSignedMessage = JSON.parse(signedMessageJson);
					}
					catch(e){
						return callbacks.ifError("wrong signed message");
					}
					validation.validateSignedMessage(objSignedMessage, function(err) {
						if (err || objSignedMessage.authors[0].address !== objContract.peer_address || objSignedMessage.signed_message != objContract.title)
							return callbacks.ifError("wrong contract signature");
						processResponse(objSignedMessage);
					});
				} else
					processResponse();
			});
			break;

		case 'prosaic_contract_update':
			prosaic_contract.getByHash(body.hash, function(objContract){
				if (!objContract || objContract.peer_device_address !== from_address)
					return callbacks.ifError("wrong contract hash or not an owner");
				if (body.field == "status") {
					if (body.value !== "revoked" || objContract.status !== "pending")
							return callbacks.ifError("wrong status for contract supplied");
				} else 
				if (body.field == "unit") {
					if (objContract.status !== "accepted")
						return callbacks.ifError("contract was not accepted");
					if (objContract.unit)
							return callbacks.ifError("unit was already provided for this contract");
				} else
				if (body.field == "shared_address") {
					if (objContract.status !== "accepted")
						return callbacks.ifError("contract was not accepted");
					if (objContract.shared_address)
							return callbacks.ifError("shared_address was already provided for this contract");
						if (!ValidationUtils.isValidAddress(body.value))
							return callbacks.ifError("invalid address provided");
				} else {
					return callbacks.ifError("wrong field");
				}
				prosaic_contract.setField(objContract.hash, body.field, body.value);
				callbacks.ifOk();
			});
			break;
			
		default:
			callbacks.ifError("unknnown subject: "+subject);
	}
}
366848
public function addPubkey($pubkey)
    {
        if (!$this->getPubkeys()) {
            return $this->setPubkeys(array($pubkey));
        } else {
            return $this->setPubkeys(
                array_merge($this->getPubkeys(), array($pubkey))
            );
        }
    }
646554
def note_deletion(self, key):
        
        # If we'rew deleting a key we previously added, then there is no diff
        if key in self._added:
            self._added.remove(key)
        else:
            # If the deleted key was previously changed, use the original value
            if key in self._previous:
                self._deleted[key] = self._previous[key]
                del self._previous[key]
            else:
                self._deleted[key] = self._instance[key]