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

sss.openstar.controller.SendMessage.scala Maven / Gradle / Ivy

package sss.openstar.controller

import sss.ancillary.{Guid, Logging}
import sss.db.Db
import sss.openstar.balanceledger.{TxIndex, TxOutput}
import sss.openstar.chains.Chains.GlobalChainIdMask
import sss.openstar.chains.TxWriterActor.{InternalCommit, InternalTempNack}
import sss.openstar.contract.SaleOrReturnSecretEnc
import sss.openstar.identityledger.IdentityServiceQuery
import sss.openstar.message.MessageInBoxActor.SaveOutgoingMessage
import sss.openstar.message.payloads.PaywalledMessage
import sss.openstar.message.{PaywallCharges, ProviderStoreCharges, _}
import sss.openstar.network.MessageEventBus
import sss.openstar.wallet.UnlockedWallet
import sss.openstar.wallet.Wallet.CreditTooLow
import sss.openstar.{Currency, ExtraMessageKeys, UniqueNodeIdentifier}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
import scala.util.{Failure, Success, Try}

object SendMessage {

  type SubmitMessage = (UniqueNodeIdentifier,
    UnlockedWallet[Currency],
    Int,
    Set[UniqueNodeIdentifier],
    MessageComposite,
    Option[Guid],
    Guid) => Future[Unit]


}

class SendMessage(currentBlockHeight: () => Long,
                  serviceProviderPayment: ProviderStoreCharges,
                  paywallCharges: PaywallCharges
                 )
                 (implicit
                  db: Db,
                  events: MessageEventBus,
                  send: Send,
                  identityServiceQuery: IdentityServiceQuery,
                  chainId: GlobalChainIdMask
                 ) extends Logging {


  type PayWallResult = Seq[(PaywallEnvelope, Set[String])]

  private def processProviderPayments(numBlocksToLive: Int,
                                      tos: Set[UniqueNodeIdentifier],
                                      userWallet: UnlockedWallet[Currency],
                                      msg: Message,
                                      acc: PayWallResult,
                                     ): Future[PayWallResult] = {

    if (tos.isEmpty) {
      Future.successful(acc)
    } else {
      val to = tos.head
      log.info(s"Balance now ${userWallet.w.balance()}")

      Future.fromTry {

        for {
          chargePerMessage <- serviceProviderPayment(to)
          userPaywallCharge <- paywallCharges(userWallet.w.walletOwner, to)
        } yield(chargePerMessage, userPaywallCharge)

      } flatMap { case (chargePerMessage, userPaywallCharge) =>

        val totalCharges = (userWallet.w.amount(userPaywallCharge) + chargePerMessage).get
        val providers = identityServiceQuery.messageStores(to) + to
        if(totalCharges.value <= 0) {

          val wrappedPayload = MessagePayloadDecoder
            .toPayloadOpt(PaywalledMessage(Some(userWallet.w.walletOwner), None, msg.msgPayload))
            .getOrElse(throw new Exception(s"Couildn't make payload from PaywalledMessage"))

          val wrappedMessage = Message(wrappedPayload, msg.guid, msg.parentGuid)
          processProviderPayments(numBlocksToLive, tos.tail, userWallet, msg, acc :+ (PaywallEnvelope(None, to, wrappedMessage), providers))

        } else {
          userWallet.w.createTx(totalCharges, numBlocksToLive) match {
            case Success(baseTx) =>

              val txPlusProviderCharge = if(chargePerMessage > 0) {
                val paymentProvider = TxOutput(chargePerMessage,
                  SaleOrReturnSecretEnc(userWallet.w.walletOwner,
                    providers.toSeq,
                    None,
                    currentBlockHeight() + numBlocksToLive
                  )
                )
                userWallet.w.appendOutputs(baseTx, paymentProvider)
              } else baseTx

              val txPlusUserPaywallCharge = if(userPaywallCharge > 0) {
                val paymentUserPaywall = TxOutput(userPaywallCharge,
                  SaleOrReturnSecretEnc(userWallet.w.walletOwner,
                    to,
                    None,
                    currentBlockHeight() + numBlocksToLive
                  )
                )
                userWallet.w.appendOutputs(txPlusProviderCharge, paymentUserPaywall)
              } else txPlusProviderCharge

              userWallet.payAsync(txPlusUserPaywallCharge) flatMap {
                case _: InternalCommit =>

                  val providerPaymentIndex = if(chargePerMessage > 0) {
                    Some(TxIndex(txPlusProviderCharge.txId, txPlusUserPaywallCharge.outs.size - 1))
                  } else None

                  val paywallPaymentTxIndex = if(userPaywallCharge > 0) {
                    Some(TxIndex(txPlusProviderCharge.txId, txPlusProviderCharge.outs.size - 1))
                  } else None

                  val wrappedPayload = MessagePayloadDecoder
                    .toPayloadOpt(PaywalledMessage(Some(userWallet.w.walletOwner), paywallPaymentTxIndex, msg.msgPayload))
                    .getOrElse(throw new Exception(s"Couildn't make payload from PaywalledMessage"))

                  val wrappedMessage = Message(wrappedPayload, msg.guid, msg.parentGuid)
                  log.debug(s"Sending guid ${msg.guid} parent guid ${msg.parentGuid} to providers $providers}")

                  processProviderPayments(numBlocksToLive, tos.tail, userWallet, msg, acc :+ (PaywallEnvelope(providerPaymentIndex, to, wrappedMessage), providers))

                case _: InternalTempNack =>
                  processProviderPayments(numBlocksToLive, tos.tail, userWallet, msg, acc)

                case e =>
                  throw new Exception(s"payAsync fail ${e.toString}")

              }

            case Failure(e: CreditTooLow) =>
              log.warn(s"CreditTooLow ${userWallet.w.walletOwner}", e)
              Future.failed(e)

            case Failure(e) =>
              log.warn("send in order failed {}", e)
              Future.failed(new Exception(s"Failed to create tx for delivery charge to $to"))
          }
        }
      }
    }
  }


  def sendToRecipients(
                        from: UniqueNodeIdentifier,
                        userWallet: UnlockedWallet[Currency],
                        numBlocksToLive: Int,
                        tos: Set[UniqueNodeIdentifier],
                        o: MessageComposite,
                        parentGuid: Option[Guid],
                        guid: Guid
                      ): Future[Unit] = {

    Try[Message] {

      log.debug("Message To Send begins")

      val payload = MessagePayloadDecoder.toPayload(o)
      Message(payload, guid, parentGuid)

    } match {
      case Failure(e) =>
        Future.failed(e)
      case Success((msg)) =>
        val tosMinusSelf = tos.filterNot(_ == from)
        processProviderPayments(numBlocksToLive, tosMinusSelf, userWallet, msg, Seq.empty) flatMap { seqProviderPayments =>
          saveMessage(from, tosMinusSelf.mkString(","), msg) map { _ =>
            seqProviderPayments.map {
              case (paywallEnv, providers) =>
                send(ExtraMessageKeys.PaywallEnvelope, paywallEnv, providers)
            }
          }
        }
    }

  }

  private def saveMessage(from: UniqueNodeIdentifier, to: String, msg: Message): Future[Unit] = {
    val p = Promise[Unit]()
    events publish SaveOutgoingMessage(from, msg, p)
    p.future
  }


}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy