Una teoría unificada de transacciones parcialmente firmadas

Este es simplemente un registro detallado de mi proceso de pensamiento actual sobre cómo ElectrumSV puede (sensatamente) rastrear y conservar el estado de transacciones sin firmar (o incompletas), principalmente para mis propios registros. Estas ideas se basan en discusiones con (pero de ninguna manera están respaldadas por) los desarrolladores de ElectrumSV (a saber, Roger Taylor).

La base de datos actual solo registra transacciones n de StateSigned (y todos los estados posteriores, es decir, StateDispatched, StateCleared, StateSettled en orden de finalidad) … La conclusión clave es que todos estos estados ( desde el inicio de sesión) ¡tiene un txid! Este txid se usa como clave externa en las otras tablas (por ejemplo, las tablas TransactionOutputs y TransactionDeltas).

Pero las transacciones parcialmente firmadas no tienen un txid . Pero aún necesitamos rastrear este estado. Pero, ¿cómo almacenamos estas transacciones tanto en caché como en RDMS? ¿Por qué es este un problema importante a resolver?

¿Por qué es este un problema importante que resolver?

1. Las solicitudes de pago BIP270 (y los casos de uso del protocolo de identidad / intercambios p2p de paymail) requieren mantener transacciones sin firmar o parcialmente firmadas (incompletas).

2. La creación de transacciones de múltiples subprocesos requiere asegurarse de que dos subprocesos no intenten acceder al mismo utxo o seleccionar la misma clave nueva para una salida determinada (si deseamos evitar la reutilización de claves). Pero si marcamos un utxo o una clave como “usado / asignado”, ¡necesitamos tener algunos datos de persistencia para lo que realmente lo usó! ( Es decir, ¿dónde está la transacción? – sin txid … es como si nunca hubiera sucedido …)

3. En la etapa de firma de la transacción, necesitamos saber qué keyinstance_ids están asociados con cada salida para poder establecer el script_type (marcándolo como ‘ya no es fresco’. Esto evita que un hilo diferente lo use. Pero recuerde cómo dije que las transacciones sin firmar ¿no tiene un txid? Sí… bueno, ¿cómo la función que realiza la firma busca los keyinstance_ids para las salidas de esta transacción sin firmar para la cual no hay registro? (especialmente en el caso de BIP270 donde el estado debe sobrevivir al reinicio entre la etapa tx sin firmar y la firma). Actualmente no puede … así que no lo hace. Podría pensar que podría generar laboriosamente claves de secuencia de comandos predeterminadas para todas las instancias de claves (almacenar en caché tal vez ) y jugar un juego de emparejamiento … pero ¿qué pasa con todos los otros tipos infinitos de scripts de salida? No es una solución hermética y el rendimiento sería terrible. Por lo tanto, deberíamos persistir en las transacciones sin firmar y resolver esto de una vez por todas.

4. Los pagos en los que se incrementa el número de secuencia y se establece un nLocktime para alguna fecha futura (cuando se vuelve definitivo y se extrae) también requieren una forma fácil de buscar las instancias clave para una salida determinada en un momento posterior ( ver el número 3 arriba).

ESV actualmente depende completamente de ElectrumX para rastrear el historial de claves y, a su vez, marcar una clave como “usada” (por lo tanto, solo puede rastrear el estado desde StateCleared en adelante a través de notificaciones pub-sub). Entonces, para llevarlo a casa, el flujo de los efectos de no rastrear el estado sin firmar en realidad conduce a una situación en la que tampoco podemos rastrear fácilmente el estado StateSigned y StateDispatched completamente. (sí, aunque se creen localmente)

¿Cómo almacenamos estas transacciones tanto en caché como en RDMS?

Esta es mi primera oportunidad en un modelo unificado para rastrear todo el estado firmado y no firmado en todo momento y, con suerte, de una manera que cubra todas nuestras bases para los requisitos futuros (por ejemplo, ¿canales de pago p2p?)

Nuevas tablas de base de datos

Tabla StagedTransactions

La terminología de “por etapas” contrasta con “comprometido” para tomar prestada la terminología de git (y se usa en relación con lo que está “comprometido” con la red bitcoin, que requiere un txid).

Tabla StagedOutputs

Tabla StagedInputs

No es necesario: una entrada de transacción debe por definición hacer referencia a un txid y un índice, por lo que hace referencia a una salida de transacción firmada. Por lo tanto, consulte la tabla TransactionOutputs existente según sea necesario para keyinstance_id.

Nota: Estas entradas / utxos de transacciones sin firmar se pueden marcar como gastadas inmediatamente en la tabla TransactionOutputs (porque ahora hay una respuesta a la pregunta: “¿dónde está la transacción?”)

Si se elimina una transacción sin firmar, los resultados se pueden marcar una vez más como “sin gastar”.

Nuevas estructuras de datos en caché

staged_txs = {uuid: (electrumsv.transaction.Transaction, flags)}

# indicadores incluyen:
– EPHEMERAL que indica que esta es simplemente una transacción sin firmar en tránsito y no necesita ser persistente en db (para una ganancia de rendimiento) – los utxos / fresh_keys también deben ser extraídos de sus respectivos cachés y no persistidos. Se perderán en caso de un reinicio repentino (porque no habría ningún registro de su uso), no nos importa.
IS_PAYMENT_REQUEST que indica que se trata de una solicitud de pago BIP270 y se creó
con la intención de conservarse en la base de datos.

staged_utxo_keys = {(uuid, index): keyinstance_id}

Proporciona una búsqueda fácil del keyinstance_id (itera sobre las salidas de tx firmadas y establece la instancia de clave script_type (marcándola implícitamente como “usada” al menos una vez. Si es un tipo efímero tx, simplemente actualiza el Account._keyinstances caché si no, haga una escritura tanto de la caché como de la base de datos.

Nota: Hay una distinción importante entre:
1) una clave que se ‘usa’ al menos una vez (es decir, script_type está configurado y ya no es miembro de ‘claves nuevas’ para otros subprocesos de creación de tx) en comparación a:
2) una clave que está ‘usada’ / ‘archivada’ porque alcanzó el saldo cero y posteriormente se canceló la suscripción de … también conocido como “Archivado” – esto está determinado por KeyInstanceFlag.IS_ACTIVE o USER_SET_ACTIVE e implica que recibió y gastó todos los utxos + que este evento ha sido confirmado en la cadena de bloques).

Cambios en SyncState

Actualmente wallet.SyncState solo rastrea key_history para transacciones StateCleared y StateSettled. Pero con la introducción del estado tx rastreado y sin firmar, podemos comenzar a rastrear el historial desde el punto de la firma tx local.

Esto solo tendrá implicaciones menores para partes del código base que han dependido (hasta ahora) de key_history que solo contienen txids StateCleared.

Si rastreamos otros estados de transacciones, se necesita un paso adicional para verificar el TxFlag (StateSigned / StateDispatched…) para ver los txids en el historial.

Por otro lado, si configuramos utxos en gastado , podemos hacerlo desde la etapa más temprana posible (al crear la transacción sin firmar), lo que hace que utxo use la firma previa segura para subprocesos.

Creación de transacciones de varios subprocesos

Esta es una tarea adicional, pero en teoría tenemos la capacidad de asignar “depósitos” de:

1. Claves nuevas de un grupo compartido con subprocesos bloqueados y clave privada para firmar.

2. Utxos

3. Resultados de la transacción

Podemos canalizar estos datos mínimos necesarios para separar los procesos de CPU en ejecución y hacer que creen la transacción sin firmar; firmarlo de forma independiente; luego devuelva la transacción firmada con los utxos y las claves nuevas que no fueron necesarias, para que se devuelva al grupo compartido de claves nuevas y utxos.

La idea sería tener una gran cantidad de utxos y claves nuevas en espera para que 10 procesos pudieran tener cada uno 10 claves nuevas y 10 utxos para cada transacción en cualquier momento.

Nota: reponer el grupo de claves nuevas cuando se agote sería un cuello de botella de rendimiento porque requiere escrituras en la base de datos cada vez … Por lo tanto, para tareas de alto rendimiento, se preferiría un grupo grande de claves nuevas para que la generación masiva de claves pudiera realizarse solo cuando no haya suficientes claves nuevas en el grupo para satisfacer la demanda (por ejemplo, menos de 10) (y una sola transacción de base de datos podría generar 500 nuevas claves nuevas cada vez).

Metadatos para vincular a Identity Pubkey

Roger Taylor (https://medium.com/@roger.taylor) señaló que se necesitan metadatos para vincular firmas de entrada de múltiples firmas con identidades (y la posterior visualización de su avatar en la interfaz de usuario como ejemplo).

Entonces, como un apéndice a lo anterior, propondría que el objeto XTxInput también incluya un atributo sig_link que sería una lista de tuplas que contienen:

· El sig_index , es decir, 0 significa la primera firma

· el identity_pubkey_id – que hace referencia a la tabla Contactos (ver a continuación):

para (hasta) tantas firmas como haya. Una firma sin una entrada sig_link correspondiente significa que en realidad no sabe quién la firmó … ¿y tal vez eso esté bien a veces? O tal vez no. No lo sé.

Tabla de contactos

Solo estoy lanzando ideas para obtener comentarios sobre una posible forma de avanzar en esta etapa. Si detecta algún problema o falla en este modelo, hágamelo saber.

Tal vez sea mejor dividir los componentes de una transacción parcialmente firmada en varias tablas de bases de datos relacionales normalizadas (en lugar de un blob binario para serializar / deserializar) … Pero en mi opinión, estas transacciones bitcoin podrían ser bestias muy dinámicas en el futuro cercano (especialmente cuando se superponen los otros metadatos requeridos por la billetera). Entonces, por ahora, propongo que la base de datos se use únicamente para transacciones parcialmente firmadas que necesitan persistencia y que usan las estructuras de datos en caché si no se requiere persistencia (por lo que no tenemos que actualizar 3 o 4 tablas diferentes cada vez que hacemos algo con una transacción!).