En las últimas dos semanas, nuestro desarrollador principal de C++, Gavin Wood, y yo hemos pasado mucho tiempo reuniéndonos con la comunidad local de Ethereum en San Francisco y Silicon Valley. Estábamos muy emocionados de ver tanto interés en nuestro proyecto y el hecho de que después de solo dos meses tenemos un grupo de reunión que se reúne todas las semanas, al igual que la reunión de Bitcoin, con más de treinta personas que asisten cada vez. Las personas de la comunidad se están encargando de hacer videos educativos, organizar eventos y experimentar con contratos, e incluso una persona está comenzando a escribir de forma independiente una implementación de Ethereum en node.js. Al mismo tiempo, sin embargo, tuvimos la oportunidad de echar otro vistazo a los protocolos de Ethereum, ver dónde las cosas aún son imperfectas y acordar una gran variedad de cambios que se integrarán, probablemente con una modificación mínima, en el PoC 3.5. clientela.
Transacciones como Cierres
En ES1 y ES2, el código de operación MKTX, que permitía que los contratos enviaran transacciones que activaban otros contratos, tenía una característica muy poco intuitiva: aunque uno esperaría naturalmente que MKTX fuera como una llamada de función, procesando toda la transacción inmediatamente y luego continuando con el resto del código, en realidad MKTX no funcionaba de esta manera. En cambio, la ejecución de la llamada se pospone hacia el final: cuando se llama a MKTX, una nueva transacción se empuja al frente de la pila de transacciones del bloque, y cuando la ejecución de la primera transacción finaliza, la ejecución de la segunda transacción comienza Por ejemplo, esto es algo que podría esperar que funcione:
x = array() x(0) = “george” x(1) = MICLAVEPUBLICIDAD
mktx(MONEDA NOMBRE,10^20,x,2)
if contract.storage(NAMECOIN)(“george”) == MYPUBKEY: registro_exitoso = 1 else: registro_exitoso = 0
// hacer mas cosas…
Use el contrato de namecoin para intentar registrar a “george”, luego use el código de operación EXTRO para ver si el registro es exitoso. Esto parece que debería funcionar. Sin embargo, por supuesto, no lo hace.
En EVM3 (ya no ES3), solucionamos este problema. Hacemos esto tomando una idea de ES2, creando un concepto de código reutilizable, funciones y bibliotecas de software, y una idea de ES1, manteniéndolo simple manteniendo el código como un conjunto secuencial de instrucciones en el estado y fusionando los dos en un concepto de “llamadas de mensaje”. Una llamada de mensaje es una operación ejecutada desde dentro de un contrato que toma una dirección de destino, un valor de ether y algunos datos como entrada y llama al contrato con ese valor de ether y datos, pero que también, a diferencia de una transacción, devuelve datos como salida. . Por lo tanto, también hay un nuevo código de operación RETURN que permite la ejecución del contrato para devolver datos.
Con este sistema, los contratos ahora pueden ser mucho más poderosos. Todavía pueden existir contratos del tipo tradicional, que realizan ciertos datos al recibir llamadas de mensajes. Pero ahora, sin embargo, también son posibles otros dos patrones de diseño. Primero, ahora se puede crear un contrato de alimentación de datos propietario; por ejemplo, Bloomberg puede publicar un contrato en el que incluye varios precios de activos y otros datos de mercado, e incluir en su contrato una API que devuelve los datos internos siempre que la llamada de mensaje entrante envíe al menos 1 finney junto con él. La tarifa no puede ser demasiado alta; de lo contrario, los contratos que obtengan datos del contrato de Bloomberg una vez por bloque y luego proporcionen un traspaso más económico serán rentables. Sin embargo, incluso con tarifas equivalentes al valor de quizás una cuarta parte de una tarifa de transacción, tal negocio de alimentación de datos puede terminar siendo muy viable. El código de operación EXTRO se elimina para facilitar esta funcionalidad, es decir. los contratos ahora son opacos desde el interior del sistema, aunque desde el exterior, obviamente, uno puede simplemente mirar el árbol de Merkle.
Segundo, es posible crear contratos que representen funciones; por ejemplo, uno puede tener un contrato SHA256 o un contrato ECMUL para calcular esas funciones respectivas. Hay un problema con esto: veinte bytes para almacenar la dirección para llamar a una función en particular puede ser demasiado. Sin embargo, esto se puede resolver creando un contrato “stdlib” que contenga algunos cientos de cláusulas para funciones comunes, y los contratos pueden almacenar la dirección de este contrato una vez como una variable y luego acceder a ella muchas veces simplemente como “x” (técnicamente, “PULSE 0 MCARGAR”). Esta es la forma en que EVM3 integra la otra idea principal de ES2, el concepto de bibliotecas estándar.
Éter y Gas
Otro cambio importante es este: los contratos ya no pagan por la ejecución del contrato, las transacciones sí lo hacen. Cuando envía una transacción, ahora debe incluir una TARIFA BÁSICA y una cantidad máxima de pasos que está dispuesto a pagar. Al comienzo de la ejecución de la transacción, el BASEFEE multiplicado por los pasos máximos se resta inmediatamente de su saldo. Luego se instancia un nuevo contador, llamado GAS, que comienza con el número de pasos que le quedan. Luego, la ejecución de la transacción comienza como antes. Cada paso cuesta 1 GAS, y la ejecución continúa hasta que se detiene de forma natural, momento en el que todo el gas restante multiplicado por la TARIFA BÁSICA proporcionada se devuelve al remitente, o la ejecución se queda sin GAS; en ese caso, se revierte toda la ejecución pero se sigue pagando la tarifa completa.
Este enfoque tiene dos beneficios importantes. Primero, permite a los mineros saber con anticipación la cantidad máxima de GAS que consumirá una transacción. En segundo lugar, y mucho más importante, permite a los redactores de contratos dedicar mucho menos tiempo a hacer que el contrato sea “defendible” frente a transacciones ficticias que intentan sabotear el contrato obligándolo a pagar tarifas. Por ejemplo, considere el viejo Namecoin de 5 líneas:
if tx.value < block.basefee * 200: detener si !contrato.almacenamiento(tx.datos(0)) o tx.datos(0) = 100: contrato.almacenamiento(tx.datos(0)) = tx.datos (1)
Dos líneas, sin cheques. Mucho más simple. Concéntrese en la lógica, no en los detalles del protocolo. La principal debilidad del enfoque es que significa que, si envía una transacción a un contrato, debe precalcular cuánto tiempo llevará la ejecución (o al menos establecer un límite superior razonable que está dispuesto a pagar), y el El contrato tiene el poder de entrar en un ciclo infinito, agotar todo el gas y obligarlo a pagar su tarifa sin ningún efecto. Sin embargo, podría decirse que esto no es un problema; cuando envías una transacción a alguien, ya estás confiando implícitamente en que no arrojará el dinero a la zanja (o al menos no se quejará si lo hace), y depende del contrato ser razonable. Los contratos pueden incluso optar por incluir una bandera que indique la cantidad de gas que esperan necesitar (por la presente nomino anteponer “PUSH 4 JMP” al código de ejecución como estándar voluntario)
Hay una extensión importante a esta idea, que se aplica al concepto de llamadas de mensaje: cuando un contrato hace una llamada de mensaje, el contrato también especifica la cantidad de gas que debe usar el contrato en el otro extremo de la llamada. Al igual que en el nivel superior, el contrato receptor puede finalizar la ejecución a tiempo o puede quedarse sin gas, momento en el que la ejecución vuelve al inicio de la llamada pero el gas aún se consume. Alternativamente, los contratos pueden poner un cero en los campos de gas; en ese caso, están confiando al subcontrato todo el gas restante. La razón principal por la que esto es necesario es permitir que los contratos automáticos y los contratos controlados por humanos interactúen entre sí; si solo estuviera disponible la opción de llamar a un contrato con todo el gas restante, entonces los contratos automáticos no podrían usar ningún contrato controlado por humanos sin confiar absolutamente en sus propietarios. Esto haría que las aplicaciones de suministro de datos m-of-n fueran esencialmente inviables. Por otro lado, esto introduce la debilidad de que el motor de ejecución deberá incluir la capacidad de volver a ciertos puntos anteriores (específicamente, el inicio de una llamada de mensaje).
La nueva guía de terminología
Con todos los conceptos nuevos que hemos introducido, hemos estandarizado algunos términos nuevos que usaremos; con suerte, esto ayudará a aclarar la discusión sobre los diversos temas.
- actor externo: Una persona u otra entidad capaz de interactuar con un nodo de Ethereum, pero externa al mundo de Ethereum. Puede interactuar con Ethereum depositando Transacciones firmadas e inspeccionando la cadena de bloques y el estado asociado. Tiene una (o más) Cuentas intrínsecas.
- Dirección: Un código de 160 bits utilizado para identificar Cuentas.
- Cuenta: Las cuentas tienen un saldo intrínseco y un recuento de transacciones mantenido como parte del estado de Ethereum. Son propiedad de Actores Externos o intrínsecamente (como una identidad) un Objeto Autónomo dentro de Ethereum. Si una cuenta identifica un objeto autónomo, Ethereum también mantendrá un estado de almacenamiento particular para esa cuenta. Cada Cuenta tiene una única Dirección que la identifica.
- Transacción: Un dato, firmado por un Actor Externo. Representa un Mensaje o un nuevo Objeto Autónomo. Las transacciones se registran en cada bloque de la cadena de bloques.
- Objeto Autónomo: Un objeto virtual que existe solo dentro del estado hipotético de Ethereum. Tiene una dirección intrínseca. Incorporado solo como el estado del componente de almacenamiento de la VM.
- Estado de almacenamiento: La información particular de un Objeto Autónomo dado que se mantiene entre los tiempos que se ejecuta.
- Mensaje: Datos (como un conjunto de bytes) y Valor (especificado como Ether) que se pasan entre dos Cuentas de forma perfectamente confiable, ya sea mediante la operación determinista de un Objeto Autónomo o la firma criptográficamente segura de la Transacción.
- Llamada de mensaje: El acto de pasar un mensaje de una Cuenta a otra. Si la cuenta de destino es un Objeto Autónomo, la VM se iniciará con el estado de dicho Objeto y se actuará sobre el Mensaje. Si el remitente del mensaje es un objeto autónomo, la llamada pasa cualquier dato devuelto por la operación de VM.
- Gas: La unidad fundamental de costo de la red. Pagado exclusivamente por Ether (a partir de PoC-3.5), que se convierte libremente hacia y desde Gas según sea necesario. El gas no existe fuera del motor de cálculo interno de Ethereum; su precio lo establece la Transacción y los mineros son libres de ignorar las Transacciones cuyo precio de Gas es demasiado bajo.
Vista a largo plazo
Pronto, publicaremos una especificación formal completa de los cambios anteriores, incluida una nueva versión del documento técnico que tiene en cuenta todas estas modificaciones, así como una nueva versión del cliente que lo implementa. Más adelante, es probable que se realicen más cambios en el EVM, pero el ETH-HLL se modificará lo menos posible; por lo tanto, es perfectamente seguro escribir contratos en ETH-HLL ahora y seguirán funcionando incluso si cambia el idioma.
Todavía no tenemos una idea final de cómo abordaremos las tarifas obligatorias; el enfoque provisional actual ahora es tener un límite de bloque de 1000000 operaciones (es decir, GAS gastado) por bloque. Económicamente, una tarifa obligatoria y un límite de bloque obligatorio son esencialmente equivalentes; sin embargo, el límite de bloques es un poco más genérico y, en teoría, permite que un número limitado de transacciones entre de forma gratuita. Próximamente habrá una publicación de blog que cubrirá nuestros últimos pensamientos sobre el tema de las tarifas. La otra idea que tenía, stack traces, también puede implementarse más adelante.
A largo plazo, tal vez incluso más allá de Ethereum 1.0, tal vez el santo grial sea atacar las dos últimas partes “intrínsecas” del sistema y ver si podemos convertirlas también en contratos: ether y ECDSA. En tal sistema, el éter seguiría siendo la moneda privilegiada del sistema; el pensamiento actual es que vamos a preminar el contrato de éter en el índice “1”, por lo que se necesitan diecinueve bytes menos para usarlo. Sin embargo, el motor de ejecución se volvería más simple ya que ya no existiría ningún concepto de moneda; en su lugar, todo se trataría de contratos y llamadas de mensajes. Otro beneficio interesante es que esto permitiría desacoplar ether y ECDSA, haciendo que ether sea opcionalmente a prueba de cuánticos; si lo desea, puede crear una cuenta ether utilizando un contrato NTRU o Lamport en su lugar. Sin embargo, un perjuicio es que la prueba de participación no sería posible sin una moneda que sea intrínseca al nivel del protocolo; esa puede ser una buena razón para no ir en esta dirección.