All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sss.openstar.message.payloads.IncomingMessageEncryptionConsumer.scala Maven / Gradle / Ivy

package sss.openstar.message.payloads

import sss.ancillary.FailWithException.fail
import sss.ancillary.Logging
import sss.openstar.{Currency, UniqueNodeIdentifier}
import sss.openstar.account.{NodeIdTag, NodeIdentity}
import sss.openstar.balanceledger.{StandardTx, TxInput, TxOutput}
import sss.openstar.chains.TxWriterActor.{InternalCommit, InternalTempNack, InternalTxResult}
import sss.openstar.contacts.ContactService
import sss.openstar.contract.SaleSecretDec
import sss.openstar.identityledger.IdentityServiceQuery
import sss.openstar.message.MessageProcessorUtils.mergeMessages
import sss.openstar.message.payloads.MessageEcryption.{BodyToBeEncrypted, EmbeddedBounty, EncryptedMessage, EncryptedSessionKey}
import sss.openstar.message.payloads.ThreadedMessage.Threadable
import sss.openstar.message.{Add, AsyncMessageProcessor, ConsumeResult, ExpandedComposite, ExpandedMessage, IncomingMessageConsumer, Message, MessageDecoder, TempRejected, UtxoWatch, ValidateBounty}
import sss.openstar.network.MessageEventBus
import sss.openstar.wallet.UnlockedWallet

import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}




class IncomingMessageEncryptionConsumer(validateBounty: ValidateBounty,
                                        userId: NodeIdentity,
                                        userWallet: UnlockedWallet[Currency],
                                        messageDecoder: MessageDecoder)(
                                 implicit identityServiceQuery: IdentityServiceQuery,
                                 events: MessageEventBus,
                                 watchUtxo:UtxoWatch,
                                 contactService: ContactService,
                                 ec: ExecutionContext)
  extends IncomingMessageConsumer with Logging {


  override val incoming: AsyncMessageProcessor = {
    case (ExpandedMessage(ExpandedComposite(msg: EncryptedMessage, guid, payload), None)) =>

      msg.bountyOpt match {
        case None if (msg.author.nodeId == userId.id) =>
          Future.successful(Seq.empty[ConsumeResult])
        case None =>
          Future.successful(Seq(Add(Message(payload, guid, None))))
        case Some(bounty) =>
          decrypt(msg.author, msg) flatMap { body =>
            processPayment(msg.author.nodeId, bounty, msg.receivers, body.secret).map {

              case _: InternalCommit if (msg.author.nodeId == userId.id) =>
                // if we are sending to ourselves, take the payment but it's already saved so don't do anything
                Seq.empty
              case _: InternalCommit =>
                Seq(Add(Message(payload, guid, None)))
              case _: InternalTempNack =>
                Seq(TempRejected)
              case e => fail(e.toString)
            }
          }
      }

    case ExpandedMessage(ExpandedComposite(msg: EncryptedMessage, guid, payload),
            Some(ExpandedComposite(parentMsg: Threadable, parentGuid, _))) =>

      msg.bountyOpt match {
        case None if(msg.author.nodeId == userId.id) =>
          Future.successful(Seq.empty[ConsumeResult])
        case None =>
          Future(mergeMessages(msg, guid, payload, parentMsg, parentGuid))
        case Some(bounty) =>
          decrypt(msg.author, msg).flatMap { body =>
            processPayment(msg.author.nodeId, bounty, msg.receivers, body.secret).map {
              case _: InternalCommit if (msg.author.nodeId == userId.id) =>
                // if we are sending to ourselves, take the payment but it's already saved so don't do anything
                Seq.empty
              case _: InternalCommit =>
                mergeMessages(msg, guid, payload, parentMsg, parentGuid)
              case _: InternalTempNack =>
                Seq(TempRejected)
              case e => fail(e.toString)
              // if we are sending to ourselves, take the payment but it's already saved to don't do anything
            }

          }
      }

  }

  /**
    * I wrote this using a fold at first, recursion is just clearer.
    */
  @tailrec
  private def findTxOffset(
                            recipients: Seq[UniqueNodeIdentifier],
                            id: UniqueNodeIdentifier,
                            acc: Int = 0): Option[Int] = {
    recipients.headOption match {
      case None => None
      case Some(recipient) if (recipient == id) => Some(acc)
      case _ => findTxOffset(recipients.tail, id, acc + 1)
    }
  }

  private def decrypt(author: NodeIdTag, eMsg: EncryptedMessage): Future[BodyToBeEncrypted] = {
    identityServiceQuery.accountOpt(author.nodeId, author.tag) match {
      case None =>
        fail(s"Message from unknown identity ${author}, cannot decrypt")
      case Some(_) =>
        eMsg.decrypt(userId, MessageEcryption.createLookupId(identityServiceQuery))
    }
  }

  private def processPayment(
                              author: UniqueNodeIdentifier,
                              bounty: EmbeddedBounty,
                              receivers: Seq[EncryptedSessionKey],
                              secret:Array[Byte]): Future[InternalTxResult] = Future {

        if (validateBounty(bounty.amountOfBounty, author)) {
          val recipientIds = receivers.map(_.identity.tag)
          val offset = findTxOffset(recipientIds, userId.id).getOrElse(fail(s"${userId.id} not in ${recipientIds}"))
          val inIndex = bounty.addrOfBounty.copy(index = bounty.addrOfBounty.index + offset)
          val in = TxInput(inIndex, bounty.amountOfBounty, SaleSecretDec)
          val out = TxOutput(bounty.amountOfBounty, userWallet.w.encumberToIdentity())
          StandardTx(Seq(in), Seq(out))

        } else {
          fail(s"Message bounty failed to validate ${bounty.amountOfBounty}")
        }

  }.flatMap {
    userWallet.payAsync(_, Some(secret))
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy