25 nov 2025

Implentación de voto sobre cadena de bloques de criptomoneda Feathercoin (FTC)

Este desarrollo esta basado en el papel publicado en este blog denominado "Estrategia de voto en cadena de Bloques"  Ruego consulte este documento para mas información sobre esta idea.


0.- Lo primero es crear una cartera para la votación. En feathercoin-qt, la interfaz gráfica core de esta criptomoneda encontraras muy sencillo hacerlo. El nombre de esta cartera es -en nuestro ejemplo-  votingWallet, y necesitaras disponer de fondos necesarios para enviar las transacciones.  Encripta la cartera si quieres prevenir que se vacie si alguien o algo accediera a tu archivo local .dat en tu ordenador.

El coste de la votación será el de las comisiones para las transacciones ya que los fondos se recuperan en la cartera una vez finalizada la votación, dejando como actas de resultados las inscripciones en la cadena de bloques. Su correlación con las preguntas y respuestas es el resultado arrojado. Solo realizaremos una pregunta por ejecución del programa, con múltiples respuestas posibles y múltiples votantes únicos. La regla es de un voto por persona.

1.- Primero vamos a la carpeta donde esta el programa, en este caso:
 

cd /home/usuario/Descargas/feathercoin-0.19.1-linux64/VP_old02


Necesitaras dos terminales si quieres ver la información del nodo corriendo. El contenido es, junto al programa para levantar el nodo de feathercoin, los siguentes archivos:

feathercoin-cli  generate_graphs.py  vote_protocol.sh
feathercoind     unlock_wallet.py


Copia el código para cada archivo y guárdalos con estos nombres:

 

 

vote_protocol.sh 

_____________________________________________________________________

#!/bin/bash

# ==========================================================
# VOTACIÓN SOBRE BLOCKCHAIN - PROTOCOLO V0.10 (FIX COMPATIBILIDAD JQ)
# ==========================================================
# Este programa permite una aproximación segura al voto y recuento electrónico
# mediante inscripción de transacciones en la cadena de bloques de Feathercoin (FTC)
# pensado para el establecimiento de mesa electoral local, voto mediante transaccion
# y recuento en la cadena de bloques.

# --- CONFIGURACIÓN DE ENTORNO ---
WALLET_NAME="votingWallet"
CLI="./feathercoin-cli -rpcwallet=$WALLET_NAME"
POLL_PREFIX="FTC_VOTE_$(date +%Y%m%d)"
DATA_FILE="${POLL_PREFIX}_data.json"
OPTIONS_FILE="${POLL_PREFIX}_options.json"
VOTER_ID_FILE="voter_ids.txt"
RESULTS_FILE="RESULTS.md"
GRAPH_SCRIPT="generate_graphs.py"
REQUIRED_CONFIRMATIONS=3
REQUIRED_AMOUNT=1.00 # Cantidad exacta de FTC que cuenta como 1 voto válido.

# --- FUNCIONES NÚCLEO ---

cleanup() {
    echo "⚙️ Limpiando archivos de encuestas anteriores..."
    rm -f $DATA_FILE $OPTIONS_FILE $VOTER_ID_FILE $RESULTS_FILE $POLL_PREFIX*results.json *.png 2>/dev/null
    echo "✅ Archivos de encuestas anteriores eliminados."
}

interactive_setup() {
    echo "=========================================================="
    echo "      1. PREPARACIÓN INTERACTIVA DE LA CONSULTA"
    echo "=========================================================="

    NUM_QUESTIONS=1

    read -p "¿Cuál es la PREGUNTA (tema principal de la votación)? " QUESTION

    echo "--- INSTRUCCIÓN: Use comillas dobles para opciones con espacios ---"
    read -p "Defina las opciones de voto (Ej: \"APROBAR\", \"RECHAZAR\", \"ABSTENCIÓN\"). Escriba las opciones separadas por comas (mínimo 2): " OPTIONS_INPUT

    # 1. Parsear, limpiar y crear un array JSON de nombres de opciones
    OPTIONS_JSON_ARRAY=$(echo "[$OPTIONS_INPUT]" | jq -R '
        split(",") | .[] |
        # 1. Eliminar espacios iniciales/finales
        ltrimstr(" ") | rtrimstr(" ") |
        # 2. Eliminar corchete inicial/final (para el caso de que la entrada esté envuelta en ellos)
        ltrimstr("[") |
        rtrimstr("]") |
        # 3. FIX V0.10: Utilizar sub con bandera global ("g") para compatibilidad máxima con jq.
        sub("\""; ""; "g") |
        # 4. Eliminar espacios de nuevo
        ltrimstr(" ") | rtrimstr(" ") |
        select(length > 0)
    ' | jq -s '.')

    if [ "$(echo "$OPTIONS_JSON_ARRAY" | jq length)" -lt 2 ]; then
        echo "❌ ERROR: Debe definir al menos 2 opciones válidas."
        exit 1
    fi

    # Usar JQ para generar la estructura final de 'options' con sus claves OPT
    JSON_OPTIONS=$(echo "$OPTIONS_JSON_ARRAY" | jq '
        to_entries |
        map({
            "name": .value,
            "key": ("OPT" + (.key + 1 | tostring))
        })
    ')

    export QUESTION="$QUESTION"

    # 2. Crear el archivo de opciones
    echo '{
        "question":"'$QUESTION'",
        "num_questions":'$NUM_QUESTIONS',
        "options":'$JSON_OPTIONS'
    }' | jq . > $OPTIONS_FILE

    echo "✅ Configuración de la pregunta guardada en $OPTIONS_FILE."
    echo "=========================================================="
}

setup_poll_addresses() {
    echo "=========================================================="
    echo "      2. CREANDO DIRECCIONES DE VOTO (SETUP POLL)"
    echo "=========================================================="

    ADDRESS_LIST=$(jq -r '.options[] | "\(.key)|\(.name)"' $OPTIONS_FILE)

    TEMP_ADDRESSES_FILE=$(mktemp)

    echo "Direcciones generadas para esta encuesta:"

    echo "$ADDRESS_LIST" | while IFS='|' read -r KEY OPTION_NAME; do

        ADDRESS=$($CLI getnewaddress)

        echo -n '"'${KEY}'":"'${ADDRESS}'",' >> $TEMP_ADDRESSES_FILE
        echo "  > ${OPTION_NAME} (${KEY}): ${ADDRESS}"
    done

    TEMP_ADDRESSES=$(cat $TEMP_ADDRESSES_FILE)
    TEMP_ADDRESSES=${TEMP_ADDRESSES%,}
    rm $TEMP_ADDRESSES_FILE

    echo '{
        "creation_time": "$(date -u +%Y-%m-%dT%H:%M:%S)Z",
        "required_amount": '$REQUIRED_AMOUNT',
        "option_addresses": {'$TEMP_ADDRESSES'}
    }' | jq . > $DATA_FILE

    echo "SUCCESS: Poll configuration saved to $DATA_FILE."
    echo "ACTION: Distribuir $DATA_FILE de forma segura (la 'papeleta')."
    echo "=========================================================="
}

fund_voters() {
    echo "=========================================================="
    echo "      3. TRANSFIRIENDO IDS DE VOTANTE"
    echo "=========================================================="

    if [ ! -f $VOTER_ID_FILE ]; then
        echo "❌ ERROR: No se encontró el archivo de IDs de votante ($VOTER_ID_FILE). Abortando."
        exit 1
    fi

    VOTER_IDS_FILE_CONTENT=$(cat $VOTER_ID_FILE)

    # Asegurar el formato de punto decimal para JSON
    JSON_PARAM=$(LC_NUMERIC=C echo "$VOTER_IDS_FILE_CONTENT" | awk -v amount="$REQUIRED_AMOUNT" '
        BEGIN {
            json_string = "{"
            total_amount = amount + 0.01
        }
        {
            amount_str = sprintf("%.2f", total_amount)
            gsub(/,/, ".", amount_str)

            json_string = json_string "\"" $1 "\":" amount_str ","
        }
        END {
            sub(/,$/, "", json_string);
            json_string = json_string "}";
            print json_string
        }'
    )

    echo "Parámetros de sendmany generados: $JSON_PARAM"

    TXID_FUND=$($CLI sendmany "" "$JSON_PARAM" 2>&1)

    if echo "$TXID_FUND" | grep -q "error"; then
        echo "❌ ERROR al transferir IDs de votante. Detalles:"
        echo "$TXID_FUND"
        exit 1
    fi

    echo "✅ Transacción de fondos enviada. TXID: $TXID_FUND"
    echo "Esperando 80 segundos para la propagación de la transacción..."
    sleep 80
    echo "=========================================================="
}

send_vote() {
    OPTION_KEY=$1

    DEST_ADDR=$(jq -r --arg key "$OPTION_KEY" '.option_addresses[$key]' $DATA_FILE)

    if [ "$DEST_ADDR" == "null" ] || [ -z "$DEST_ADDR" ]; then
        echo "❌ ERROR: Clave de opción ($OPTION_KEY) no encontrada en el archivo de datos." >&2
        return 1
    fi

    # Redirigir todos los mensajes de debug a STDERR (>&2)
    echo "  > Ejecutando: VOTO N° $(($i + 1)) -> DESTINO ($DEST_ADDR) con $REQUIRED_AMOUNT FTC." >&2

    TXID_VOTE=$($CLI sendtoaddress "$DEST_ADDR" "$REQUIRED_AMOUNT" "" "" "false" 2>&1)

    if echo "$TXID_VOTE" | grep -q "error"; then
        echo "❌ ERROR al enviar voto. Detalles:" >&2
        echo "$TXID_VOTE" >&2
        return 1
    fi

    echo "✅ Voto enviado. TXID: $TXID_VOTE" >&2

    # SOLO la TXID pura va a STDOUT
    echo "$TXID_VOTE"
    return 0
}

secure_counting() {
    echo "=========================================================="
    echo "      5. RECUENTO SEGURO Y AUDITORÍA                      "
    echo "=========================================================="

    declare -g -A RESULT_COUNTS
    TOTAL_VOTOS_VALIDOS=0

    ADDRESSES_JSON=$(jq -r '.option_addresses' $DATA_FILE)

    TEMP_COUNTS_FILE=$(mktemp)

    echo "$ADDRESSES_JSON" | jq -r 'to_entries[] | "\(.key)|\(.value)"' | while IFS='|' read -r KEY ADDRESS; do

        OPTION_NAME=$(jq -r --arg key "$KEY" '.options[] | select(.key == $key) | .name' $OPTIONS_FILE)

        VOTE_COUNT=$($CLI listreceivedbyaddress 0 true true "$ADDRESS" | jq -r '.[] | .txids | length' 2>/dev/null)

        if [ -z "$VOTE_COUNT" ]; then
            VOTE_COUNT=0
        fi

        echo "$OPTION_NAME:$VOTE_COUNT" >> $TEMP_COUNTS_FILE

    done

    while IFS=':' read -r OPTION_NAME COUNT_VALUE; do

        RESULT_COUNTS[$OPTION_NAME]=$COUNT_VALUE
        TOTAL_VOTOS_VALIDOS=$((TOTAL_VOTOS_VALIDOS + COUNT_VALUE))
        echo "  > ${OPTION_NAME}: $COUNT_VALUE votos válidos encontrados."
    done < $TEMP_COUNTS_FILE

    rm $TEMP_COUNTS_FILE

    QUESTION_TEXT=$(jq -r '.question' $OPTIONS_FILE)

    echo "# RESULTADOS DE LA VOTACIÓN SEGURA FTC" > $RESULTS_FILE
    echo "" >> $RESULTS_FILE
    echo "## PREGUNTA" >> $RESULTS_FILE
    echo "$QUESTION_TEXT" >> $RESULTS_FILE
    echo "" >> $RESULTS_FILE

    echo "## AUDITORÍA" >> $RESULTS_FILE
    echo "| Categoría | Votos |" >> $RESULTS_FILE
    echo "| :--- | :--- |" >> $RESULTS_FILE
    echo "| Votos Válidos | $TOTAL_VOTOS_VALIDOS |" >> $RESULTS_FILE
    echo "| Votos Duplicados | (Requiere análisis) |" >> $RESULTS_FILE
    echo "| Votos Inválidos/Monto Incorrecto | (Requiere análisis) |" >> $RESULTS_FILE
    echo "" >> $RESULTS_FILE

    echo "## RECUENTO FINAL" >> $RESULTS_FILE
    echo "| Opción | Votos |" >> $RESULTS_FILE
    echo "| :--- | :--- |" >> $RESULTS_FILE

    JSON_GRAPH_DATA='{'
    for OPTION_NAME in "${!RESULT_COUNTS[@]}"; do
        COUNT_VALUE=${RESULT_COUNTS[$OPTION_NAME]}
        echo "| $OPTION_NAME | $COUNT_VALUE |" >> $RESULTS_FILE
        JSON_GRAPH_DATA+='"'${OPTION_NAME}'":'${COUNT_VALUE}','
    done
    JSON_GRAPH_DATA=${JSON_GRAPH_DATA%,}
    JSON_GRAPH_DATA+='}'

    echo "$JSON_GRAPH_DATA" | jq . > "${POLL_PREFIX}_results.json"

    echo "" >> $RESULTS_FILE
    echo "Generado en $(date -u) UTC." >> $RESULTS_FILE

    echo "✅ Recuento y auditoría finalizados."
    echo "=========================================================="
}

# --------------------------------------------------------------------------------
#                   INICIO DEL PROTOCOLO
# --------------------------------------------------------------------------------

main() {
    POLL_PREFIX="FTC_VOTE_$(date +%Y%m%d)"
    DATA_FILE="${POLL_PREFIX}_data.json"
    OPTIONS_FILE="${POLL_PREFIX}_options.json"

    echo "=========================================================="
    echo "          INICIO DE PROTOCOLO DE VOTACIÓN SEGURO"
    echo "=========================================================="
    echo "ADVERTENCIA: REQUIERE que $WALLET_NAME esté desbloqueado y tenga > 1.05 FTC * número de votantes."

    # Si hay archivos de votación, el usuario quiere continuar. De lo contrario, iniciar desde cero.
    if [ ! -f "$DATA_FILE" ] || [ ! -f "$OPTIONS_FILE" ]; then
        cleanup
        interactive_setup
        setup_poll_addresses

        echo "=========================================================="
        echo "      3. GENERANDO IDs DE VOTANTE (Padrón)"
        echo "=========================================================="

        while true; do
            read -p "¿Cuántos VOTANTES/IDs desea generar? (Mínimo 1): " NUM_VOTERS_INPUT
            if [[ "$NUM_VOTERS_INPUT" =~ ^[0-9]+$ ]] && [ "$NUM_VOTERS_INPUT" -ge 1 ]; then
                NUM_VOTERS=$NUM_VOTERS_INPUT
                break
            else
                echo "❌ Entrada no válida. Por favor, ingrese un número entero mayor o igual a 1."
            fi
        done

        > $VOTER_ID_FILE

        for i in $(seq 1 $NUM_VOTERS); do
            $CLI getnewaddress -addresstype=legacy >> $VOTER_ID_FILE
        done

        echo "✅ Padrón creado con $NUM_VOTERS IDs de Votante únicos en $VOTER_ID_FILE."
        echo "=========================================================="

        fund_voters
    fi # Fin de la lógica de inicio/continuación

    echo "=========================================================="
    echo "      6. EMISIÓN INTERACTIVA DE VOTOS"
    echo "=========================================================="

    VOTER_IDS_ARRAY=($(cat $VOTER_ID_FILE 2>/dev/null))
    if [ ${#VOTER_IDS_ARRAY[@]} -eq 0 ]; then
        echo "❌ ERROR: No se encontraron IDs de votante. Finalizando el protocolo."
        exit 1
    fi

    LAST_TXID=""
    OPTIONS_MAP=$(jq -r '.options[] | "\(.name)"' $OPTIONS_FILE | tr '\n' ' ')

    echo "OPCIONES VÁLIDAS:"
    jq -r '.options[] | "\(.name)"' $OPTIONS_FILE | sed 's/^/  - /'
    echo "-------------------------------------"


    for i in "${!VOTER_IDS_ARRAY[@]}"; do
        VOTER_ID=${VOTER_IDS_ARRAY[i]}

        while true; do
            # 🎯 INSTRUCCIÓN CLAVE DE USO: Pedir al usuario que use comillas.
            read -p "VOTO N° $((i+1)). Escriba la OPCIÓN (Ej: opción con espacios): " OPTION_NAME_INPUT

            # Quitar comillas del input si existen, para la búsqueda en el JSON
            # Es un paso de limpieza redundante de seguridad
            CLEANED_INPUT=$(echo "$OPTION_NAME_INPUT" | sed 's/"//g')

            OPTION_KEY_INPUT=$(jq -r --arg name "$CLEANED_INPUT" \
                '.options[] | select(.name == $name) | .key' $OPTIONS_FILE)

            if [ -z "$OPTION_NAME_INPUT" ]; then
                echo "⚠️ No se introdujo opción. Saltando voto N° $((i+1))." >&2
                break
            fi

            if [ -n "$OPTION_KEY_INPUT" ] && [ "$OPTION_KEY_INPUT" != "null" ]; then
                break
            else
                echo "❌ Opción NO válida. Por favor, use una de las siguientes (Ej: opción con espacios): " >&2
                jq -r '.options[] | "\(.name)"' $OPTIONS_FILE | sed 's/^/  - /' >&2
            fi
        done

        if [ -z "$OPTION_NAME_INPUT" ]; then
            continue
        fi

        TXID_OUTPUT=$(send_vote "$OPTION_KEY_INPUT")

        if [ $? -ne 0 ]; then
            echo "❌ ERROR fatal al enviar voto N° $((i+1)). Continuando..." >&2
            continue
        fi

        LAST_TXID="$TXID_OUTPUT"
    done

    echo "Última TXID exitosa a verificar: $LAST_TXID"

    # 7. Bucle de Confirmación
    MAX_CONFIRM_RETRIES=30
    RETRY_DELAY=10
    CONFIRMATIONS=0
    RETRY_COUNT=0

    CLEAN_TXID=$(echo "$LAST_TXID" | tr -d '\n' | grep -oP '^[[:xdigit:]]{64}$')

    if [ -z "$CLEAN_TXID" ] || echo "$LAST_TXID" | grep -q "error"; then
        echo "❌ No se pudo obtener una última TXID válida para esperar confirmación. Asumiendo confirmación para continuar."
    else
        echo "Esperando confirmación para TXID: $CLEAN_TXID (Necesitas $REQUIRED_CONFIRMATIONS, MÁXIMO $((MAX_CONFIRM_RETRIES * RETRY_DELAY)) segundos)..."

        while [ "$CONFIRMATIONS" -lt "$REQUIRED_CONFIRMATIONS" ] && [ "$RETRY_COUNT" -lt "$MAX_CONFIRM_RETRIES" ]; do
            CONFIRMATIONS=$($CLI gettransaction "$CLEAN_TXID" | jq -r .confirmations 2>/dev/null)

            if [ -z "$CONFIRMATIONS" ] || [ "$CONFIRMATIONS" = "null" ]; then
                CONFIRMATIONS=0
            fi

            if [ "$CONFIRMATIONS" -lt "$REQUIRED_CONFIRMATIONS" ]; then
                echo "   > Confirmaciones: $CONFIRMATIONS/$REQUIRED_CONFIRMATIONS. Reintentando en $RETRY_DELAY segundos... (Intento $((RETRY_COUNT + 1))/$MAX_CONFIRM_RETRIES)"
                sleep "$RETRY_DELAY"
                RETRY_COUNT=$((RETRY_COUNT + 1))
            fi
        done

        # LÓGICA DE BYPASS DE CONFIRMACIÓN
        if [ "$CONFIRMATIONS" -ge "$REQUIRED_CONFIRMATIONS" ]; then
            echo "✅ Transacción confirmada en bloque ($CONFIRMATIONS confirmaciones)."
        else
            echo "❌ ADVERTENCIA: La Transacción ($CLEAN_TXID) solo tiene $CONFIRMATIONS/$REQUIRED_CONFIRMATIONS confirmaciones después de $MAX_CONFIRM_RETRIES reintentos."
            read -p "¿Desea proceder con el RECUENTO y la AUDITORÍA A PESAR de las bajas confirmaciones? (y/N): " PROCEED_COUNTING
            if [[ "$PROCEED_COUNTING" == "y" || "$PROCEED_COUNTING" == "Y" ]]; then
                echo "⚠️ Procediendo con el recuento, asumiendo que las transacciones serán válidas."
            else
                echo "❌ RECUENTO CANCELADO por la falta de confirmaciones. El protocolo finaliza aquí."
                exit 1
            fi
        fi
    fi

    # 8. RECUENTO SEGURO Y AUDITORÍA
    secure_counting

    # 9. GENERANDO GRÁFICOS Y RESULTADOS FINALES
    echo "=========================================================="
    echo "          9. GENERANDO GRÁFICOS Y RESULTADOS FINALES"
    echo "=========================================================="

    echo "Ejecutando script de graficado ($GRAPH_SCRIPT)..."
    python3 $GRAPH_SCRIPT

    if [ $? -ne 0 ]; then
        echo "❌ ERROR: Falló la generación de gráficos. Asegúrate de tener 'generate_graphs.py' y la librería Matplotlib instalada (pip install matplotlib)."
    fi

    echo ""
    echo "=========================================================="
    echo "          FIN DE PROTOCOLO DE VOTACIÓN SEGURO             "
    echo "=========================================================="
}

# Ejecutar el proceso principal
main
 

_____________________________________________________________________ 

 

 

unlock_wallet.py

_____________________________________________________________________ 

import subprocess
import getpass
import sys
import time

# --- Configuration ---
WALLET_NAME = "votingWallet"

def secure_unlock():
    """Prompts for password securely and executes the Feathercoin unlock command."""
    print("--- Secure Wallet Unlock Script ---")

    # 1. Securely ask for the password without echoing it
    try:
        # Use getpass to hide the password input
        password = getpass.getpass(f"Enter password for wallet '{WALLET_NAME}': ")
    except Exception as e:
        print(f"Error during password input: {e}")
        return

    # 2. Define the unlock command
    # Unlocks for 600 seconds (10 minutes)
    unlock_time = 600
    command = [
        "./feathercoin-cli",
        # CRITICAL: Always specify the named wallet
        f"-rpcwallet={WALLET_NAME}",
        "walletpassphrase",
        password,
        str(unlock_time)
    ]

    print(f"Executing unlock command for {unlock_time} seconds...")

    # 3. Execute the command securely
    try:
        result = subprocess.run(command, capture_output=True, text=True, check=False)

        if result.returncode == 0:
            print("\n✅ Wallet UNLOCKED successfully.")
            print("You now have 10 minutes to run your administrative scripts.")
        else:
            print("\n❌ WALLET UNLOCK FAILED. Error details:")
            print(result.stderr.strip())
            print("Possible causes: Incorrect password, wallet not loaded, or Feathercoin daemon not running.")

    except FileNotFoundError:
        print("\nERROR: 'feathercoin-cli' not found. Check your PATH or run this script from the Feathercoin directory.")

# Execute the script
if __name__ == "__main__":
    secure_unlock()
 

_____________________________________________________________________ 

 


generate_graphs.py

_____________________________________________________________________ 

 import json
import sys
import os
from datetime import datetime
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator

def generate_graph():
    # 1. Encontrar el archivo JSON más reciente
    poll_prefix = f"FTC_VOTE_{datetime.now().strftime('%Y%m%d')}"
    json_filename = f"{poll_prefix}_results.json"
    graph_filename = f"{poll_prefix}_grafica_votos.png"

    if not os.path.exists(json_filename):
        print(f"Error: No se encontró el archivo de resultados JSON: {json_filename}")
        sys.exit(1)

    # 2. Leer los datos del resultado del recuento
    try:
        with open(json_filename, 'r') as f:
            data = json.load(f)
    except Exception as e:
        print(f"Error al leer el archivo JSON: {e}")
        sys.exit(1)

    if not data:
        print("No hay datos para graficar.")
        sys.exit(0)

    # Leer la pregunta del archivo de opciones para el título
    options_filename = f"{poll_prefix}_options.json"
    question_text = "Resultados de la Votación"
    if os.path.exists(options_filename):
        try:
            with open(options_filename, 'r') as f_opt:
                options_data = json.load(f_opt)
                question_text = options_data.get('question', question_text)
        except:
            pass

    # 3. Preparar y ORDENAR datos por número de votos (de mayor a menor)
    # Convertir el diccionario a una lista de tuplas (opción, conteo)
    sorted_items = sorted(data.items(), key=lambda item: item[1], reverse=True)

    options = [item[0] for item in sorted_items]
    counts = [item[1] for item in sorted_items]

    # 4. Configurar colores
    colors = ['#2CA1A5', '#36C5C9', '#5ACFD3', '#7EDADD', '#A2E4E6', '#C7EFF0', '#0F3738', '#227E81', '#051414', '#EBF9FA']
    current_colors = colors[:len(options)]

    # 5. Crear la gráfica
    fig, ax = plt.subplots(figsize=(10, 6))

    # Crear las barras, redondeando los bordes con edgecolor y linewidth
    bars = ax.bar(
        options,
        counts,
        color=current_colors,
        edgecolor='#222222', # Borde más oscuro para el efecto redondeado
        linewidth=0.8
    )

    # Añadir las etiquetas de conteo encima de cada barra
    for bar in bars:
        yval = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2, yval + 0.1, int(round(yval)), ha='center', va='bottom', fontsize=12)

    # 6. Configurar la apariencia
    ax.set_ylabel('Número de Votos', fontsize=14)
    ax.set_title(f"{question_text}\n(Generado el {datetime.now().strftime('%Y-%m-%d')})", fontsize=16)
    ax.set_xlabel('Opciones de Voto', fontsize=14)

    # Forzar que las marcas del eje Y sean solo números enteros.
    ax.yaxis.set_major_locator(MaxNLocator(integer=True))

    # Ajustar límite Y
    max_count = max(counts) if counts else 0
    y_limit = max(1.0, max_count * 1.2)
    ax.set_ylim(0, y_limit)

    # Configurar el marco de la tabla (Axis spine)
    for spine in ax.spines.values():
        spine.set_linewidth(1.5)  # Engrosar el borde
        spine.set_edgecolor('#555555') # Color del borde

    plt.tight_layout()

    # 7. Guardar la gráfica
    plt.savefig(graph_filename)
    print(f"Gráfica guardada como {graph_filename}")

if __name__ == "__main__":
    generate_graph()

_____________________________________________________________________ 


 

2.- Ahora arrancamos un nodo de Feathercoin en una de las terminales que indexe las transacciones en la piscina de memoria mediante el comando

./feathercoind -txindex=1

 

y cargamos nuestro monedero:

./feathercoin-cli loadwallet "votingWallet"


información técnica del nodo

En la consola del nodo podrás ver pasar la información de como se añade la cartera, así como los votos una vez sucedan.


Ahora debemos esperar a que nuestro nodo este sincronizado. Si es la primera vez que arrancas el nodo deberemos esperar mucho. Aunque existe una opcion para el recuento aun no teniendo las suficientes confirmaciones es posible que no funcione si el nodo no esta sincronizado.

Ya tenemos todo listo para comenzar. Ahora vamos a correr un script de python que permite desbloquer tu cartera durante el tiempo que especifiques, en el caso por defecto son 10 minutos.

python3 unlock_wallet.py


Pon la contraseña elegida para la cartera y comenzamos corriendo el siguiente script que incia el protocolo. ¡Que comienza la votación!

./vote_protocol.sh

.
Primera parte de la votación. Establecer la pregunta, las respuestas y los votantes.


Ahora debemos introducir la pregunta, las respuestas posibles (entrecomilladas si incluyen espacios en blanco) y el número de votantes. Si se preveen votos no validos es conveniente establecer aqui también esa última opción.

Ahora esperamos algo mas de un minuto a que se propagen las transacciones con la información necesaria para el desarrollo de la consulta. En este momento se ponen fondos para que cada dirección pueda ejercer su voto, y se preparan las direcciones que representan las respuestas a la pregunta.

Emisión de los votos o recuento por personal de mesa autorizado.


Una vez transcurrida la espera comienza la votación. Ahora deben votar por turno las personas. Si es preciso se deben establecer papeletas fisicas en una urna y posteriormente se realiza el volcado de la consulta analógica en la cadena de bloques.

Confirmaciones y generación de resultados.


Hecha la votación, ahora esperamos a la confirmación de las transacciones. La velocidad de esta confirmación varía en funcion de la red y del nodo. Transcurridos 300 segundos se nos preguntará si queremos continuar aún sin las tres confirmaciones, a lo que podemos responder que sí pues rara vez son canceladas una vez la primera confirmacion entra. Una vez terminada esta confirmación comienza el recuento y se generan los resultados en un archivo RESULTS.md

Gráfica con los resultados de la votación anónima.


Además encontraremos generados en la carpeta de la aplicación un padron electoral con las direcciones empleadas para votar voters_ids.txt y archivos .json incluyendo los datos de la votación, las opciones, y los resultados, junto a un archivo gráfico generado .png con el número de votos por opción.

Todo este trabajo esta publicado bajo licencia Creative Commons y su caracter es puramente educativo y experimental. Programado con asistencia de google gemini. Noviembre 2025. 





No hay comentarios:

Publicar un comentario