Artículo de Blog

Reduciendo la fricción en la red de Stellar con Saldos Reclamables

Autor

Jonathan Jove

Fecha de publicación

Actualización de protocolo

Saldos reclamables

Para transferir un activo — distinto de Lumens — entre dos cuentas Stellar, ambas cuentas deben tener una línea de confianza para ese activo. Esta restricción ha sido una fuente constante de fricción para los usuarios. En el Protocolo 15, estamos realizando nuestra visión de que el dinero se mueva como el correo electrónico al facilitar la transferencia de activos a una cuenta sin una línea de confianza.

¿Qué es una línea de confianza?

Supongamos que vives cerca de la frontera entre Canadá y los Estados Unidos. Tu billetera contiene una mezcla de dólares estadounidenses (USD) y dólares canadienses (CAD). Esto te permite comprar fácilmente en ambos países. Para llevar un registro de tus finanzas, mantienes un libro de cuentas con dos columnas. Cada noche, registras la cantidad de USD que posees en la primera columna y la cantidad de CAD que posees en la segunda columna.

Una línea de confianza es la representación de una columna de tu libro de cuentas hipotético en la blockchain de Stellar. Específicamente, una línea de confianza registra el saldo de un solo activo poseído por una sola cuenta. Si una cuenta no tiene una línea de confianza para un cierto activo, entonces esa cuenta no puede poseer ese activo porque no habría un lugar para registrar el saldo. La única excepción a esta regla son los Lumens. Dado que cada cuenta siempre puede poseer Lumens, el saldo de Lumens para una cuenta se almacena en la cuenta misma.

Una línea de confianza en realidad almacena más información que solo el saldo. También almacena

  • El límite, que es establecido por el poseedor del activo y determina el saldo máximo que la línea de confianza puede tener
  • El estado de autorización, que es establecido por el emisor y determina lo que el poseedor del activo está permitido hacer con el activo

A primera vista, parece extraño establecer un límite: ¿no estaría siempre más feliz con un saldo más alto? El mundo no es tan simple. El nombre línea de confianza es un reflejo del hecho de que los activos en Stellar son en realidad créditos; confías en el emisor para que los canjee por los activos subyacentes. Si no crees que el emisor sea muy confiable, entonces probablemente querrás limitar cuánto de sus activos posees. No tener una línea de confianza en absoluto es la declaración definitiva de falta de confianza, porque estás diciendo que te niegas a poseer cualquier activo de ese tipo.

Desafíos con las líneas de confianza

Las líneas de confianza a menudo hacen difícil hacer cosas que deberían ser fáciles. Supongamos que sales a tomar algo con un amigo. El bar está lleno, así que tu amigo ofrece poner tus bebidas en su cuenta. Tú aceptas y ofreces saldar más tarde en Stellar ya que ambos tienen cuentas. Pero cuando vas a saldar tu cuenta más tarde, las cosas no son tan simples. Intentas enviar USD desde tu cuenta a la cuenta de tu amigo. El pago falla porque los USD que posees fueron emitidos por una cuenta diferente a los USD que tu amigo posee.

Siendo un ávido usuario de Stellar, no te desanimas. Verificas qué activo USD posee tu amigo, luego envías un pago de ruta que convierte tus USD a los USD de tu amigo. Para tu sorpresa, esto también falla. Resulta que tu amigo tiene un límite bajo en su línea de confianza de USD porque no cree que el emisor sea muy solvente. Tu pago lo enviaría sobre el límite.

Ya no sabes cómo satisfacer tu acuerdo con tu amigo. Estás frustrado, porque siempre cumples tu palabra. Llamas a tu amigo, quien te dice que simplemente pueden saldar en efectivo la próxima vez que se vean. Te queda muy claro que el dinero no se está moviendo como el correo electrónico.

Hecho o fricción

En tu frustración, te preguntas por qué la línea de confianza no se crea automáticamente cuando envías un pago. Esta propuesta fue en realidad muy debatida dentro de la comunidad de Stellar. Aunque parece una solución sencilla, tiene una serie de complicaciones.

La primera complicación es que esta propuesta sola no es suficiente para resolver el problema en el caso general. Como ejemplo concreto, tu segundo intento falló porque el límite de la línea de confianza era demasiado bajo, no porque la línea de confianza no existiera. Este problema podría evitarse simplemente ignorando el límite de la línea de confianza por completo. Crear automáticamente líneas de confianza implica esto, porque una línea de confianza que no existe es conceptualmente equivalente a un límite de 0.

La segunda complicación es que esta propuesta no funciona para activos que requieren autorización. Una línea de confianza que se crea automáticamente no puede ser autorizada automáticamente, porque la autorización está a discreción del emisor. Pero el saldo de una línea de confianza que no está autorizada nunca puede aumentar. Así que aunque la línea de confianza podría crearse automáticamente, todavía no permitiría que el pago se realizara.

La tercera complicación es que esta propuesta ignora el hecho de que crear una línea de confianza también crea un requisito de reserva. Para mitigar los ataques de denegación de servicio en la red de Stellar, los usuarios deben pagar por cada pieza de información que se almacena en el libro mayor. Si un pago crea automáticamente una línea de confianza, ¿quién debería pagar por la reserva? ¿El remitente o el destinatario? Si el destinatario paga el requisito de reserva, entonces sería posible atacar cuentas creando muchas líneas de confianza "spam" que bloqueen saldo. Esto es claramente inaceptable. Pero si el remitente paga el requisito de reserva, entonces hemos creado una nueva fuente de fricción porque los requisitos de reserva son mucho más altos que las tarifas de transacción.

Hay más desafíos con esta propuesta aparentemente sencilla. Pero ya en este punto, parece que este problema no es tan simple después de todo. El debate en torno a esta propuesta despertó las pasiones de la comunidad porque era fácil ver la importancia de resolver el problema, pero difícil encontrar una solución que fuera suficientemente robusta. Esto creó una tormenta de nuevas ideas.

Pagos de dos partes

Esta discusión me inspiró. Las restricciones producen creatividad. ¿Cómo podría crear una experiencia de usuario favorable que funcione en muchas circunstancias sin introducir nuevos problemas? El primer paso fue formular el problema en un formato que fuera más fácil de evaluar. Me decidí por la siguiente pregunta: ¿puedes crear un mecanismo en Stellar donde los pagos puedan enviarse con éxito sin considerar la cuenta receptora, y donde los pagos puedan recibirse con éxito sin considerar la cuenta emisora? Dado que el remitente de un pago no puede hacer ninguna garantía sobre la cuenta receptora, tal pago debe crear algún tipo de estado intermedio. Enviar el pago se convierte en “crear algún estado intermedio” y recibir el pago se convierte en “actuar sobre el estado intermedio consumiéndolo”. Esta observación fue la base de lo que posteriormente me referiría como pagos de dos partes.

Pero una observación no es una propuesta — no importa cuánto quisiéramos que lo fuera. Ahora comenzaría el trabajo duro. ¿Cuáles eran los requisitos para el estado intermedio? Primero, tenemos que manejar las complicaciones mencionadas anteriormente:

  1. Necesitaría mantener saldos sin límites, para evitar la primera complicación mencionada
  2. Necesitaría mantener activos que requieren autorización, para evitar la segunda complicación mencionada
  3. Necesitaría un requisito de reserva pagado por el remitente, para evitar la tercera complicación mencionada

El requisito (2) conduce a un nuevo e interesante problema. Si el estado intermedio puede mantener activos que requieren autorización, entonces debe haber una verificación de autorización antes de que el usuario pueda incorporar el saldo en su línea de confianza. Como se mencionó anteriormente, la autorización está a discreción del emisor. Si un emisor se niega a autorizar al receptor, entonces los fondos están atascados. El remitente probablemente querría recuperar los fondos en ese caso. Mi solución inicial fue agregar un tiempo de caducidad, donde el receptor podría aceptar el pago antes del tiempo de caducidad y el remitente podría recuperar el pago después del tiempo de caducidad.

Como se mencionó anteriormente en mi discusión sobre la tercera complicación, tener que el remitente pague la reserva es una nueva fuente de fricción. Para mitigar esa fricción, la reserva tendría que ser reembolsada al remitente cuando se completa el pago. Si el remitente quiere poder recuperar la reserva rápidamente, entonces podrían simplemente establecer un tiempo de caducidad que estuviera en un futuro cercano.

Nuevas primitivas de pago

Una vez que tuve una visión de cómo resolver el problema, dirigí mi atención a mejorar la propuesta. Gran parte de la propuesta estaba rígidamente fijada por los requisitos, pero el tiempo de caducidad se destacó para mí como una oportunidad de mejora. Me encontré haciendo las siguientes preguntas:

  • ¿Por qué debería ser un tiempo?
  • ¿Por qué el destinatario siempre debe tener la primera oportunidad?
  • ¿Por qué solo debe haber un posible destinatario?

No tenía buenas respuestas, así que me propuse reinventar el tiempo de expiración. Me centré en la pregunta motivadora: ¿pueden los pagos de dos partes extenderse para habilitar primitivas de pago completamente nuevas en Stellar? Algunas primitivas de pago simples que son interesantes incluyen

  • Pagos simples: el destinatario es la única parte que puede adquirir los fondos
  • Pagos recuperables: el remitente tiene la oportunidad de recuperar un pago si el destinatario no lo acepta
  • Pagos cancelables: el remitente tiene la oportunidad de cancelar unilateralmente antes de que el destinatario pueda aceptar
  • Pago a múltiples destinatarios: hay varias cuentas que pueden ser el destinatario (por ejemplo, enviar dinero a una pareja)

Algunas combinaciones de estas también pueden ser interesantes, por ejemplo, un pago recuperable a múltiples destinatarios. Un buen diseño no debe restringir las posibles primitivas a unos pocos tipos, sino que debe hacerlas personalizables. Los pagos simples y los pagos recuperables ya son posibles con los pagos de dos partes descritos anteriormente, pero el diseño es demasiado rígido para permitir pagos cancelables o pagos a múltiples destinatarios. La solución requirió dos cambios.

Primero tuve que abstraer la idea de un remitente y un destinatario. El estado intermedio debe contener en cambio una lista de posibles destinatarios. Si el remitente es un destinatario, entonces es solo una instancia específica de este marco más general. No se requiere lógica separada para manejar el caso en que el remitente es un destinatario, por lo que ni siquiera es un caso especial.

Segundo, tuve que abstraer la idea de un tiempo de expiración. En cambio, el tiempo de expiración se consideró como un ejemplo específico de un predicado. Un predicado es cualquier declaración sobre la cual puedes preguntar, "¿Es esto verdadero o falso?" Por ejemplo, las declaraciones “Esto es una moneda”, “Esto es un cuarto” y “Esto es un diez centavos” son todos predicados. Un predicado más complejo es “Esto es una moneda, y esto no es un cuarto ni un diez centavos”. Este predicado es verdadero para un níquel, y falso para un dólar. La observación interesante es que el predicado complejo se formó a partir de los tres predicados más simples usando los operadores booleanos “y”, “o”, y “no”.

Ahora solo necesitaba una forma de representar los posibles predicados. Para lograr flexibilidad, quería crear algunos predicados simples que pudieran combinarse usando operadores lógicos. Para el lanzamiento inicial de los pagos de dos partes, hubo un consenso para incluir solo predicados basados en el tiempo. Un ejemplo de predicado basado en el tiempo es “El tiempo de cierre del ledger actual es antes del 1 de noviembre de 2020”. Sin embargo, la belleza del diseño es que sería fácil agregar nuevos predicados. Sería solo una pequeña modificación agregar predicados basados en secuencia de ledger en cambio. Como en el ejemplo anterior, los predicados se pueden combinar con y, o, y no.

El diseño final de los pagos de dos partes empareja a cada destinatario con un predicado. En el contexto de los pagos de dos partes, los destinatarios se llaman “reclamantes” porque tienen el potencial de reclamar el estado intermedio. De manera similar, los predicados se llaman “predicados de reclamo” porque determinan si ese reclamante puede reclamar realmente el estado intermedio.

Resolviendo tu problema

Volvamos al ejemplo de saldar una cuenta en Stellar. No pudiste reembolsar a tu amigo usando las primitivas de pago existentes que proporciona Stellar. Pero podrás hacerlo sin esfuerzo con los pagos de dos partes.

Todo lo que tienes que hacer es enviar un pago de dos partes a tu amigo. Como se introdujo en CAP-0023, un pago de dos partes se envía con la operación CreateClaimableBalance. Tu amigo puede tomar las acciones necesarias para preparar su cuenta, luego usar la operación ClaimClaimableBalance para recibir los fondos. Probablemente quieras incluirte como reclamante en caso de que tu amigo no pueda aceptar el pago, se niegue a aceptar el pago, o si terminas saldando en efectivo. Recuerda que un pago de dos partes no puede ser eliminado. Uno de los reclamantes tiene que reclamar los fondos, o el dinero no se puede recuperar.

Si quieres saber más sobre esta emocionante nueva característica, consulta la documentación y la referencia de la API.

Más movimiento de dinero, menos problemas

Nos propusimos reducir la fricción relacionada con las líneas de confianza. Al resolver ese problema, también creamos un nuevo marco para primitivas de pago poderosas. Los desafíos son solo nuevas oportunidades.

[¿Interesado en saber más sobre el Protocolo 15 y más allá? No te pierdas la ponencia técnica de Jon Jove en Meridian 2020.]