Оригинальное название: «Введение в механизм принудительного включения Rollup».

Автор: НИК Лин, руководитель Taipei Ethereum Meetup

 

Буквально вчера произошло то, что шокировало бесчисленное количество людей: Linea, второй уровень Ethereum, запущенный материнской компанией Metamask Consensys, был превентивно закрыт. И это не может не напомнить людям о предыдущем закрытии BSC Chain (BNB Chain) по официальному согласованию с целью снижения потерь от хакерских атак. Всякий раз, когда люди говорят о подобных вещах, они начинают сомневаться в ценности децентрализации, которую отстаивает Web3.

Конечно, основная причина вышеупомянутых инцидентов кроется скорее в несовершенстве самой инфраструктуры, то есть в недостаточной децентрализации: если цепочка достаточно децентрализована, то она не должна останавливаться на одном месте. Из-за уникальной структуры второго уровня Эфириума большая часть уровня 2 опирается на централизованный секвенсор. Хотя в последние годы появляется все больше и больше аргументов в пользу децентрализованных секвенсоров, учитывая назначение второго уровня и его структуру, мы, вероятно, так и делаем. Можно считать, что сортировщик уровня 2, скорее всего, не будет очень децентрализованным и, в конце концов, может быть не таким децентрализованным, как цепочка BSC. Если это действительно так, что нам делать?

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

За последние несколько лет основные вторые уровни Ethereum предложили различные решения проблем антицензуры, такие как Loopring и Degate, функции принудительного вывода и аварийного выхода StarkEx, а также функцию принудительного включения Arbitrum и других OP Rollup. Эти методы могут создавать проверки. и балансы на Sequencer при определенных условиях, чтобы предотвратить отклонение любого запроса транзакции пользователя без причины.

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

Обзор транзакций и принудительное включение

Устойчивость к цензуре транзакций (Censorship Resistance) очень важна для блокчейна. Если блокчейн может произвольно просматривать и отклонять транзакции, инициированные пользователями, он ничем не будет отличаться от сервера Web2. Текущая устойчивость транзакций Ethereum к цензуре обусловлена ​​большим количеством валидаторов. Если кто-то хочет просмотреть транзакции Боба и предотвратить загрузку его транзакций в цепочку, он должен либо попытаться подкупить большинство валидаторов в сети, либо рассылать спам всем. сети Постоянно отправлять ненужные транзакции с более высокой комиссией за обработку, чем у Боба, чтобы захватить пространство блока. В любом случае, стоимость будет очень высокой.

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

А как насчет роллапа? Rollup не требует большого количества валидаторов для обеспечения безопасности. Даже если Rollup имеет только одну централизованную роль (Sequencer) для генерации блоков, он так же безопасен, как L1. Но безопасность и устойчивость к цензуре — это две разные вещи. Даже если Rollup так же безопасен, как Ethereum, и имеет только один централизованный секвенсор, любые транзакции пользователя могут подвергаться цензуре.

Sequencer может отказаться обрабатывать транзакцию пользователя, в результате чего средства пользователя будут удержаны и он не сможет покинуть накопительный пакет.

Механизм принудительного включения

Вместо того, чтобы требовать от Rollup большого количества децентрализованных секвенсоров, лучше напрямую использовать антицензурные возможности L1:

Изначально Sequencer должен упаковывать данные транзакции и отправлять их в контракт L1 Rollup. Лучше добавить в контракт конструкцию, чтобы пользователи могли самостоятельно вставлять транзакции в контракт Rollup. Этот механизм называется «Принудительное включение». Пока Sequencer не может подвергать пользователей цензуре на уровне L1, он не может запретить пользователям принудительно вставлять транзакции на уровне L1. Таким образом, Rollup может унаследовать устойчивость L1 к цензуре.

Sequencer не может просматривать транзакции пользователя L1, не заплатив высокую цену.

Как должны вступить в силу принудительные транзакции?

Если транзакции разрешено записывать непосредственно в контракт накопительного пакета посредством принудительного включения (то есть вступает в силу немедленно), статус накопительного пакета изменится немедленно. Например, Боб вставляет транзакцию «перевести 1000 DAI Кэрол» посредством принудительного включения. Если транзакция вступит в силу немедленно, баланс Боба в последнем состоянии будет на 1000 DAI меньше, а баланс Кэрол будет на 1000 DAI больше.

Если Force Inclusion может напрямую записать транзакцию в контракт Rollup и немедленно вступить в силу, статус изменится немедленно.

Если в это время Sequencer также собирает транзакции вне цепочки и отправляет следующий пакет транзакций в контракт Rollup, на него могут повлиять транзакции, которые Боб принудительно вставляет и которые немедленно вступают в силу. Такого рода проблем следует избегать, насколько это возможно, поэтому Rollup обычно не приводит к немедленному вступлению в силу транзакции принудительного включения. Вместо этого он сначала позволяет пользователю вставить транзакцию в очередь ожидания на L1 и войти в состояние «подготовки». .

Когда Sequencer упаковывает транзакции вне цепочки и отправляет их в контракт Rollup, он выбирает, вставлять ли вышеупомянутые транзакции в последовательность транзакций. Если Sequencer игнорировал эти транзакции в состоянии «подготовки», после окончания периода окна пользователь. может принудительно выполнить эти транзакции. Вставьте в контракт объединения.

Секвенсор может решить, когда «случайно получить» транзакции, ожидающие в очереди.

Секвенсор по-прежнему может отказаться обрабатывать транзакции в очереди ожидания.

Если Sequencer отказывается в течение длительного времени, через некоторое время любой может принудительно включить транзакцию в контракт Rollup с помощью функции Force Inclusion.

Далее мы представим по порядку реализацию механизма Force Inclusion четырех известных Rollup, включая Optimism, Arbitrum, StarkNet и zkSync.

Механизм принудительного включения оптимизма

Сначала мы представим процесс депозита Оптимизма. Этот депозит не только относится к внесению денег в Оптимизм, но также включает в себя «отправку информации, отправленной пользователями на L2», на L2. После получения вновь депонированного сообщения узел L2 преобразует сообщение в транзакцию L2 для выполнения и отправляет его назначенному получателю сообщения.

Сообщение пользователя от депозита L1 к L2

Контракт L1CrossDomainMessenger

Когда пользователь хочет внести токены ETH или ERC-20 в Optimism, он будет взаимодействовать с контрактом L1StandardBridge на L1 через интерфейсную веб-страницу, указывая, какую сумму внести и какой адрес L2 для получения этих активов.

Контракт L1StandardBridge передает сообщение контракту L1CrossDomainMessenger следующего уровня. Этот контракт в основном используется в качестве компонента для взаимной связи между L1 и L2. L1StandardBridge связывается с L2StandardBridge на L2 через этот общий компонент связи, чтобы решить, кто может передать токен. в монетах L2 или кто может разблокировать токены из L1.

Если разработчику необходимо разработать контракт, который передает и синхронизирует статус между L1 и L2, он может построить его на основе контракта L1CrossDomainMessenger.

Сообщение пользователя передается с L1 на L2 через контракт CrossDomainMessenger.

Примечание. На некоторых изображениях в этой статье CrossDomainMessager записан как CrossChainMessager.

ОптимизмПортальный контракт

Контракт L1CrossDomainMessenger затем отправит сообщение контракту OptimismPortal нижнего уровня. После обработки контракт OptimismPortal выдаст событие под названием TransactionDeposited. Параметры включают в себя «человек, отправивший сообщение», «человек, получивший сообщение». и соответствующие параметры выполнения.

Затем узел L2 Optimism прослушивает событие Transaction Deposited, созданное контрактом OptimismPortal, и преобразует параметры события в транзакцию L2. Инициатором этой транзакции будет «отправитель сообщения», указанный в параметре события Transaction Deposited. получателем транзакции является «человек, который получает сообщение» в параметрах события, а другие параметры транзакции также являются производными от параметров в вышеуказанных событиях.

Узел L2 преобразует параметр события Transaction Deposited OptimismPortalemit в транзакцию L2.

Например, это транзакция, в которой пользователь вносит 0,01ETH через контракт L1StandardBridge. Это сообщение и ETH передаются до контракта OptimismPortal (адрес 0xbEb5...06Ed), а затем конвертируются в L2. транзакция через несколько минут:

Инициатором сообщения является контракт L1CrossDomainMessenger; получателем является контракт L2CrossDomainMessenger на L2; содержание сообщения заключается в том, что L1StandardBridge получил депозит BoB в размере 0,01ETH; После этого будут запущены некоторые процессы, например, выдача дополнительных 0,01 ETH L2StandardBridge, которые затем будут переданы Бобу.

Как его конкретно вызвать

Когда вы хотите принудительно выполнить транзакцию в контракте Rollup Optimism, вы хотите добиться плавного выполнения «транзакции, инициированной и выполненной на L2 с вашего адреса L2». В это время вам следует использовать свой адрес L2. отправляет сообщение непосредственно в контракт OptimismPortal (обратите внимание, что контракт OptimismPortal на самом деле находится на уровне L1, но формат адреса OP такой же, как формат адреса L1. Вы можете напрямую вызвать вышеуказанный контракт, используя учетную запись L1 с тем же адресом, что и у учетная запись L2).

«Инициатором» транзакции L2, преобразованной событием «Депозит транзакции», выданным контрактом, будет ваша учетная запись L2. В настоящее время формат транзакции соответствует обычной транзакции L2.

В транзакции L2, преобразованной из события Transaction Deposited, инициатором будет сам Боб; получателем является контракт Uniswap, и он будет сопровождаться указанным ETH, точно так же, как сам Боб инициировал транзакцию L2;

Если вы хотите вызвать функцию принудительного включения Optimism, вам необходимо напрямую вызвать функцию депозита Transaction контракта OptimismPortal и заполнить параметры транзакции, которую вы хотите выполнить, в L2.

Я провел простой эксперимент с принудительным включением. Эта транзакция хотела добиться одной цели: использовать мой адрес для самостоятельной передачи на L2 (0xeDc1...6909) и прикрепить текстовое сообщение «принудительное включение».

Это транзакция L1, в которой я выполняю функцию депозита через контракт OptimismPortal. Вы можете видеть, что в событии «Депозит транзакции» оно выдает как от меня, так и к мне.

Значения в оставшемся непрозрачном столбце «Данные» кодируют «сколько ETH приходит человеку, который вызывает функцию транзакции депозита», «сколько ETH инициатор транзакции L2 хочет отправить получателю», «Транзакция L2 GasLimit» и « к данным получателя L2» и другую информацию.

Расшифровав приведенную выше информацию, вы получите:

«Сколько ETH было прикреплено человеком, вызвавшим депозитную транзакцию»: 0, потому что я не вносил ETH с L1 на L2;

«Сколько ETH инициатор транзакции L2 хочет отправить получателю»: 5566 (wei)

«GasLimit для транзакций L2»: 50000

«Данные для приемника L2»: 0x666f72636520696e636c7573696f6e, что представляет собой шестнадцатеричную кодировку строки «принудительное включение».

Вскоре после этого появилась конвертированная транзакция L2: транзакция L2, в которой я перевел деньги себе, сумма составила 5566 вэй, а данными была строка «принудительного включения». И вы можете заметить, что TxnType (тип транзакции) в других атрибутах в предпоследней строке на рисунке показывает системную транзакцию 126 (Система), что означает, что эта транзакция не была инициирована мной в L2, а была депонирована транзакцией L1. . События трансформируются.

Конвертированная транзакция L2

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

Окно секвенсора

Упомянутый ранее узел Optimism L2 преобразует событие Transaction Deposited в транзакцию L2. В конце концов, это связано с секвенированием транзакций, поэтому только Sequencer может решить, когда преобразовать вышеупомянутое событие в. транзакция L2.

При прослушивании события TransactionDeposited Sequencer не обязательно немедленно преобразует событие в транзакцию L2. Максимальное значение этого периода называется SequencerWindow.

Текущее окно секвенсора в сети Optimism составляет 24 часа, то есть, когда пользователь вносит сумму денег из L1 или принудительно включает транзакцию, в худшем случае она будет включена в историю транзакций L2 через 24 часа.

Механизм принудительного включения Arbitrum

В Optimism операция депозита L1 вызовет событие Transaction Deposited, а остальное — дождаться, пока Sequencer соберет вышеуказанные операции, но в Arbitrum — операции, которые происходят в L1 (экономия денег или отправка сообщений в L2 и т. д.); .) будет храниться в L1 в очереди, а не просто генерировать событие.

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

Arbitrum будет поддерживать очередь в контракте L1. Если Sequencer не обрабатывает активно транзакции в очереди, по истечении времени любой может принудительно включить транзакции в очереди в историю транзакций L2.

По замыслу Arbitrum, такие операции, как депозиты, которые происходят на L1, должны проходить через контракт «Отложенные входящие». Как следует из названия, операции здесь будут отложены, чтобы вступить в силу, другой контракт — это «Входящие секвенсора», который является непосредственным местом, где происходит входящее сообщение. Секвенсор загружает транзакции L2 в L1. Каждый раз, когда Sequencer загружает транзакцию L2, он может извлечь некоторые ожидающие транзакции из папки «Отложенные входящие» и записать их в историю транзакций.

Когда Sequencer записывает новую транзакцию, он может извлечь транзакцию из DelayedInbox и записать ее вместе.

Сложные конструкции и обширные справочные материалы.

Если читатели напрямую обратятся к официальной главе Arbitrum, посвященной Sequencer и Force Inclusion, они увидят, что там упоминается, как примерно работает Force Inclusion, а также имена некоторых параметров и функций:

Пользователь сначала обращается к контракту DelayedInbox, чтобы вызвать функцию sendUnsignedTransaction. Если Sequencer не включен в течение примерно 24 часов, пользователь может вызвать функцию ForceInclusion контракта SequencerInbox. Тогда чиновник Арбитрума не прикрепил ссылку на функцию к официальному документу сайта, поэтому вы можете только самостоятельно посмотреть соответствующую функцию в коде контракта.

Когда вы найдете функцию sendUnsignedTransaction, вы обнаружите, что вам нужно самостоятельно заполнить значение nonce и значение maxFeePerGas. Какой адрес является nonce? В какой сети находится maxFeePerGas? Как лучше всего его заполнить? Нет ссылки на файл, даже Natpsec. Тогда в контракте Arbitrum вы также найдете кучу похожих функций:

SendL1FundedUnsignedTransaction, sendUnsignedTransactionToFork, sendContractTransaction, sendL1FundedContractTransaction, нет документа, объясняющего разницу между этими функциями, как их использовать, как заполнять параметры, даже нет Natpsec.

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

ОтправитьL2Сообщение

Позже я случайно нажал на поиск Google и узнал, что у Arbitrum есть библиотека Tutorial, содержащая скрипты, демонстрирующие отправку транзакций L2 из L1 (что и означает Force Inclusion), а функции, перечисленные в ней, не являются ни одной из функций упомянутый выше, но функция под названием sendL2Message, а параметр сообщения на самом деле является транзакцией, подписанной с учетной записью L2?

Кто бы мог знать, что «сообщение, отправленное на L2 посредством принудительного включения», на самом деле будет «подписанной транзакцией L2»? И нет документации или Natspec, объясняющих, когда и как использовать эту функцию.

Вывод: вручную создать принудительную транзакцию Arbitrum затруднительно. Рекомендуется следовать официальному руководству и запустить Arbitrum SDK. В отличие от других накопительных пакетов, Arbitrum имеет четкую документацию для разработчиков и примечания к коду. Назначение и параметры многих функций не имеют объяснения, из-за чего разработчики тратят больше времени, чем ожидалось, на доступ к ним и их использование. Я также спросил людей из Arbitrum на Arbitrum Discord, но не получил удовлетворительного ответа.

Когда я спросил в Discord, другая сторона сказала мне только посмотреть sendL2Message. Они не хотели объяснять функции других функций (даже sendUnsignedTransaction, упомянутых в документе Force Inclusion), для чего они используются, как их использовать. и когда их использовать.

Механизм ForceInclusion StarkNet

К сожалению, в StarkNet в настоящее время нет механизма ForceInclusion. На официальном форуме есть только две статьи, посвященные цензуре и принудительному включению.

Невозможно доказать несостоявшуюся транзакцию

Вышеупомянутая причина на самом деле заключается в том, что система доказательства с нулевым разглашением StarkNet не может доказать неудачную транзакцию, поэтому принудительное включение не может быть разрешено. Потому что, если кто-то злонамеренно (или непреднамеренно) принудительно включит неудачную транзакцию, которую невозможно доказать, StarkNet напрямую застрянет: потому что после принудительного включения транзакции Prover должен доказать неудавшуюся транзакцию, но у него нет никакого способа доказать это.

StarkNet рассчитывает ввести функцию подтверждения неудачных транзакций в версии v0.15.0, после чего появится возможность дальнейшей реализации механизма Force Inclusion.

Механизм ForceInclusion zkSync

Передача сообщений L1->L2 в zkSync и механизм принудительного включения выполняются с помощью функции requestL2Transaction контракта MailBox. Пользователь указывает адрес L2, данные вызова, дополнительную сумму ETH, значение L2GasLimit и т. д. requestL2Transaction объединит эти параметры в файл L2. Затем транзакция помещается в приоритетную очередь (PriorityQueue). Когда транзакция упаковывается и загружается в L1 (с помощью функции commitBatches), Sequencer укажет, сколько транзакций следует извлечь из приоритетной очереди, и включить их в запись транзакции L2. .

zkSync очень похож на Optimism в форме принудительного включения. Он использует адрес L2 инициатора (согласованный с адресом L1) для вызова связанных функций и заполнения данных (вызываемого абонента, данных вызова и т. д.), а не как Arbitrum Fill. в подписанной транзакции L2; но конструкция такая же, как у Arbitrum, оба поддерживают очередь в L1, а Sequencer извлекает ожидающие транзакции, отправленные непосредственно пользователем, из очереди и записывает их в середину истории транзакций.

Если вы перейдете к депозиту ETH через официальный мост zkSync, такой как эта транзакция, он вызывает функцию requestL2Transaction контракта MailBox. Он поместит транзакцию L2 депозита ETH в приоритетную очередь и выдаст событие NewPriorityRequest. Поскольку контракт кодирует данные транзакции L2 в строку байтов, их трудно прочитать. Если вы посмотрите на параметры этой транзакции L1, вы увидите, что получатель L2 в параметрах также является инициатором транзакции ( потому что она отложена себе), поэтому через некоторое время, когда эта транзакция L2 будет вынесена Sequencer из приоритетной очереди и включена в историю транзакций, она будет конвертирована в транзакцию, переданную себе на L2, и сумма перевод представляет собой депозит инициатора транзакции в L1. Сумма ETH, перенесенная в транзакции ETH.

В транзакции L1Deposit инициатором и получателем транзакции являются 0xeDc1...6909, сумма — 0,03ETH, а данные вызова пусты.

Произойдет транзакция 0xeDc1...6909 по переводу денег себе на L2. Тип транзакции (TxnType) — 255, это системная транзакция.

Затем я напрямую вызвал функцию requestL2Transaction zkSync и отправил самостоятельную передачу, как я делал раньше, когда экспериментировал с функцией принудительной транзакции OP: без какого-либо ETH данные вызова содержали HEX-кодировку строки «принудительного включения».

Затем он преобразуется в последнюю транзакцию L2 для самого себя. Данные вызова представляют собой шестнадцатеричную строку «принудительного включения»: 0x666f72636520696e636c7573696f6e.

Когда Sequencer извлекает транзакцию из PriorityQueue и записывает ее в историю транзакций, она преобразуется в соответствующую транзакцию L2 на L2.

С помощью функции requestL2Transaction пользователи могут использовать учетную запись L1 с тем же адресом L2 для отправки информации в L1, указав получателя L2, соответствующую сумму ETH и данные вызова. Если пользователь хочет вызвать другие контракты с другими Данными, то же самое делается путем заполнения параметров в функцию requestL2Transaction один за другим.

Нет функции, позволяющей пользователям принудительно включать

Хотя после того, как транзакция L2 будет помещена в приоритетную очередь, период ожидания включения транзакции L2 в Sequencer будет рассчитываться случайно. Однако текущая конструкция zkSync не имеет функции принудительного включения, которую пользователи могут применять, а именно. эквивалентно только половине набора. То есть, хотя существует «период ожидания для включения», на самом деле он «проверяет, хочет ли Sequencer получать доход»: Sequencer может дождаться истечения срока действия, прежде чем получить транзакцию, или он никогда не сможет получать какие-либо транзакции. в приоритетной очереди.

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

Подведем итог

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

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

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

И zkSync, и Arbitrum поддерживают очередь в L1 для управления транзакциями L2, отправленными пользователями из L1, или сообщениями в L2. Arbitrum называется DelayedInbox, zkSync называется PriorityQueue;

Однако способ отправки транзакций L2 аналогичен способу Optimism. Он использует адрес L2 для отправки сообщений в L1. После преобразования в транзакцию L2 инициатором будет адрес L2. Функция Optimism для отправки транзакций L2 называется DepositTransaction. zkSync называется requestL2Transaction; Arbitrum генерирует и подписывает полную транзакцию L2, а затем отправляет ее через функцию sendL2Message. Arbitrum восстанавливает подписывающего лица через подпись на L2 как инициатора транзакции L2.

В StarkNet на данный момент нет механизма принудительного включения; zkSync похож на половину набора Force Inclusion — есть PriorityQueue и каждая транзакция L2 в очереди имеет период действия включения, но этот срок действия на данный момент предназначен только для украшения. Секвенсор может вообще не включать транзакции L2 в PriorityQueue.