Статьи

Предотвращение недопустимых расходов транслируемых государств

Corda очень гибок и позволит вам собрать код, необходимый для написания множества сложных рабочих процессов. Эта гибкость имеет один недостаток. Вам, разработчику, нужно потратить некоторое время и подумать о разработке вашего приложения. Нет больше пустых контрактов. Нет больше потоков ответчика, которые подписывают все, что они получают. Вы будете рады, что приложили усилия, когда ваше приложение без проблем работает на производстве. К счастью для вас, я здесь, чтобы защитить ваши хранилища от хитрых сделок и недопустимых расходов ваших штатов.

В моем предыдущем посте, « Передача транзакции внешним организациям» , я показал вам, как делиться транзакциями и состояниями внутри них с кем-либо в сети. Повышение потенциальных возможностей для государств, которые будут потрачены способами, которые не были предусмотрены. Возможно, ведущий к бизнес-процессу, который CorDapp (s) определяет развалиться.

Я дал вам знания, чтобы оказаться в такой ситуации, и теперь я пытаюсь помешать вам совершить глупые и потенциально существенные ошибки. Другими словами, я дал вам ключи от машины, и теперь я пытаюсь не дать вам разбиться или сбить кого-нибудь.

Или даже,

С большой властью приходит большая ответственность.

В этой статье я расскажу о видах проверок, которые вы должны добавлять в свои контракты и потоки, чтобы другие узлы не могли получить преимущества от потенциальных упущений в вашем приложении. Точнее, предотвращение того, чтобы государства, полученные от транслируемых транзакций, расходовались непредвиденными способами. После внесения этих изменений государства могут расходовать только те стороны, которые полностью разрешено приложением.

Я буду использовать MessageState ( LinearState ), который я включил во все мои сообщения Corda. Для ясности добавлено ниже:

1
2
3
4
5
6
7
8
@BelongsToContract(MessageContract::class)
data class MessageState(
  val sender: Party,
  val recipient: Party,
  val contents: String,
  override val linearId: UniqueIdentifier,
  override val participants: List<Party> = listOf(sender, recipient)
) : LinearState

Используя это состояние в качестве примера, я могу помочь вам подумать о разработке ваших собственных контрактов и проверке ваших потоков. Рабочий процесс, который мы рассмотрим, — это процесс ответа на сообщение, отправленное между двумя узлами.

Проверка договора

Я надеюсь, вы уже знаете это. Проверка контракта важна. Это проверка, выполняемая каждым подписавшим стороной транзакции и любым, кому необходимо проверить эту же транзакцию в будущем. Государства могут развиваться только так, как это предусмотрено их контрактом. Кто может их создать, кто может их потратить, какие ценности они могут иметь, сколько их на транзакцию и так далее. Это зависит от вас, чтобы определить эти правила.

Ниже приведены некоторые общие требования, которые, на мой взгляд, важны для обеспечения того, чтобы контракт устанавливал необходимые границы для государства. Направляя государство по возможным маршрутам, оно может быть создано и преобразовано:

01
02
03
04
05
06
07
08
09
10
11
12
override fun verify(tx: LedgerTransaction) {
  val commandWithParties: CommandWithParties<Commands> = tx.commands.requireSingleCommand()
  when (commandWithParties.value) {
    is Commands.Send -> // validation for sending
    is Commands.Reply -> requireThat {
      val inputPublicKeys = tx.inputs.flatMap { it.state.data.participants.map(AbstractParty::owningKey) }.toSet()
      "The input participant keys are a subset of the signing keys" using commandWithParties.signers.containsAll(inputPublicKeys)
      val outputPublicKeys = tx.outputStates.flatMap { it.participants.map(AbstractParty::owningKey) }.toSet()
      "The output participant keys are a subset of the signing keys" using commandWithParties.signers.containsAll(outputPublicKeys)
    }
  }
}

Хотя эта проверка относится к LinearState , я фактически использовал правила из ContractsDSL.verifyMoveCommand . Возможно, это указывает на то, что мой MessageState на самом деле является OwnableState (состояние с одним владельцем), но давайте OwnableState с этим под ковриком & # 56904; & # 56357; & # 56906; & # 56906 ;.

Правила в приведенном выше фрагменте проверяют, что транзакция будет подписана всеми участниками состояний ввода и вывода. Обеспечение всем соответствующим сторонам возможности подтвердить транзакцию. Другими словами, поток должен включать стороны, упомянутые в состояниях ввода и вывода в необходимых подписывающих элементах команды, и сеансы, открытые для отправки им транзакции.

Благодаря этим правилам невозможно передать транзакцию в чье-либо хранилище без подписи транзакции участниками состояний ввода и вывода. Это, с точки зрения контракта, будет незаконным. Позвольте мне перефразировать это в более управляемом объяснении. Любые транзакции, использующие MessageState не могут быть созданы без уведомления первоначальных участников о состоянии. Более того, ответ не может быть создан и сохранен в хранилище участника без их согласия.

Эти правила являются хорошей основой для создания собственного контракта проверки. Явное указание того, какие стороны должны подписать транзакцию, является важным требованием для подтверждения ее действительности. Дополнительные правила могут быть добавлены по мере необходимости.

Например, следующие проверки могут быть добавлены к примеру выше:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
override fun verify(tx: LedgerTransaction) {
  val commandWithParties: CommandWithParties<Commands> = tx.commands.requireSingleCommand()
  val command = commandWithParties.value
  when (command) {
    is Commands.Send -> // validation for sending
    is Commands.Reply -> requireThat {
      // general requirements from previous snippet
      // precise requirements for `MessageState`
      "One input should be consumed when replying to a message." using (tx.inputs.size == 1)
      "Only one output state should be created when replying to a message." using (tx.outputs.size == 1)
      val output = tx.outputsOfType<MessageState>().single()
      val input = tx.inputsOfType<MessageState>().single()
      "Only the original message's recipient can reply to the message" using (output.sender == input.recipient)
      "The reply must be sent to the original sender" using (output.recipient == input.sender)
      // covered by the general requirements
      "The original sender must be included in the required signers" using commandWithParties.signers.contains(input.sender.owningKey)
      "The original recipient must be included in the required signers" using commandWithParties.signers.contains(input.recipient.owningKey)
    }
  }
}

Эти правила являются специфическими для MessageState использования MessageState . Ограничение сторон, которые могут ответить на сообщение. Точнее, отправитель ответа должен быть получателем исходного сообщения и может быть отправлен обратно только исходному отправителю. Эти правила имеют смысл в этом сценарии.

Даже с этими правилами, хитрые транзакции все еще могут проходить. Контракт проверяет подписи на основе состояний транзакции, а также содержания состояний. Но есть еще несколько способов обойти это.

При этом некоторые из этих ситуаций могут возникать только в том случае, если транзакция была отправлена ​​сторонам, которые изначально не участвовали в обмене. Это тот сценарий, который может возникнуть в результате трансляции транзакций, как я упоминал во введении и в трансляции транзакций внешним организациям .

Теперь, когда я подготовил почву, позвольте мне объяснить дыру в контракте выше. Ничто не мешает сторонним разработчикам создавать имитированный ответ на исходное сообщение. При этом отправитель и получатель ответа действительны, но сообщение создано кем-то, кто на самом деле не является отправителем. Немного поразмыслить, поэтому вам, возможно, придется прочитать это предложение несколько раз.

Чтобы этого не происходило, необходимо добавить некоторую логику в поток, который создает эту транзакцию. Это будет темой следующего раздела.

Прежде чем мы продолжим, я хочу еще раз подчеркнуть, что эта ситуация может возникнуть, только если исходная транзакция была передана другим сторонам. Таким образом, это может быть ситуация, которая никогда не случается. Но это возможно. Охватить ваши базы в этих сценариях действительно зависит от вашего варианта использования и от того, насколько вы параноидальны из-за того, что пользователи шаливают.

Проверка потока

Потоки предоставляют пространство для добавления проверки транзакции, которая находится за пределами договора. Это могут быть правила, которые более специфичны для отдельной организации (я затронул этот вопрос в разделе «Расширение потоков» для настройки проверки транзакции ), или использовать информацию, недоступную для контракта.

Исходя из предыдущего раздела, где я упомянул, в проверке контракта есть несколько пробелов. Поток респондента ниже добавляет два ограничения для их исправления:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@InitiatedBy(ReplyToMessageFlow::class)
class ReplyToMessageResponder(private val session: FlowSession) : FlowLogic<SignedTransaction>() {
 
  @Suspendable
  override fun call(): SignedTransaction {
    val stx = subFlow(object : SignTransactionFlow(session) {
      override fun checkTransaction(stx: SignedTransaction) {
        val message = stx.coreTransaction.outputsOfType<MessageState>().single()
        require(message.sender != ourIdentity) {
          "The sender of the new message cannot have my identity when I am not the creator of the transaction"
        }
        require(message.sender == session.counterparty) {
          "The sender of the reply must must be the party creating this transaction"
        }
      }
    })
    return subFlow(
      ReceiveFinalityFlow(
        otherSideSession = session,
        expectedTxId = stx.id
      )
    )
  }
}

Блоки require в этом примере невозможны изнутри договора, так как им нужна информация, которую договор не содержит. Этот дополнительный контекст жизненно важен. Договор может быть подтвержден только на основании предоставленной информации. С другой стороны, поток знает, что он находится на принимающей стороне предложенной транзакции и кто является контрагентом, отправляющим ее.

Используя эти знания, поток может добавить два новых правила.

  • Отправитель ответа не может иметь идентификатор потока. Запретить другому лицу выдавать себя за свою личность и пытаться отправить ответ от ее имени.
  • Отправитель ответа должен быть владельцем сеанса контрагента. Предотвращение подражания личности отправителя другой стороной.

Хотя второе правило является надмножеством первого, их разделение дает возможность улучшить сообщение об ошибке. Если сообщения не важны для вас, тогда можно удалить первое require .

Благодаря контракту, транзакция должна собрать подпись этой стороны, прежде чем она может быть сохранена. Предоставление возможности их респондентам добавлять дополнительную проверку и отклонять ее при необходимости. С контрактом и потоком, работающим как команда, шансы незаконного использования потока значительно уменьшены.

Вывод

Благодаря гибкости Corda, вы, как разработчик CorDapp, обязаны ограничить ваше приложение настолько, чтобы предотвратить его неправильное использование. Это дополнительно осложняется возможностью обмена транзакциями с узлами, не участвующими в исходных взаимодействиях. Если CorDapp не составлен вдумчиво, состояния транзакции могут быть потрачены стороной, которая не имеет прямого владения этими состояниями. Проверка, найденная в ваших контрактах и ​​потоках, важна для предотвращения возникновения этих сценариев.

Тем не менее, важно помнить, что это действительно зависит от вашего варианта использования. Возможно, вы хотите разрешить сторонам тратить государства, которым они не принадлежат, на транзакции, которыми они обмениваются. В этом случае вы можете ослабить ограничения CorDapp. Пока это не дизайн, а надзор.

Если вам понравился этот пост или вы нашли его полезным (или и тем, и другим), пожалуйста, не стесняйтесь, следите за мной в Твиттере на @LankyDanDev и не забудьте поделиться с кем-либо, кто может найти это полезным!

Опубликовано на Java Code Geeks с разрешения Дэна Ньютона, партнера нашей программы JCG . См. Оригинальную статью здесь: Предотвращение недопустимых расходов транслируемых штатов

Мнения, высказанные участниками Java Code Geeks, являются их собственными.