Como desenvolvedor independente, passei um mês e meio construindo do zero um aplicativo desktop de monitoramento e análise inteligente de ativos de dupla cadeia chamado "Sentinela Web3". Ele suporta toda a gama de cadeias compatíveis com EVM e a cadeia Solana, integrando funções avançadas como interpretação de trades com IA, notificações multicanal e agregação de dados on-chain, e já está em funcionamento estável há meses.

Anteriormente, publiquei uma visão técnica, mas sempre senti que a análise da implementação central não era suficientemente "satisfatória". Por isso, decidi escrever este aprofundamento técnico, compartilhando sem reservas as decisões de design e os detalhes de implementação que estão escondidos nas linhas de código, abordando aspectos como arquitetura de processos, modelo de I/O, consistência de dados e engenharia de IA. Espero que este artigo possa fornecer referências valiosas para desenvolvedores que também estão explorando o campo do Web3.

Uma, Arquitetura de processos: por que escolher "processo principal + múltiplos subprocessos"?

Muitos scripts de monitoramento Python no mercado usam um único processo asyncio, mas desde o início do meu projeto eu decidi adotar uma arquitetura de microkernel "processo principal (GUI) + subprocessos independentes (EVM/SOL)".

1.1 Isolamento e estabilidade acima de tudo

Conexões longas de WebSocket são propensas a exceções de reinicialização em momentos de flutuação de rede, e até mesmo a extensão C da biblioteca subjacente pode falhar por razões desconhecidas. Se misturarmos a GUI com a lógica de monitoramento em um único processo, qualquer exceção não capturada ou violação de acesso à memória pode levar o aplicativo desktop a falhar. Ao usar subprocess.Popen para isolar a lógica de monitoramento de EVM e Solana em subprocessos, eu implementei isolamento físico: se Evm.py falhar ou for encerrado à força, a janela principal continuará a funcionar normalmente, o ícone da bandeja não desaparecerá e o usuário poderá clicar em "Iniciar" para reiniciar.

Transmissão de logs: o processo principal captura o stdout do subprocesso através de um pipe, utilizando uma thread forwardoutput para ler linha por linha, limpar códigos de cor ANSI e, em seguida, injetar no DOM do front-end através de window.evaluate_js, permitindo a atualização em tempo real dos logs enquanto mantém o thread da UI leve.

1.2 Detalhes da gestão do ciclo de vida

No core_process.py, parar subprocessos não é tão simples quanto usar terminate(). Eu implementei um mecanismo de força de finalização:

python

proc.terminate()

proc.wait(timeout=3)

if proc.poll() is None:

proc.kill()

proc.wait(timeout=2)

if proc.poll() is None:

os.system(f'taskkill /F /PID {proc.pid}')

Esse conjunto de técnicas garante que mesmo que o interpretador Python trave, a camada de Windows consiga limpar completamente a árvore de processos, evitando processos residuais que ocupem portas ou bloqueiem bancos de dados, resultando em falha na próxima inicialização.

Dois, Modelo de I/O e conexão de alta disponibilidade: não é só asyncio

2.1 Modo de monitoramento híbrido: WSS em tempo real + compensação RPC

Para moedas nativas de cadeias EVM, como não há registro de eventos Transfer padrão, não é possível assinar através de WSS. Eu projetei um compensador de polling: a cada 60 segundos, utilizo RPC para obter eth_getBalance e comparo com o snapshot da memória, se a diferença exceder 1e-18, aciono uma notificação. Esse mecanismo aparentemente simples é, na verdade, a última linha de defesa quando a assinatura do WSS fica inativa.

Para tokens e NFTs, o sistema se inscreve em logs e processa minuciosamente os eventos TransferSingle e TransferBatch do ERC1155. Especialmente para TransferBatch, cujo campo data contém um array dinâmico, implementei uma análise de deslocamento manual baseada na especificação ABI, ao invés de depender de bibliotecas pesadas, reduzindo significativamente o custo de análise.

2.2 O backoff exponencial do WSS e a comutação de nós quentes

Em ambientes de produção, nós públicos RPC/WSS podem estar sujeitos a limitações de taxa ou falhas a qualquer momento. Eu implementei uma estratégia de rotação e reconexão de nós em chain_wss_monitor_direction:

Pool de nós: o arquivo de configuração deve definir múltiplos WSS_NODES para cada cadeia, escolhendo aleatoriamente ou em sequência um ao iniciar.

Algoritmo de backoff: após a desconexão, o intervalo de nova tentativa começa em 5 segundos, dobrando a cada tentativa até 60 segundos, evitando uma tempestade de reconexões ao estilo DDoS antes que o nó se recupere.

Adaptação ao ambiente de rede doméstico: o sistema suporta a configuração do endereço HTTP do software de proxy local, redirecionando o tráfego WSS para o proxy através da biblioteca websockets_proxy, restaurando a comunicação estável com nodos no exterior.

2.3 Pipeline de análise assíncrona da Solana

A velocidade de blocos da Solana é extremamente rápida e a estrutura das transações é complexa. Para evitar que chamadas à API do Helius bloqueiem a recepção de mensagens do WSS, eu projetei um modelo desacoplado de produtor-consumidor:

Produtor: Após receber uma assinatura do WSS logsSubscribe, ela é imediatamente colocada em asyncio.Queue ou aciona uma tarefa de fundo com asyncio.create_task.

Consumidor: tarefas assíncronas independentes responsáveis por chamar a interface Helius /v0/transactions, analisando campos como nativeTransfers, tokenTransfers, events.nft, etc.

Isso garante que o loop recv() da conexão WSS nunca fique preso por solicitações HTTP lentas, garantindo a atualidade das mensagens em um ambiente de TPS extremamente alto.

Três, Consistência de dados: da desduplicação na memória às restrições do SQLite

3.1 Lado EVM: índice único do banco de dados

Na monitorização de EVM, uma única transação pode ser processada várias vezes devido à reconexão do WSS, compensação de polling e outros fatores. Apenas depender de um set de memória não é suficiente para lidar com reinicializações do processo. Portanto, eu projetei uma restrição composta única para a tabela tx_history:

sql

UNIQUE(tx_hash, log_index, address)

Qualquer inserção duplicada será silenciosamente descartada pelo ON CONFLICT IGNORE do SQLite, garantindo idempotência do nível do núcleo do banco de dados. Para transferências de moeda nativa sem log_index, a combinação tx_hash + address será utilizada como chave composta.

3.2 Lado Solana: desduplicação de assinatura dentro da janela de tempo

As transações Solana não têm o conceito de log_index, e a análise do Helius pode gerar múltiplos registros. Eu usei um set de memória para armazenar as assinaturas processadas recentemente e, utilizando a ideia de uma variação do LimitedSizeDict, ao exceder 1000 assinaturas, automaticamente esvaziamos metade (ou utilizamos OrderedDict para remover a entrada mais antiga). Essa desduplicação de janela deslizante alcança um bom equilíbrio entre desempenho e precisão.

Quatro, Engenharia de IA: Tolerância a falhas de múltiplos provedores e a arte da análise JSON

4.1 Escalonamento dinâmico baseado em recursos

O MultiAIClient é o núcleo do módulo de IA. Não é apenas um simples if-else, mas um agendador baseado em bandeiras de recursos. O arquivo de configuração define as funcionalidades suportadas por cada provedor (como transaction_insight, daily_report, etc.). Quando uma solicitação é interpretada, o sistema filtra a lista de provedores que suportam aquele recurso e inicia a solicitação em ordem de prioridade, com timeout ou falha automaticamente rebaixando para o próximo.

4.2 Análise defensiva da saída do LLM

A saída do grande modelo em JSON é instável por natureza. Meu fluxo de tratamento é muito mais complexo que o json.loads:

Limpeza: remove tags de bloco de código Markdown json` e.

Extração por regex: se a análise falhar, use diretamente a expressão regular r'"insight"\s*:\s*"([^"]*)"' para extrair o campo de forma bruta, esta é a última linha de defesa.

Mapeamento de campos: compatível com várias nomeações de chave como insight / Insight / interpretação.

Esse mecanismo garante que mesmo que o Moonshot ou DeepSeek retornem "metade de um JSON", o front-end ainda consiga exibir uma interpretação válida, sem gerar JSONDecodeError que levaria a uma tela em branco.

Cinco, Memória e desempenho: LimitedSizeDict e filas assíncronas

5.1 Dicionário de capacidade limitada personalizado

A biblioteca padrão do Python não possui um dicionário LRU embutido com limite de tamanho. Eu implementei o LimitedSizeDict com base em collections.OrderedDict:

python

def setitem(self, key, value):

if len(self) >= self.max_size:

self.popitem(last=False) # Remove o item mais antigo inserido

super().__setitem__(key, value)

Esta estrutura de dados simples é usada para cache de informações de Token, cache de preços e cache de metadados de NFT. Ela garante que, durante longos períodos de execução, o uso de memória não cresça linearmente com o aumento de endereços monitorados, mas se mantenha em um nível extremamente baixo.

5.2 Escrita assíncrona serial dos logs da Solana

Os logs detalhados de transações da Solana precisam ser escritos em um arquivo JSON. Se múltiplos corrotinas chamarem json.dump ao mesmo tempo, isso pode facilmente corromper o formato do arquivo. Eu introduzi asyncio.Queue:

Todas as solicitações de gravação de log colocarão dados na fila.

O único coroutine de fundo filewriter bloqueia à espera da fila, executando operações de I/O de arquivos após extrair os dados.

Isso não só evita bloqueios complexos de threads, mas também utiliza características assíncronas para garantir a segurança dos dados sob alta concorrência.

Seis, Comunicação bidirecional entre front-end e back-end: integração profunda do pywebview

6.1 Injeção de API JS

O pywebview permite expor métodos de objetos Python diretamente para o JavaScript do front-end. Minha classe Api herda de vários Mixin, e todos os métodos públicos que começam com def se tornam automaticamente membros de window.pywebview.api. Isso me permite implementar separação entre front-end e back-end a um custo muito baixo, com o front-end focando apenas na interação da UI.

6.2 Instâncias independentes e comunicação da janela flutuante

A janela flutuante não é um sub DIV da janela principal, mas sim uma segunda janela independente criada pelo pywebview. Eu gerencio seu ciclo de vida através do FloatingWindowManager e, utilizando a instância js_api do processo principal, injetei código JS na janela flutuante (evaluate_js), permitindo a atualização em tempo real dos logs da janela principal para a janela flutuante. Este design garante que mesmo que a janela flutuante seja fechada, não afetará a execução da tarefa de monitoramento principal.

Sete, Empacotamento de aplicativos desktop: as armadilhas do PyInstaller e práticas de engenharia

Entregar projetos Python a usuários finais sem formação técnica, empacotando em um EXE independente, é um caminho necessário. O PyInstaller parece resolver com um único comando, mas em projetos complexos, há uma infinidade de detalhes que podem fazer o desenvolvedor entrar em colapso. Esta seção compartilha algumas armadilhas típicas e soluções encontradas durante o empacotamento do "Web3 Sentinel".

7.1 Importações implícitas e --hidden-import

O PyInstaller constrói a árvore de dependências analisando estaticamente as instruções de importação do arquivo de entrada. No entanto, muitas bibliotecas (como pystray, websockets) usam importlib.import_module ou import para carregar dinamicamente submódulos, resultando em ModuleNotFoundError quando o EXE empacotado é executado.

A chave para resolver esse problema está em identificar módulos ausentes com base nas mensagens de erro e declarar explicitamente --hidden-import no comando de empacotamento. Por exemplo, na funcionalidade do ícone da bandeja, é necessário adicionar:

bash

--hidden-import pystray._win32

--hidden-import pystray._util

--hidden-import win32event

--hidden-import win32api

Isso requer que os desenvolvedores tenham um certo entendimento da estrutura interna das bibliotecas de dependência, normalmente exigindo leitura do código-fonte e tentativa e erro repetida para listar completamente todas as dependências implícitas.

7.2 --collect-all e armadilhas de arquivos de recursos

A biblioteca pywebview não só contém código Python, mas também depende de arquivos HTML/JS do front-end e do runtime Edge WebView2. A análise padrão do PyInstaller não consegue perceber esses recursos não-código. Se não for tratado, o programa empacotado falhará, resultando em uma tela em branco por não encontrar index.html ou webview.js.

A forma correta de tratamento é utilizar --collect-all pywebview, que força o PyInstaller a copiar todos os arquivos do diretório do pacote pywebview (incluindo binários e recursos estáticos) para o diretório de empacotamento. Esta é a operação padrão para lidar com bibliotecas GUI "pesadas".

7.3 Dependências binárias e compressão UPX

As bibliotecas dependentes do projeto, como pywin32 e Pillow, contêm arquivos binários .pyd e .dll. Esses arquivos são grandes e não serão automaticamente compactados pelo PyInstaller. Ao integrar a ferramenta UPX e especificar --upx-dir no comando de empacotamento, é possível compactar os arquivos binários dentro do EXE em uma taxa alta (normalmente reduzindo o tamanho em 30%-50%). É importante notar que alguns softwares antivírus antigos podem gerar falsos positivos em programas empacotados com UPX, mas para um público técnico, essa probabilidade é baixa e pode ser resolvida ao enviar amostras.

7.4 "Congelamento" de caminho e sys._MEIPASS

Este é o conceito mais central no empacotamento do PyInstaller. Durante a fase de desenvolvimento, o programa acessa arquivos de configuração e recursos de imagem através de file ou caminhos relativos. Após ser empacotado em um único arquivo EXE, todos os recursos são extraídos para um diretório temporário, cujo caminho é armazenado na variável sys._MEIPASS.

Os desenvolvedores devem substituir globalmente toda a lógica de acesso a arquivos no código, com o padrão típico sendo:

python

def get_resource_path(relative_path):

if getattr(sys, 'frozen', False):

base = sys._MEIPASS

else:

base = os.path.abspath(".")

return os.path.join(base, relative_path)

Ignorar essa adaptação resultará em não encontrar nenhum arquivo externo durante a execução do programa, que é o "inferno de caminhos" mais comum enfrentado por novatos ao empacotar. Minha abordagem foi encapsular essa função em utils.py, chamando de forma unificada em todo o projeto, garantindo a consistência do comportamento do caminho antes e depois do empacotamento.

7.5 Tratamento especial de subprocessos

Este sistema adota uma arquitetura de processo principal que inicia subprocessos. Após o empacotamento, os scripts Evm.py e Sol.py também estão encapsulados dentro do EXE. Se o processo principal ainda tentar iniciar Evm.py com python.exe, falhará por não encontrar o arquivo. Minha solução foi detectar dinamicamente se está em modo de empacotamento ao iniciar subprocessos e passar o parâmetro correto --main-exe-dir, permitindo que os subprocessos localizem o diretório externo onde o arquivo de configuração está localizado. Esses detalhes lógicos já foram discutidos no capítulo sobre a arquitetura de processos, então não vou me aprofundar mais aqui.

Oito, Conclusão

Revisando todo o processo de desenvolvimento, desde scripts únicos até arquiteturas multiprocessos, desde WebSockets brutos até pools de nós de alta disponibilidade, desde logs simples em print até armazenamento estruturado em SQLite, cada passo é uma profundidade maior na compreensão de engenharia. A exploração na etapa de empacotamento me fez perceber: fazer o software rodar de forma estável é só o primeiro passo, fazer com que os usuários possam usá-lo facilmente é a verdadeira entrega.

Isso não é apenas uma ferramenta de monitoramento, mas também o culminar da minha experiência prática em programação assíncrona, gerenciamento de processos, integração de IA e desenvolvimento de software desktop.

Se você se interessar por qualquer detalhe técnico mencionado, sinta-se à vontade para visitar meu GITHUB:

https://github.com/pingdj/Web3

Obtenha o software e mais documentos técnicos. Também espero trocar ideias e discutir com todos vocês na Binance Square.

Sobre o autor: Xiaonan, desenvolvedor independente & Web3 de pilha completa, focado em aplicativos desktop Python, análise de dados blockchain e implementação de engenharia de IA.

$BNB

BNB
BNB
785.44
-2.18%