├── README.md ├── scripts ├── analisis │ ├── app │ │ ├── __init__.py │ │ ├── main.aux │ │ ├── main.fls │ │ ├── main.fdb_latexmk │ │ ├── styles.py │ │ ├── main.log │ │ ├── readata.py │ │ ├── bot.py │ │ └── main.tex │ ├── SUBIDA.sh │ ├── autoprice │ │ ├── auto_price.sh │ │ └── updateprice.py │ ├── lectornpz.py │ ├── bot_data_manage.py │ ├── hello world │ │ ├── helloworld2.py │ │ └── helloworld1.py │ ├── bot_spider.py │ ├── blocknotify.py │ ├── actualizar_db.py │ ├── timestamp.py │ ├── block_size.py │ ├── btcsupply.py │ ├── price-hashrate.py │ ├── hashrate.py │ ├── recopilador.py │ ├── ntx.py │ └── dificultad.py ├── bins │ ├── br_d.png │ ├── br_w.png │ ├── HeavyDataNerdFont-Regular.ttf │ ├── BigBlueTerm437NerdFont-Regular.ttf │ ├── HeavyDataNerdFontPropo-Regular.ttf │ └── OpenDyslexicAltNerdFont-Regular.otf ├── images │ ├── tx.png │ ├── life.ppm │ ├── genesis.png │ ├── onchain.png │ ├── secret.png │ ├── txmodel.png │ ├── genesis2.png │ └── howworks.png └── README.md ├── req.txt ├── .gitignore └── LICENSE /README.md: -------------------------------------------------------------------------------- 1 | # HELLO WORLD 2 | -------------------------------------------------------------------------------- /scripts/analisis/app/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /scripts/analisis/app/main.aux: -------------------------------------------------------------------------------- 1 | \relax 2 | -------------------------------------------------------------------------------- /scripts/bins/br_d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/br_d.png -------------------------------------------------------------------------------- /scripts/bins/br_w.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/br_w.png -------------------------------------------------------------------------------- /scripts/images/tx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/tx.png -------------------------------------------------------------------------------- /req.txt: -------------------------------------------------------------------------------- 1 | tqdm==4.65.0 2 | python-bitcoinlib==0.11.2 3 | python-bitcoinrpc==1.0 4 | python-dotenv==0.21.1 -------------------------------------------------------------------------------- /scripts/images/life.ppm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/life.ppm -------------------------------------------------------------------------------- /scripts/images/genesis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/genesis.png -------------------------------------------------------------------------------- /scripts/images/onchain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/onchain.png -------------------------------------------------------------------------------- /scripts/images/secret.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/secret.png -------------------------------------------------------------------------------- /scripts/images/txmodel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/txmodel.png -------------------------------------------------------------------------------- /scripts/images/genesis2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/genesis2.png -------------------------------------------------------------------------------- /scripts/images/howworks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/images/howworks.png -------------------------------------------------------------------------------- /scripts/bins/HeavyDataNerdFont-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/HeavyDataNerdFont-Regular.ttf -------------------------------------------------------------------------------- /scripts/bins/BigBlueTerm437NerdFont-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/BigBlueTerm437NerdFont-Regular.ttf -------------------------------------------------------------------------------- /scripts/bins/HeavyDataNerdFontPropo-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/HeavyDataNerdFontPropo-Regular.ttf -------------------------------------------------------------------------------- /scripts/bins/OpenDyslexicAltNerdFont-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bitcoinR3search/nodedata/HEAD/scripts/bins/OpenDyslexicAltNerdFont-Regular.otf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /scripts/bins/.env 2 | /scripts/bins/database.npz 3 | /scripts/bins/MonoLisaSimpson.ttf 4 | /scripts/analisis/app/__* 5 | /scripts/bins/ath* 6 | -------------------------------------------------------------------------------- /scripts/analisis/SUBIDA.sh: -------------------------------------------------------------------------------- 1 | 2 | cd /home/ghost/BitcoinResearch/ 3 | 4 | git pull --rebase origin main 5 | 6 | git add -A 7 | git commit -m 'actualización semanal' 8 | git push origin main 9 | -------------------------------------------------------------------------------- /scripts/analisis/autoprice/auto_price.sh: -------------------------------------------------------------------------------- 1 | cd /home/ghost/BitcoinResearch/scripts/bins 2 | 3 | 4 | git add -A 5 | git commit -m "auto price $(date +'%Y-%m-%d')" 6 | git pull -ff origin main 7 | git push origin main 8 | -------------------------------------------------------------------------------- /scripts/analisis/lectornpz.py: -------------------------------------------------------------------------------- 1 | """ 2 | Este script es para verificar que los datos almacenados 3 | por recopilador.py son correctos 4 | """ 5 | 6 | from app.readata import * 7 | 8 | 9 | 10 | estado_data() 11 | -------------------------------------------------------------------------------- /scripts/analisis/app/main.fls: -------------------------------------------------------------------------------- 1 | PWD d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app 2 | INPUT c:/texlive/2020/texmf.cnf 3 | INPUT c:/texlive/2020/texmf-dist/web2c/texmf.cnf 4 | INPUT c:/texlive/2020/texmf-var/web2c/pdftex/pdflatex.fmt 5 | INPUT d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex 6 | OUTPUT d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.log 7 | -------------------------------------------------------------------------------- /scripts/analisis/bot_data_manage.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI, HTTPException, Request 2 | 3 | app = FastAPI() 4 | 5 | @app.post("/notify") 6 | async def notify_block(request: Request): 7 | if request.client.host != "127.0.0.1": 8 | return {"error": "Forbidden"}, 403 9 | 10 | # Obtener los datos del cuerpo de la solicitud 11 | body = await request.json() 12 | n = body.get("tipo") 13 | data = body.get("data") 14 | 15 | # Imprimir los valores recibidos 16 | print(n, data) 17 | 18 | return {"message": "Notification received"} 19 | 20 | if __name__ == '__main__': 21 | import uvicorn 22 | uvicorn.run(app, host='127.0.0.1', port=8000) 23 | -------------------------------------------------------------------------------- /scripts/analisis/autoprice/updateprice.py: -------------------------------------------------------------------------------- 1 | """ 2 | # En este script vemos como usar un request 3 | # a una url que devuelve datos en json 4 | """ 5 | 6 | 7 | import datetime 8 | import requests 9 | 10 | 11 | def func(): 12 | """ 13 | recopila el dato del precio de coindesk 14 | """ 15 | now = datetime.datetime.now() 16 | 17 | url_req = requests.get('http://api.coindesk.com/v1/bpi/currentprice.json',timeout=5) 18 | data = url_req.json()['bpi']['USD']['rate'] 19 | data_clean = float(data.replace(',', '')) 20 | print(round(data_clean,2),end=' ') 21 | print('USD',end=',') 22 | print(now.strftime("%Y-%m-%d %H:%M:%S")) 23 | 24 | if __name__== "__main__": 25 | func() 26 | -------------------------------------------------------------------------------- /scripts/analisis/hello world/helloworld2.py: -------------------------------------------------------------------------------- 1 | """ 2 | Este script es de prueba para ver si conecta al nodo 3 | y esta configurado 4 | """ 5 | 6 | import os 7 | from bitcoinrpc.authproxy import AuthServiceProxy 8 | from dotenv import load_dotenv 9 | from pprint import pprint 10 | 11 | #os.chdir('D:\proyectos\BitcoinResearch\BitcoinResearch\scripts') 12 | os.chdir('/home/ghost/BitcoinResearch/scripts') 13 | 14 | load_dotenv('/home/ghost/.env') 15 | #load_dotenv('analisis/.env') 16 | 17 | 18 | # RPC remote procedure call 19 | rpc_user = os.getenv('user') 20 | rpc_password = os.getenv('pass') 21 | 22 | 23 | # rpc_user and rpc_password are set in the bitcoin.conf file 24 | 25 | rpc_connection = AuthServiceProxy("http://%s:%s@nodeone.local:8332" % (rpc_user, rpc_password)) 26 | 27 | best = rpc_connection.getblockchaininfo() 28 | pprint(best) 29 | -------------------------------------------------------------------------------- /scripts/analisis/hello world/helloworld1.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEPRECATED 3 | 4 | Este script es de prueba para ver si conecta al nodo 5 | y esta configurado 6 | """ 7 | import os 8 | from bitcoin.rpc import RawProxy 9 | from pprint import pprint 10 | from dotenv import load_dotenv 11 | 12 | os.chdir('D:\proyectos\BitcoinResearch\BitcoinResearch\scripts') 13 | 14 | #load_dotenv('/home/ghost/.env') 15 | load_dotenv('/analisis/.env') 16 | 17 | 18 | # # RPC remote procedure call 19 | # rpc_user = os.getenv('user') 20 | # print(rpc_user) 21 | # rpc_password = os.getenv('pass') 22 | # #p = RawProxy(service_url='http://%s:%s@nodeone.local:8332' % 23 | # # (rpc_user, rpc_password)) 24 | 25 | 26 | p = RawProxy(service_url='http://%s:%s@192.168.1.4:8332' % 27 | (rpc_user, rpc_password)) 28 | 29 | 30 | # info = p.getblockchaininfo() 31 | 32 | # pprint(info) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 ePrime Research 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/analisis/app/main.fdb_latexmk: -------------------------------------------------------------------------------- 1 | # Fdb version 3 2 | ["pdflatex"] 1709259465 "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex" "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.pdf" "main" 1709259465 3 | "c:/texlive/2020/texmf-dist/web2c/texmf.cnf" 1616473097 39451 15a3ebee027466ecb89701aa91dfebaf "" 4 | "c:/texlive/2020/texmf-var/web2c/pdftex/pdflatex.fmt" 1616479767 2706494 62266d865e6d435e96efb4e150df3bb9 "" 5 | "c:/texlive/2020/texmf.cnf" 1616479651 713 e69b156964470283e0530f5060668171 "" 6 | "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.aux" 1709259465 9 a94a2480d3289e625eea47cd1b285758 "" 7 | "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex" 1709259461 24469 a055610ba2d06fdc4c39e067d1752403 "" 8 | "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/rbf.cls" 0 -1 0 "" 9 | "main.tex" 1709259461 24469 a055610ba2d06fdc4c39e067d1752403 "" 10 | "rbf.cls" 0 -1 0 "" 11 | (generated) 12 | "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.pdf" 13 | "d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.log" 14 | "main.log" 15 | -------------------------------------------------------------------------------- /scripts/analisis/bot_spider.py: -------------------------------------------------------------------------------- 1 | # Este bot es un servidor API - REST que escucha la llegada 2 | # de nuevos bloques desde el nodo. Procesa los datos para 3 | # encontrar ATH en: 4 | # 1 Hashrate promedio movil de 1 semana 5 | # 2 block time total 6 | # 3 block time halv 7 | # 4 dificultad 8 | 9 | ############################################################# 10 | # Librerias 11 | 12 | import uvicorn 13 | from app.bot import analisis,actualizar_server 14 | from fastapi import FastAPI, HTTPException, Request, BackgroundTasks 15 | 16 | 17 | app = FastAPI() 18 | 19 | 20 | # Esta sección escucha la llegada de nuevos bloques 21 | # cuando llegan analiza si hay nuevos ATH. 22 | @app.post("/notify") 23 | async def notify_block(request: Request): 24 | if request.client.host != "127.0.0.1": 25 | return {"error": "Forbidden"}, 403 26 | 27 | block_info = await request.json() 28 | block_hash = block_info.get("hash") 29 | if not block_hash: 30 | raise HTTPException(status_code=400, detail="Block hash not provided") 31 | 32 | print(f"Nuevo bloque recibido: {block_hash}") 33 | analisis(hash=block_hash) 34 | actualizar_server() 35 | return {"message": "Notification received"} 36 | 37 | 38 | 39 | 40 | if __name__ == '__main__': 41 | actualizar_server() 42 | uvicorn.run(app, host='127.0.0.1', port=8000) 43 | 44 | -------------------------------------------------------------------------------- /scripts/analisis/app/styles.py: -------------------------------------------------------------------------------- 1 | 2 | ''' 3 | este script contiene los estilos y colores que se usaran en las gráficas 4 | contiene: 5 | Colores de la imagen -> estilo_ 6 | 7 | Colores de los datos -> colores 8 | ''' 9 | 10 | # se tienen variables que contienen los colores del formato: 11 | # estilo[0]= Titulos y letras 12 | # estilo[1] = Fondo principal 13 | # estilo[2] = Fondo imagen 14 | estilo_dark=[(255/255., 255/255., 255/255.), (7/255., 25/255., 82/255.), (7/255., 25/255., 82/255.)] 15 | estilo_blanco=[(58/255., 53/255., 59/255.), (255/255., 255/255., 255/255.), (255/255., 255/255., 255/255.)] 16 | 17 | Estilos = { 18 | 'estilo_dark': estilo_dark, 19 | 'estilo_blanco': estilo_blanco} 20 | 21 | #(255, 219, 225) 22 | # colores: ESTA VARIABLE CONTIENE COLORES 23 | colores = [(255,255,255), #Blanco 0 24 | (0,0,0), #negro 1 La escala de colores primario, fuerte, debil 25 | (21,150,129),(170, 226, 255),(190, 247, 245), # Verde 2 3 4 26 | (255, 3, 45),(79, 32, 40),(255, 192, 203), # Rojo 5 6 7 27 | (7,25,82), #AZUL DE FONDO 28 | (170, 226, 255,),#CELESTE DE LINEA 29 | (0, 128, 255) #AZUl marino 30 | ] 31 | 32 | 33 | 34 | 35 | #transforma la lista ed colores en RGB float 36 | colores=[(r / 255., g / 255., b / 255.) for r,g,b in colores] 37 | 38 | -------------------------------------------------------------------------------- /scripts/analisis/app/main.log: -------------------------------------------------------------------------------- 1 | This is pdfTeX, Version 3.14159265-2.6-1.40.21 (TeX Live 2020/W32TeX) (preloaded format=pdflatex 2021.3.23) 1 MAR 2024 05:17 2 | entering extended mode 3 | restricted \write18 enabled. 4 | file:line:error style messages enabled. 5 | %&-line parsing enabled. 6 | **d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex 7 | (d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex 8 | LaTeX2e <2020-10-01> patch level 4 9 | L3 programming layer <2021-02-18> 10 | 11 | ! LaTeX Error: File `rbf.cls' not found. 12 | 13 | Type X to quit or to proceed, 14 | or enter new name. (Default extension: cls) 15 | 16 | Enter file name: 17 | d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex:4: Emergency stop. 18 | 19 | 20 | l.4 \usepackage 21 | {amsmath}^^M 22 | *** (cannot \read from terminal in nonstop modes) 23 | 24 | 25 | Here is how much of TeX's memory you used: 26 | 22 strings out of 479014 27 | 674 string characters out of 5863080 28 | 283631 words of memory out of 5000000 29 | 17601 multiletter control sequences out of 15000+600000 30 | 403430 words of font info for 27 fonts, out of 8000000 for 9000 31 | 1141 hyphenation exceptions out of 8191 32 | 32i,0n,39p,151b,13s stack positions out of 5000i,500n,10000p,200000b,80000s 33 | d:/proyectos/BitcoinResearch/BitcoinResearch/scripts/analisis/app/main.tex:4: ==> Fatal error occurred, no output PDF file produced! 34 | -------------------------------------------------------------------------------- /scripts/analisis/blocknotify.py: -------------------------------------------------------------------------------- 1 | # Este script solo envia el hash del bloque nuevo 2 | # al bot en el raspberrypi2 3 | 4 | # El rpi2 corre un servidor API REST que esta escuchando 5 | # la llegada de bloques. esta comunicación no esta cifrada 6 | # y esta limitada para aceptar solo los POST del rpi1 7 | # de la misma manera que rp1 solo acepta solicitudes del rp2 8 | 9 | # Para funcionar debe tener permisos y configuraciones en el 10 | # bitcoin.conf de bitcoin-core 11 | 12 | 13 | import requests 14 | import sys 15 | import os # Importa el módulo os 16 | 17 | def send_block_notification(block_hash): 18 | url = "http://nodetwo.local:8000/notify" 19 | headers = { 20 | "accept": "application/json", 21 | "Content-Type": "application/json" 22 | } 23 | data = { 24 | "hash": block_hash 25 | } 26 | try: 27 | response = requests.post(url, json=data, headers=headers) 28 | response.raise_for_status() # Lanza una excepción si la respuesta tiene un código de estado de error 29 | except requests.exceptions.RequestException: 30 | os._exit(1) # Sale del script con un código de salida de 1 31 | 32 | print("Notification sent successfully") 33 | 34 | def main(): 35 | if len(sys.argv) != 2: 36 | print("Usage: python3 test.py ") 37 | os._exit(1) # Sale del script con un código de salida de 1 38 | 39 | block_hash = sys.argv[1] 40 | send_block_notification(block_hash) 41 | 42 | if __name__ == "__main__": 43 | main() 44 | -------------------------------------------------------------------------------- /scripts/analisis/app/readata.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.dates as mdates 5 | 6 | 7 | #os.chdir('D:/proyectos/BitcoinResearch/BitcoinResearch/scripts/') 8 | os.chdir('~/BitcoinResearch/scripts/') 9 | #os.chdir('/home/richard/TRABAJO/BitcoinResearch/scripts/') 10 | 11 | 12 | def leer_data(*args): 13 | aux = np.load('bins/database.npz', allow_pickle='TRUE') 14 | if len(args) == 1: 15 | return aux[args[0]] 16 | else: 17 | return [aux[arg] for arg in args] 18 | 19 | def estado_data(): 20 | aux = np.load('bins/database.npz', allow_pickle='TRUE') 21 | last_n_block = aux['n_block'][-1] 22 | variables = list(aux.files) 23 | return print('Last block: '+str(int(last_n_block))+'\nVariables :'+', '.join(variables)) 24 | 25 | def time_data(time_b): 26 | time_b = mdates.date2num(pd.to_datetime(time_b).date) 27 | return time_b 28 | 29 | 30 | def bitcoins_emitidos(num=np.load('bins/database.npz', allow_pickle='TRUE')['n_block'][-1]): 31 | num_bloque=int(num) 32 | # Recompensa inicial por bloque 33 | recompensa_inicial = 50 34 | # Número de bloques entre halvings 35 | bloques_por_halving = 210000 36 | 37 | # Determinar cuántos halvings han ocurrido 38 | num_halvings = num_bloque // bloques_por_halving 39 | 40 | # Calcular la recompensa actual por bloque 41 | recompensa_actual = recompensa_inicial / (2 ** num_halvings) 42 | 43 | # Calcular el total de bitcoins emitidos hasta el bloque dado 44 | bitcoins_total = 0 45 | for i in range(num_halvings + 1): 46 | bloques_en_este_periodo = min(bloques_por_halving, num_bloque - bloques_por_halving * i) 47 | bitcoins_total += bloques_en_este_periodo * (recompensa_inicial / (2 ** i)) 48 | return bitcoins_total 49 | 50 | def punto_halv(b=np.load('bins/database.npz', allow_pickle='TRUE')['n_block'][-1]): 51 | bloque = int(b) 52 | bloques_por_halving = 210000 53 | halving_completos = bloque // bloques_por_halving 54 | residuo = bloque % bloques_por_halving 55 | fraccion = residuo / bloques_por_halving 56 | return halving_completos + fraccion 57 | 58 | def last_block(): 59 | aux = np.load('bins/database.npz', allow_pickle='TRUE') 60 | last_b = aux['n_block'][-1] 61 | return int(last_b) 62 | 63 | if __name__ == '__main__': 64 | print(last_block()) 65 | -------------------------------------------------------------------------------- /scripts/analisis/actualizar_db.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import datetime 4 | import requests 5 | import numpy as np 6 | import pandas as pd 7 | from time import sleep 8 | from dotenv import load_dotenv 9 | from bitcoinrpc.authproxy import AuthServiceProxy 10 | #esta libreria se carga desde el otro script que invoca 11 | #por eso lleva app.readata 12 | from app.readata import time_data, leer_data 13 | 14 | os.chdir('/home/ghost/BitcoinResearch/scripts/') 15 | 16 | 17 | print('Actualizando la DB') 18 | 19 | load_dotenv('bins/.env') 20 | 21 | user = os.getenv('user') 22 | passw = os.getenv('pass') 23 | 24 | aux = np.load('bins/database.npz', allow_pickle='TRUE') 25 | n_block = aux['n_block'] 26 | time_b = aux['time_b'] 27 | size = aux['size'] 28 | ntx = aux['ntx'] 29 | bits = aux['bits'] 30 | chainwork = aux['chainwork'] 31 | strippedsize = aux['strippedsize'] 32 | weight = aux['weight'] 33 | total = aux['total'] 34 | flag = True 35 | print(f'last block en db {int(n_block[-1])}') 36 | # conectarse al nodo 37 | while(flag): 38 | try: 39 | print('Conectando al nodo ... ') 40 | node = AuthServiceProxy("http://%s:%s@nodeone.local:8332" % (user,passw)) 41 | print('nodo conectado') 42 | last_block = node.getblockcount() 43 | last_db_block = int(aux['n_block'][-1]) 44 | print(f'last block en red {last_block}') 45 | 46 | if(last_block==last_db_block): 47 | print('Todo sinctronizado') 48 | break 49 | else: pass 50 | while(last_block!=last_db_block): 51 | print('Actualizando ...') 52 | #este bucle recopila rapidamente los valores del DB 53 | print(f'Nuevo bloque encontrado {last_db_block+1}') 54 | hash = node.getblockhash(last_db_block+1) 55 | block = node.getblock(hash) 56 | a_b, b, c, d, e, f, g, h = block['height'], block['time'], block['size'], block['nTx'], block['bits'], block['chainwork'], block['strippedsize'], block['weight'] 57 | n_block=np.append(n_block, a_b) 58 | date = datetime.datetime.utcfromtimestamp(b).strftime('%Y-%m-%d %H:%M:%S') 59 | time_b=np.append(time_b, date) 60 | size=np.append(size, c) 61 | ntx=np.append(ntx, d) 62 | bits=np.append(bits, e) 63 | chainwork=np.append(chainwork, f) 64 | strippedsize=np.append(strippedsize, g) 65 | weight=np.append(weight, h) 66 | last_db_block+=1 67 | last_block = node.getblockcount() 68 | sleep(1) 69 | print(f'Actualizado a {last_block}') 70 | general = node.getblockchaininfo() 71 | total_size = general['size_on_disk'] 72 | print('Guardando DB, no cancele hasta que termine') 73 | np.savez('bins/database.npz', n_block=n_block, time_b=time_b, size=size, ntx=ntx, bits=bits,chainwork=chainwork, strippedsize=strippedsize, weight=weight, total=total_size) 74 | print('Guardado finalizado') 75 | flag=False 76 | except: 77 | # si el nodo no esta funcionando espera 78 | # 5 minutos antes de volver a intentarlo 79 | print('error autentificando al nodo') 80 | print('Probando otra vez en 5 min...') 81 | sleep(300) 82 | -------------------------------------------------------------------------------- /scripts/analisis/timestamp.py: -------------------------------------------------------------------------------- 1 | 2 | import os 3 | import numpy as np 4 | import pandas as pd 5 | 6 | import matplotlib.pyplot as plt 7 | import matplotlib.dates as mdates 8 | from matplotlib import font_manager as fm 9 | 10 | from PIL import Image 11 | from datetime import datetime 12 | from app.styles import Estilos, colores 13 | from app.readata import leer_data, time_data 14 | 15 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 16 | prop = fm.FontProperties(fname=fpath) 17 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 18 | title = fm.FontProperties(fname=fpatht) 19 | fname = os.path.split(fpath)[1] 20 | 21 | 22 | tipo = 'estilo_dark' 23 | 24 | fig, ax = plt.subplots(figsize=(20, 5), dpi=200) 25 | 26 | fig.patch.set_facecolor(Estilos[tipo][1]) 27 | ax.patch.set_facecolor(Estilos[tipo][1]) 28 | preferencias = {'color': Estilos[tipo][0], 'fontproperties': prop} 29 | 30 | plt.suptitle("Timestamp of Bitcoin\nBlock Arrivals",fontsize=45,x=0.25,y=1.4,fontproperties=title,color=Estilos[tipo][0]) 31 | time,n_block = leer_data('time_b','n_block') 32 | n_block=n_block[1:] 33 | #time_b = time_data(time)[1:] 34 | diferencias = pd.Series(pd.to_datetime(time)).diff().dt.total_seconds().dropna()/60 35 | 36 | A_p = np.where(diferencias.values >= 0)[0] 37 | A_n = np.where(diferencias.values < 0)[0] 38 | A_p = A_p[1:] 39 | A_n = A_n[1:] 40 | 41 | d_max = diferencias[diferencias.idxmax()] 42 | d_min = diferencias[diferencias.idxmin()] 43 | 44 | ax.xaxis.set_tick_params(labelsize=18, rotation=20,color='w') 45 | ax.yaxis.set_tick_params(labelsize=13, rotation=10,color='w') 46 | ax.tick_params(axis='both', labelcolor='w') 47 | 48 | ax.axvline(n_block[210000*1],ymax=.5, color=Estilos[tipo][0], linestyle='--', linewidth=1) 49 | ax.text(n_block[210000*1+50000],-400, '1st\nHalv', ha='right', va='center', size=18,**preferencias) 50 | ax.axvline(n_block[210000*2],ymax=.5,linestyle='--', linewidth=1,color=Estilos[tipo][0]) 51 | ax.text(n_block[210000*2+50000],-400, '2nd\nHalv',ha='right', va='center', size=18,**preferencias) 52 | ax.axvline(n_block[210000*3],ymax=.5,color=Estilos[tipo][0], linestyle='--', linewidth=1) 53 | ax.text(n_block[210000*3+50000],-400, '3rd\nHalv', ha='right', va='center', size=18,**preferencias) 54 | 55 | ax.axhline(0,color=Estilos[tipo][0],linewidth=1.5,zorder=3) 56 | 57 | ax.spines['top'].set_visible(False) 58 | ax.spines['right'].set_visible(False) 59 | #ax.spines['bottom'].set_visible(False) 60 | 61 | for spine in ax.spines.values(): 62 | spine.set_color(Estilos[tipo][0]) 63 | 64 | 65 | ax.set_ylabel('Block arrival times [min]',fontsize=18,**preferencias) 66 | ax.set_xlabel('\n# Block',fontsize=18,**preferencias) 67 | ax.set_ylim(-1400, 1600) 68 | ax.set_yticks(range(-200, 1601, 400)) 69 | 70 | ax.plot(n_block[A_p],diferencias.values[A_p],color=colores[9]) 71 | ax.plot(n_block[A_n],diferencias.values[A_n],color=colores[7]) # ,s=0.1) 72 | 73 | d_max = diferencias[diferencias.idxmax()] 74 | d_min = diferencias[diferencias.idxmin()] 75 | 76 | ax.scatter(n_block[diferencias.idxmax()], d_max,color=colores[9],s=300) 77 | ax.scatter(n_block[diferencias.idxmax()], d_max,color=colores[8],s=100) 78 | ax.scatter(n_block[diferencias.idxmax()], d_max,color='r',s=50) 79 | 80 | ax.scatter(n_block[diferencias.idxmin()], d_min,color='w',s=300) 81 | ax.scatter(n_block[diferencias.idxmin()], d_min,color=colores[8],s=100) 82 | ax.scatter(n_block[diferencias.idxmin()], d_min,color='r',s=50) 83 | 84 | mss = f'The ATH (All-Time High) for\nthe arrival time of a block\nwas {round(diferencias.max()/60,2)} hours ({round(diferencias.max()/(24*60),2)} day)\nin block {round(n_block[diferencias.idxmax()])}' 85 | ax.text(200000,1400,mss, color=Estilos[tipo][0], ha='right', va='center',size=15) 86 | 87 | 88 | mss = f"The ATL (All-Time Low) for\nthe block arrival time was\n{round(60*diferencias.min())} seconds in block {round(n_block[diferencias.idxmin()])}\ndue to timestamp issues" 89 | ax.text(180000,-800,mss, color=Estilos[tipo][0], ha='right', va='center',size=15) 90 | 91 | 92 | minutos = round(diferencias[420000:].mean(),2) 93 | segundos = int((minutos-int(minutos)) * 60) # Convertir la parte decimal a segundos 94 | 95 | mss = f'The average block arrival time\nover the last two halvings is:\n' 96 | mss2 = f'{int(minutos)} minutes and {segundos} seconds' 97 | ax.text(650000,700,mss, color=Estilos[tipo][0], ha='right', va='center',size=18) 98 | ax.text(650000,400,mss2, color=Estilos[tipo][0], ha='right', va='center',size=22) 99 | 100 | 101 | if tipo[7:8] == 'd': 102 | tw1 = Image.open('bins/br_w.png') 103 | else: 104 | tw1 = Image.open('bins/br_d.png') 105 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) 106 | tw1_array = np.array(tw1_resized) 107 | # Reduce el tamaño de la imagen a la mitad 108 | fig.figimage(tw1_array, xo=2900, yo=1250, alpha=0.55, zorder=1) 109 | plt.savefig('analisis/resultados/timestamp_'+tipo + 110 | '.png', bbox_inches='tight', pad_inches=0.75) 111 | -------------------------------------------------------------------------------- /scripts/analisis/block_size.py: -------------------------------------------------------------------------------- 1 | # este script construye la gráfica histórica 2 | # del tamaño de bloques en Bitcoin 3 | 4 | # este script construye un gráfico de la evolución del tamaño de bloques 5 | # a lo largo del cada bloque 6 | 7 | # librerias a usar 8 | import os 9 | from datetime import datetime 10 | import numpy as np 11 | import matplotlib.pyplot as plt 12 | import matplotlib.dates as mdates 13 | from matplotlib import font_manager as fm 14 | from PIL import Image 15 | from app.styles import Estilos, colores 16 | from app.readata import leer_data, time_data 17 | 18 | # Cambiar la tipografia 19 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 20 | prop = fm.FontProperties(fname=fpath) 21 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 22 | title = fm.FontProperties(fname=fpatht) 23 | fname = os.path.split(fpath)[1] 24 | 25 | 26 | def crear_imagen(tipo='estilo_dark'): 27 | # Color del fondo 28 | fig, ax = plt.subplots(figsize=(13, 5), dpi=200) 29 | fig.patch.set_facecolor(Estilos[tipo][1]) 30 | ax.patch.set_facecolor(Estilos[tipo][1]) 31 | 32 | preferencias = {'color': Estilos[tipo][0], 'fontproperties': prop} 33 | plt.suptitle("Bitcoin Block Size\nHistory 2009-2023", fontsize=40, 34 | x=0.2, y=1.3, fontproperties=title, color=Estilos[tipo][0]) 35 | size, time = leer_data('size', 'time_b') 36 | size = np.array(size)/1000000 37 | 38 | time = time_data(time) 39 | total = np.cumsum(size) 40 | 41 | ax.axhline(1, color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 42 | ax.axhline(4, color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 43 | 44 | ax.plot(time, size, color=colores[3]) 45 | ax.plot(time, total/100000, '-', color=colores[6], linewidth=5) 46 | ax.plot(time, total/100000, '-', color=colores[5], linewidth=2.5) 47 | ax.plot(time, total/100000, '-', color=colores[0], linewidth=.5) 48 | ax.spines['top'].set_visible(False) 49 | ax.spines['right'].set_visible(False) 50 | 51 | locator = mdates.MonthLocator(interval=17) 52 | formatter = mdates.DateFormatter('%b\n%Y') 53 | ax.xaxis.set_major_locator(locator) 54 | ax.xaxis.set_major_formatter(formatter) 55 | ax.xaxis.set_tick_params(labelsize=15, rotation=30) 56 | ax.tick_params(axis='both', colors=Estilos[tipo][0]) 57 | ax.set_ylabel('Size per Block\n', fontsize=25, **preferencias) 58 | ax.set_yticks([0, 1, 2, 3, 4]) 59 | ytick_labels = ['0 MB', '1 MB', '2 MB', '3 MB', '4 MB'] 60 | ax.set_yticklabels(ytick_labels, fontsize=18) 61 | ax.tick_params(axis='both', length=5, width=3) 62 | 63 | date = datetime(2017, 7, 24) 64 | x_value = mdates.date2num(date) 65 | ax.scatter(x_value, 2.5, s=350, color=colores[3]) 66 | ax.scatter(x_value, 2.5, s=100, color=colores[8]) 67 | ax.scatter(x_value, 2.5, s=5, color=colores[5]) 68 | ax.vlines(x_value, 0, 2.275, colors=Estilos[tipo][0], linestyles='dashed') 69 | date = datetime(2017, 1, 24) 70 | x_value = mdates.date2num(date) 71 | ax.text(x_value, 3.2, 'Segwit\nactivate in\nBlock 481824', 72 | color=Estilos[tipo][0], ha='right', va='center', size=18) 73 | 74 | date = datetime(2022, 12, 14) 75 | x_value = mdates.date2num(date) 76 | ax.scatter(x_value, 5.5, s=350, color=colores[3]) 77 | ax.scatter(x_value, 5.5, s=100, color=colores[8]) 78 | ax.scatter(x_value, 5.5, s=5, color=colores[5]) 79 | ax.vlines(x_value, 0, 5.28, colors=Estilos[tipo][0], linestyles='dashed') 80 | ax.text(x_value, 6.25, 'Ordinals\nBRC-20', 81 | color=Estilos[tipo][0], ha='right', va='center', size=18) 82 | 83 | date = datetime(2021, 11, 14) 84 | x_value = mdates.date2num(date) 85 | ax.scatter(x_value, 4.5, s=350, color=colores[3]) 86 | ax.scatter(x_value, 4.5, s=100, color=colores[8]) 87 | ax.scatter(x_value, 4.5, s=5, color=colores[5]) 88 | ax.vlines(x_value, 0, 4.25, colors=Estilos[tipo][0], linestyles='dashed') 89 | 90 | date = datetime(2021, 3, 1) 91 | x_value = mdates.date2num(date) 92 | ax.text(x_value, 5, 'Taproot\nactivate in\nBlock 709632', 93 | color=Estilos[tipo][0], ha='right', va='center', size=18) 94 | 95 | date = datetime(2010, 5, 22) 96 | x_value = mdates.date2num(date) 97 | ax.scatter(x_value, 1.5, s=350, color=colores[3]) 98 | ax.scatter(x_value, 1.5, s=100, color=colores[8]) 99 | ax.scatter(x_value, 1.5, s=5, color=colores[5]) 100 | ax.vlines(x_value, 0, 1.28, colors=Estilos[tipo][0], linestyles='dashed') 101 | date = datetime(2011, 1, 22) 102 | x_value = mdates.date2num(date) 103 | ax.text(x_value, 2.25, 'Bitcoin\nPizza Day', 104 | color=Estilos[tipo][0], ha='right', va='center', size=18) 105 | 106 | 107 | # ax.set_yticklabels 108 | ax2 = ax.twinx() 109 | # Establecer límites del segundo eje Y 110 | ax2.set_yticks([0, 1, 2, 3, 4]) 111 | 112 | last_block = leer_data('n_block')[-1] 113 | total_bd = 'Through Block '+str(round(last_block))+'\nthe total blockchain\nsize is '+str( 114 | round(leer_data('total')/1_000_000_000, 2))+' GB\n' 115 | ytick_labels = ['', '', '', '', total_bd] 116 | ax2.set_yticklabels(ytick_labels, fontsize=18, color=Estilos[tipo][0]) 117 | 118 | for spine in ax2.spines.values(): 119 | spine.set_color(Estilos[tipo][0]) 120 | 121 | if tipo[7:8] == 'd': 122 | tw1 = Image.open('bins/br_w.png') 123 | else: 124 | tw1 = Image.open('bins/br_d.png') 125 | 126 | # Reduce el tamaño de la imagen a la mitad 127 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) 128 | 129 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 130 | tw1_array = np.array(tw1_resized) 131 | 132 | ax2.spines['top'].set_visible(False) 133 | ax2.spines['right'].set_visible(False) 134 | ax2.tick_params(axis='y', length=0) 135 | # Añade la imagen a la figura 136 | fig.figimage(tw1_array, xo=2600, yo=1100, alpha=0.55, zorder=1) 137 | plt.savefig('analisis/resultados/blocksize_'+tipo + 138 | '.png', bbox_inches='tight', pad_inches=0.75) 139 | 140 | 141 | crear_imagen('estilo_dark') 142 | # for a in Estilos.keys(): 143 | # crear_imagen(a) 144 | -------------------------------------------------------------------------------- /scripts/analisis/btcsupply.py: -------------------------------------------------------------------------------- 1 | """ INFO 2 | Para calcular cuanto bitcoin será emitido con precisión 3 | usamos la regla: 4 | -Recompensa Inicial > 50 btc 5 | -Cada 210 000 bloques se reduce a la mitad 6 | -La mínima unidad es satoshi 1 btc = 100 000 000 sats 7 | la recompensa inicial son 50*10^8 sats (50 btc) 8 | """ 9 | 10 | # librerias a usar 11 | import os 12 | import numpy as np 13 | from datetime import datetime 14 | import matplotlib.pyplot as plt 15 | import matplotlib.dates as mdates 16 | import matplotlib.image as mpimg 17 | from matplotlib import font_manager as fm 18 | #from matplotlib.offsetbox import OffsetImage, AnnotationBbox 19 | from PIL import Image 20 | from app.styles import Estilos, colores 21 | from app.readata import bitcoins_emitidos,punto_halv, last_block 22 | 23 | #os.chdir('D:\proyectos\BitcoinResearch\BitcoinResearch\scripts') 24 | # Cambiar la tipografia 25 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 26 | prop = fm.FontProperties(fname=fpath) 27 | 28 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 29 | title = fm.FontProperties(fname=fpatht) 30 | 31 | fname = os.path.split(fpath)[1] 32 | 33 | 34 | 35 | 36 | 37 | # Se va a calcular la cantidad de bitcoin que se va a emitir 38 | # por cada halving hasta que la recompensa sea 1 satoshi 39 | 40 | #Al inicio, el premio era de 50 btc, se convierten a satoshis 41 | #reward = 50e8 42 | 43 | #el halving se da cada 210k bloques 44 | # limit = 210_000 45 | # halv = [] 46 | # supply = [] 47 | 48 | # btc = 0 49 | # h=0 50 | # rew=[] 51 | # while reward >= 1: 52 | # cnt = 1 53 | # while cnt<=limit: 54 | # #print(limit) 55 | # cnt+=1 56 | # btc+=reward 57 | # h+=1 58 | # rew.append(reward) 59 | # reward//=2 60 | # halv.append(h) 61 | # supply.append(btc/1e8) 62 | 63 | 64 | #el método anterior se puede resumir en tres lineas 65 | 66 | rewards = [50e8 // (2**i) for i in range(50) if 50e8 // (2**i) >= 1] 67 | halvings = list(range(1, len(rewards) + 1)) 68 | supply = [sum(rewards[:i+1]) * 210_000 / 1e8 for i in range(len(rewards))] 69 | 70 | 71 | def crear_imagen(tipo='estilo_dark'): 72 | fig, ax = plt.subplots(figsize=(13,5), dpi=200) 73 | fig.patch.set_facecolor(Estilos[tipo][1]) 74 | ax.patch.set_facecolor(Estilos[tipo][1]) 75 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 76 | plt.suptitle("Bitcoin Supply\n2009 - 2140", fontsize=45,x=0.1,y=1.4,fontproperties=title,color=Estilos[tipo][0]) 77 | ax.set_ylabel('BTC Supply', fontsize=25,**preferencias) 78 | ax.set_xlabel('Halving', fontsize=25,**preferencias) 79 | ax.tick_params(axis='both',colors=Estilos[tipo][0]) 80 | 81 | 82 | ax.plot(halvings[:],supply[:],color=colores[6],linewidth=7.5) 83 | ax.plot([0,1,2,3,punto_halv()],[0,supply[0],supply[1],supply[2],bitcoins_emitidos()],color=colores[5],linewidth=5) 84 | ax.plot([0,1,2,3,punto_halv()],[0,supply[0],supply[1],supply[2],bitcoins_emitidos()],color=colores[0],linewidth=1) 85 | 86 | 87 | 88 | ax.set_yticks(supply[:5]+[supply[-1]]) 89 | ytick_labels = ['50% ','75% ','87.5% ','','','100% '] 90 | ax.set_yticklabels(ytick_labels,fontsize=15) 91 | 92 | 93 | ax.set_xticks([1,2,3,4]) 94 | xtick_labels = ['1st Halv\n28/12/2012','2nd Halv\n9/8/2016','3rd Halv\n11/6/2020','4th Halv\nEstimated:\n~20/04/2024'] 95 | ax.set_xticklabels(xtick_labels,fontsize=15) 96 | 97 | ax.tick_params(axis='y', length=0) 98 | ax.spines['top'].set_visible(False) 99 | ax.spines['right'].set_visible(False) 100 | 101 | 102 | ax.text(3,23e6,"For Block "+str(last_block())+" we've reached\n"+str(round(100*(punto_halv()%1),2))+"% completion of the 4th\nhalving cicle, emitting\n"+str(round(bitcoins_emitidos(),5))+' $btc', fontsize=15, **preferencias) 103 | 104 | ax.text(4.5,11e6,"As of Block 6'930'000 a\ntotal of "+str(supply[-1])+" $btc\nwill have been issued (~21M $btc)\nduring 33 halvings until the\nestimated year of 2140.", fontsize=15, **preferencias) 105 | 106 | 107 | for spine in ax.spines.values(): 108 | spine.set_color(Estilos[tipo][0]) 109 | 110 | ax.plot([0, 1], [supply[0],supply[0]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 111 | ax.plot([0, 2], [supply[1],supply[1]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 112 | ax.plot([0, 3], [supply[2],supply[2]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 113 | ax.plot([0, 6], [supply[-1],supply[-1]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 114 | ax.plot([1, 1], [0,supply[0]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 115 | ax.plot([2, 2], [0,supply[1]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 116 | ax.plot([3, 3], [0,supply[2]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 117 | ax.plot([4, 4], [0,supply[3]], color=Estilos[tipo][0], linestyle='dashed', linewidth=1) 118 | 119 | 120 | ax.scatter([1,2,3,4],supply[:4],color=colores[6],s=100,zorder= 2) 121 | ax.scatter([1,2,3,4],supply[:4],color=colores[5],s=30,zorder = 2) 122 | ax.scatter([1,2,3,4],supply[:4],color=colores[0],s=10,zorder = 2) 123 | 124 | ax.scatter([punto_halv()],[bitcoins_emitidos()],color=colores[2],s=200,zorder= 2) 125 | ax.scatter([punto_halv()],[bitcoins_emitidos()],color=colores[0],s=50,zorder = 2) 126 | ax.scatter([punto_halv()],[bitcoins_emitidos()],color=colores[1],s=15,zorder = 2) 127 | 128 | ax.set_xlim(0,6) 129 | ax.set_ylim(0,21e6) 130 | 131 | if tipo[7:8]=='d': 132 | tw1 = Image.open('bins/br_w.png') 133 | else: 134 | tw1 = Image.open('bins/br_d.png') 135 | 136 | 137 | tw1_resized = tw1.resize((int(tw1.width * 0.40), int(tw1.height * 0.40))) # Reduce el tamaño de la imagen a la mitad 138 | 139 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 140 | tw1_array = np.array(tw1_resized) 141 | 142 | 143 | 144 | fig.figimage(tw1_array, xo=2600, yo=1250, alpha=0.55, zorder=2) 145 | 146 | 147 | 148 | plt.savefig('analisis/resultados/supply_btc_'+tipo+'.png',bbox_inches='tight',pad_inches=0.75) 149 | 150 | # for a in Estilos.keys(): 151 | # crear_imagen(a) 152 | crear_imagen('estilo_dark') -------------------------------------------------------------------------------- /scripts/analisis/price-hashrate.py: -------------------------------------------------------------------------------- 1 | # Importando las bibliotecas necesarias 2 | import yfinance as yf 3 | import pandas as pd 4 | 5 | import os 6 | import numpy as np 7 | import matplotlib.pyplot as plt 8 | import matplotlib.dates as mdates 9 | from matplotlib import font_manager as fm 10 | from PIL import Image 11 | from app.styles import Estilos, colores 12 | from app.readata import leer_data,time_data 13 | from datetime import datetime 14 | 15 | 16 | # Definir el ticker para Bitcoin 17 | ticker = 'BTC-USD' 18 | # Crear el objeto de datos 19 | btc_data = yf.download(ticker) 20 | 21 | # Extraer las fechas (índice) y el precio de cierre 22 | fecha_p = btc_data.index.strftime('%Y-%m-%d').to_numpy() 23 | precio = np.round(btc_data['Close'].to_numpy(), 2) 24 | 25 | chainw, timestamp = leer_data('chainwork','time_b') 26 | 27 | # procesamos los datos del tiempo para obtener en segundos 28 | time = np.array(timestamp, dtype='datetime64[D]') 29 | time_block = pd.Series(pd.to_datetime(timestamp)).diff().dt.total_seconds().dropna().replace(0, 1) 30 | 31 | window=4*2016 32 | chainwork = pd.Series([int(a,16) for a in chainw]).diff().dropna() 33 | 34 | hashrate = pd.Series([(chainwork[a]/1e18)/time_block[a] for a in range(1,len(time_block)+1)]) 35 | hashrate_smoothed = hashrate.rolling(window).median().fillna(np.mean(hashrate[:window])) 36 | 37 | df = pd.DataFrame({ 38 | 'date': time[1:], 39 | 'hash_rate': hashrate_smoothed 40 | }) 41 | 42 | df_grouped = df.groupby('date')['hash_rate'].mean().reset_index() 43 | 44 | fecha_hr = df_grouped['date'].to_numpy().astype('datetime64[D]').astype(str) 45 | hashr = df_grouped['hash_rate'].to_numpy() 46 | 47 | df_precio = pd.DataFrame({'precio': precio}, index=pd.to_datetime(fecha_p)) 48 | df_hash_r = pd.DataFrame({'hash_rate': hashr}, index=pd.to_datetime(fecha_hr)) 49 | 50 | fecha_inicio = min(df_precio.index.min(), df_hash_r.index.min()) 51 | fecha_fin = max(df_precio.index.max(), df_hash_r.index.max()) 52 | fechas_completas = pd.date_range(start=fecha_inicio, end=fecha_fin) 53 | 54 | df_precio_reindexado = df_precio.reindex(fechas_completas, fill_value=0) 55 | df_hash_r_reindexado = df_hash_r.reindex(fechas_completas, fill_value=hashrate_smoothed.iloc[-1]) # O el valor que consideres lógico 56 | 57 | fecha_array = df_precio_reindexado.index.to_numpy() 58 | 59 | # Convertir las columnas de precios y hash rate a arrays de NumPy 60 | precio_array = df_precio_reindexado['precio'].to_numpy() 61 | hash_array = df_hash_r_reindexado['hash_rate'].to_numpy() 62 | # Asegurarse de que todas las fechas estén en formato string si es necesario 63 | fecha_array = time_data(fecha_array.astype('datetime64[D]').astype(str)) 64 | 65 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 66 | prop = fm.FontProperties(fname=fpath) 67 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 68 | title = fm.FontProperties(fname=fpatht) 69 | fname = os.path.split(fpath)[1] 70 | 71 | tipo='estilo_dark' 72 | 73 | fig, ax = plt.subplots(figsize=(18,5), dpi=200) 74 | fig.patch.set_facecolor(Estilos[tipo][1]) 75 | ax.patch.set_facecolor(Estilos[tipo][1]) 76 | 77 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 78 | plt.suptitle("Price and Hashrate\n Dynamics in Bitcoin",fontsize=45,x=0.3,y=1.45,fontproperties=title,color=Estilos[tipo][0]) 79 | 80 | locator = mdates.MonthLocator(interval=10) 81 | formatter = mdates.DateFormatter('%B\n%Y') 82 | ax.xaxis.set_major_locator(locator) 83 | ax.xaxis.set_major_formatter(formatter) 84 | ax.xaxis.set_tick_params(labelsize=13, rotation=30,length=5,width=3) 85 | ax.tick_params(axis='both',colors=Estilos[tipo][0]) 86 | ax.set_ylabel('Normalized Scale\n', fontsize=18,**preferencias) 87 | 88 | # Normalizar precios 89 | precio_min = np.min(precio_array) 90 | precio_max = np.max(precio_array) 91 | precio_n = (precio_array - precio_min) / (precio_max - precio_min) 92 | 93 | # Normalizar hash rate 94 | hash_min = np.min(hash_array) 95 | hash_max = np.max(hash_array) 96 | hash_n = (hash_array - hash_min) / (hash_max - hash_min) 97 | 98 | 99 | ax.plot(fecha_array[360*6:-1],precio_n[360*6:-1],color=colores[3],label='Precio BTC') 100 | ax.plot(fecha_array[360*6:-1],hash_n[360*6:-1],color=colores[5],label='*Hash-Rate') 101 | 102 | 103 | legend = ax.legend(fontsize=18,loc='center', bbox_to_anchor=(0.12, 1)) 104 | 105 | legend.get_frame().set_facecolor('#071952') 106 | for text in legend.get_texts(): 107 | text.set_color('w') 108 | 109 | date = datetime(2016,6,1) 110 | 111 | 112 | x_value = mdates.date2num(date) 113 | ax.text(x_value,.8, '*Moving Average\nOver 2016 Blocks',color=Estilos[tipo][0], ha='right', va='center', size=15) 114 | 115 | 116 | 117 | ax.spines['top'].set_visible(False) 118 | ax.spines['right'].set_visible(False) 119 | 120 | for spine in ax.spines.values(): 121 | spine.set_color(Estilos[tipo][0]) 122 | 123 | indices_cruce = np.where(np.diff(np.sign(precio_n[360*6:] - hash_n[360*6:])))[0] 124 | 125 | 126 | for i in indices_cruce[-4:]: 127 | # Usamos i+1 porque el cruce ocurre entre los puntos i y i+1 128 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='y',s=100) 129 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='g',s=30) 130 | 131 | for i in indices_cruce[:2]: 132 | # Usamos i+1 porque el cruce ocurre entre los puntos i y i+1 133 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='y',s=100) 134 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='g',s=30) 135 | 136 | for i in indices_cruce[3:4]: 137 | # Usamos i+1 porque el cruce ocurre entre los puntos i y i+1 138 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='y',s=100) 139 | ax.scatter(fecha_array[360*6:][i+1], precio_n[360*6:][i+1], color='g',s=30) 140 | 141 | 142 | 143 | date = datetime(2016, 7, 9) 144 | x_value = mdates.date2num(date) 145 | ax.vlines(x_value, 0,.5, colors=Estilos[tipo][0], linestyles='dashed') 146 | date = datetime(2016,5,9) 147 | x_value = mdates.date2num(date) 148 | ax.text(x_value, .5, '2nd\nHalv',color=Estilos[tipo][0], ha='right', va='center', size=13) 149 | 150 | 151 | date = datetime(2020,5,11) 152 | x_value = mdates.date2num(date) 153 | ax.vlines(x_value, 0,.5, colors=Estilos[tipo][0], linestyles='dashed') 154 | date = datetime(2020,1,11) 155 | x_value = mdates.date2num(date) 156 | ax.text(x_value, .5, '3rd\nHalv',color=Estilos[tipo][0], ha='right', va='center', size=13) 157 | 158 | if tipo[7:8] == 'd': 159 | tw1 = Image.open('bins/br_w.png') 160 | else: 161 | tw1 = Image.open('bins/br_d.png') 162 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) 163 | tw1_array = np.array(tw1_resized) 164 | # Reduce el tamaño de la imagen a la mitad 165 | fig.figimage(tw1_array, xo=2600, yo=1250, alpha=0.55, zorder=1) 166 | plt.savefig('analisis/resultados/priceHash_'+tipo +'.png', bbox_inches='tight', pad_inches=0.75) -------------------------------------------------------------------------------- /scripts/analisis/hashrate.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Este script calcula y muestra el hashrate de la red Bitcoin 3 | 4 | El hashrate es una variable que indica la potencia de trabajo de la red Bitcoin 5 | La potencia de trabajo es la cantidad de hashes que se pueden realizar en un segundo 6 | y depende del poder de hash de cada minero que esta en la red. 7 | 8 | Este valor no se puede conocer con precision pues depende de factores externos 9 | a la red como el precio de la energia, la cantidad de hashpower de cada minero 10 | y maquina, etc. 11 | 12 | sin embargo se puede inferir este valor a partir del trabajo de la red 13 | y el tiempo de llegada de cada bloque. 14 | 15 | ''' 16 | 17 | # importamos librerias a usar 18 | 19 | import numpy as np 20 | import pandas as pd 21 | import os 22 | from app.readata import leer_data, time_data, last_block 23 | import matplotlib.pyplot as plt 24 | import matplotlib.dates as mdates 25 | from matplotlib import font_manager as fm 26 | from PIL import Image 27 | from datetime import datetime 28 | from app.styles import Estilos, colores 29 | 30 | 31 | # tomamos los valores de la red 32 | chainw, timestamp = leer_data('chainwork','time_b') 33 | 34 | # procesamos los datos del tiempo para obtener en segundos 35 | time = time_data(timestamp[1:]) 36 | time_block = pd.Series(pd.to_datetime(timestamp)).diff().dt.total_seconds().dropna().replace(0, 1) 37 | 38 | window=4*2016 39 | chainwork = pd.Series([int(a,16) for a in chainw]).diff().dropna() 40 | 41 | hashrate = pd.Series([(chainwork[a]/1e18)/time_block[a] for a in range(1,len(time_block)+1)]) 42 | hashrate_smoothed = hashrate.rolling(window).median().fillna(np.mean(hashrate[:window])) 43 | 44 | 45 | # Cambiar la tipografia 46 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 47 | prop = fm.FontProperties(fname=fpath) 48 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 49 | title = fm.FontProperties(fname=fpatht) 50 | fname = os.path.split(fpath)[1] 51 | 52 | def crear_imagen_total(tipo='estilo_dark'): 53 | # Color del fondo 54 | fig, ax = plt.subplots(1,2,figsize=(20,5), dpi=200) 55 | fig.patch.set_facecolor(Estilos[tipo][1]) 56 | ax[0].patch.set_facecolor(Estilos[tipo][1]) 57 | ax[1].patch.set_facecolor(Estilos[tipo][1]) 58 | 59 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 60 | 61 | plt.suptitle("Bitcoin\n Hashrate",fontsize=50,y=1.5,x=0.15,fontproperties=title,color=Estilos[tipo][0]) 62 | 63 | 64 | ax[0].plot(time,hashrate_smoothed,color=colores[1],zorder=1,linewidth=7) 65 | ax[0].plot(time,hashrate_smoothed,color=colores[2],zorder=1,linewidth=2) 66 | ax[0].plot(time,hashrate_smoothed,color=colores[3],zorder=1,linewidth=0.5) 67 | 68 | 69 | 70 | #ax[0].set_yscale('log') 71 | locator = mdates.MonthLocator(interval=23) 72 | formatter = mdates.DateFormatter('%b\n%Y') 73 | ax[0].xaxis.set_major_locator(locator) 74 | ax[0].xaxis.set_major_formatter(formatter) 75 | ax[0].xaxis.set_tick_params(labelsize=15, rotation=30,length=5,width=3) 76 | ax[0].tick_params(axis='both',colors=Estilos[tipo][0]) 77 | ax[0].set_ylabel('Hashrate\n', fontsize=25,**preferencias) 78 | ax[0].set_title("Scale:'linear'",loc='right',fontsize=12,color='white') 79 | 80 | ax[0].axhline(hashrate_smoothed.max(),linestyle='dashed',color='red',linewidth=2) 81 | ax[1].axhline(1e18*hashrate_smoothed.max(),linestyle='dashed',color='red',linewidth=1) 82 | 83 | ax[0].axhline(hashrate_smoothed.iloc[-1],linestyle='dashed',color='green',linewidth=2) 84 | ax[1].axhline(1e18*hashrate_smoothed.iloc[-1],linestyle='dashed',color='green',linewidth=1.5) 85 | 86 | 87 | ax[1].plot(time,hashrate_smoothed*1e18,color=colores[1],zorder=1,linewidth=7) 88 | ax[1].plot(time,hashrate_smoothed*1e18,color=colores[2],zorder=1,linewidth=2) 89 | ax[1].plot(time,hashrate_smoothed*1e18,color=colores[3],zorder=1,linewidth=0.5) 90 | 91 | 92 | 93 | ax[1].set_yscale('log') 94 | locator = mdates.MonthLocator(interval=23) 95 | formatter = mdates.DateFormatter('%b\n%Y') 96 | ax[1].xaxis.set_major_locator(locator) 97 | ax[1].xaxis.set_major_formatter(formatter) 98 | ax[1].xaxis.set_tick_params(labelsize=15, rotation=30,length=5,width=3) 99 | ax[1].tick_params(axis='both',colors=Estilos[tipo][0]) 100 | ax[1].set_ylabel('Hashrate\n', fontsize=25,**preferencias) 101 | 102 | ax[1].set_title("Scale:'logy'",loc='right',fontsize=15,color='white') 103 | 104 | ax[0].set_yticks([0,1e2,2e2,3e2,4e2,5e2]) 105 | ytick_labels = ['0 EH\\s','100 EH\\s','200 EH\\s','300 EH\\s','400 EH\\s','500 EH\\s'] 106 | ax[0].set_yticklabels(ytick_labels,rotation=23,**preferencias) 107 | ax[0].yaxis.set_tick_params(labelsize=15) 108 | ax[0].tick_params(axis='y',labelsize=15,rotation=25) # Cambia 20 al tamaño que prefieras 109 | 110 | 111 | ax[1].yaxis.set_tick_params(labelsize=23,rotation=20) 112 | ax[1].tick_params(axis='y',labelsize=15,rotation=20) # Cambia 20 al tamaño que prefieras 113 | 114 | ax[0].spines['top'].set_visible(False) 115 | ax[0].spines['right'].set_visible(False) 116 | 117 | ax[1].spines['top'].set_visible(False) 118 | ax[1].spines['right'].set_visible(False) 119 | 120 | plt.subplots_adjust(wspace=2, hspace=1) 121 | 122 | 123 | for spine in ax[0].spines.values(): 124 | spine.set_color(Estilos[tipo][0]) 125 | for spine in ax[1].spines.values(): 126 | spine.set_color(Estilos[tipo][0]) 127 | 128 | if tipo[7:8]=='d': 129 | tw1 = Image.open('bins/br_w.png') 130 | else: 131 | tw1 = Image.open('bins/br_d.png') 132 | 133 | 134 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) # Reduce el tamaño de la imagen a la mitad 135 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 136 | tw1_array = np.array(tw1_resized) 137 | 138 | date = datetime(2021,5,21) 139 | x_value = mdates.date2num(date) 140 | ax[0].scatter(x_value,350,s=100,color=colores[3]) 141 | ax[0].vlines(x_value,-10,350,colors=Estilos[tipo][0], linestyles='dashed') 142 | 143 | date = datetime(2020,5,21) 144 | x_value = mdates.date2num(date) 145 | mss = 'China ban\nBitcoin mining' 146 | ax[0].text(x_value,400, mss, color=Estilos[tipo][0], ha='right', va='center',size=18) 147 | 148 | date = datetime(2023,10,1) 149 | x_value = mdates.date2num(date) 150 | mss = f'The Hashrate is estimated\nbased on variables such as\ndifficulty and chainwork.\n\nAs of block {round(last_block())} the \nvalue of chainwork is\n' 151 | mss2= r'$2.39\times10^{23}$'+' Hashes' 152 | ax[1].text(x_value,1e12,mss+mss2, color=Estilos[tipo][0], ha='right', va='center',size=18) 153 | 154 | 155 | 156 | 157 | # Usa el índice para obtener la fecha correspondiente 158 | fecha_datetime = datetime.strptime(timestamp[np.argmax(hashrate)][:10],'%Y-%m-%d') 159 | formatted_date = fecha_datetime.strftime('%d of %B %Y') 160 | mss1 = '*Up to block ' + str(last_block())+'\nthe All-Time High\nwas ' 161 | mss2 = str(round(hashrate_smoothed.max(),2))+' TH/s on\n'+str(formatted_date) 162 | 163 | fig.text(0.5,1.15,mss1+mss2, ha='center', va='center', fontsize=20,**preferencias) 164 | 165 | fig.figimage(tw1_array, xo=2800, yo=1200, alpha=0.55, zorder=1) 166 | plt.subplots_adjust(wspace=0.25) 167 | plt.savefig('analisis/resultados/hashrate_'+tipo+'.png',bbox_inches='tight',pad_inches=0.5) 168 | 169 | crear_imagen_total('estilo_dark') 170 | -------------------------------------------------------------------------------- /scripts/analisis/recopilador.py: -------------------------------------------------------------------------------- 1 | """ 2 | # Este script es un ejemplo de como se puede 3 | # recopilar data del bitcoin usando el nodo 4 | """ 5 | # librerias 6 | import os 7 | import datetime 8 | import time 9 | import logging 10 | import sys 11 | import numpy as np 12 | # coneccion con el nodo 13 | from bitcoin.rpc import RawProxy 14 | # para enmascarar tokens 15 | from dotenv import load_dotenv 16 | # diversos 17 | 18 | # ver el estado de un proceso 19 | from tqdm import tqdm 20 | 21 | 22 | # cargamos variables de autentificacion 23 | load_dotenv('/home/ghost/.env') 24 | rpc_user = os.getenv('user') 25 | rpc_password = os.getenv('pass') 26 | 27 | # iniciamos logs 28 | logging.basicConfig(filename='/home/ghost/BitcoinResearch/scripts/bins/recopilador.log', filemode='a+', 29 | format='%(asctime)s,%(message)s,%(levelname)s', datefmt='%d-%b-%y,%H:%M:%S', level=logging.INFO) 30 | logging.info('*****************START***************************') 31 | logging.info('Corriendo script ' +datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')) 32 | 33 | # autentificación 34 | 35 | 36 | def func(): 37 | try: 38 | node = RawProxy(service_url='http://%s:%s@nodeone.local:8332' % 39 | (rpc_user, rpc_password)) 40 | # si todo esta ok y el nodo esta online acepta sesion 41 | logging.info('Autentificando sesion') 42 | except: 43 | # si el nodo esta offline o no se logra autentificar 44 | logging.info('Error de auth sesion') 45 | logging.info('Cerrando Todo') 46 | logging.info('********************************************') 47 | # directamente sale al sistema 48 | sys.exit() 49 | 50 | # Este script reconoce si existen los binarios y base de datos. 51 | # de no ser asi, empieza creandolos. 52 | 53 | if os.path.exists('/home/ghost/BitcoinResearch/scripts/bins/database.npz'): 54 | logging.info('Se detecto la db en bins/database.npz') 55 | aux = np.load('/home/ghost/BitcoinResearch/scripts/bins/database.npz', allow_pickle='TRUE') 56 | n_block = aux['n_block'] 57 | time_b = aux['time_b'] 58 | size = aux['size'] 59 | ntx = aux['ntx'] 60 | bits = aux['bits'] 61 | chainwork = aux['chainwork'] 62 | strippedsize = aux['strippedsize'] 63 | weight = aux['weight'] 64 | else: 65 | logging.info('No existe la db, creando variables') 66 | n_block = np.array([]) 67 | time_b = np.array([]) 68 | size = np.array([]) 69 | ntx = np.array([]) 70 | bits = np.array([]) 71 | chainwork = np.array([]) 72 | strippedsize = np.array([]) 73 | weight = np.array([]) 74 | # blockchain dividimos por lotes. 75 | # se podria ejecutar en cualqueir momento, verifica y reconoce el último dato 76 | # introducido y sigue desde este punto para adelante. 77 | 78 | # Recoge la última altura de bloque ya recopilado 79 | 80 | if any(n_block): 81 | last = n_block[-1] 82 | logging.info('ultimo bloque en db %d', last) 83 | else: 84 | last = 0 85 | logging.info('Iniciando con el bloque 1') 86 | 87 | lote = 10000 # en nuestro caso procesaremos lotes de X bloques 88 | 89 | flag = True 90 | while flag: 91 | try: 92 | # debe realizar el recorrido hasta el último bloque. 93 | n_max = node.getblockcount() 94 | n = (n_max // lote)+1 95 | general = node.getblockchaininfo() 96 | total_size = general['size_on_disk'] 97 | flag = False 98 | except: 99 | time.sleep(3) 100 | try: 101 | logging.info('Error conección server, reconectando... ') 102 | node = RawProxy( 103 | service_url='http://%s:%s@nodeone.local:8332' % (rpc_user, rpc_password)) 104 | # si todo esta ok y el nodo esta online acepta sesion 105 | except: 106 | # si el nodo esta offline o no se logra autentificar 107 | logging.info('Error de auth sesion') 108 | logging.info('Cerrando Todo') 109 | logging.info('********************************************') 110 | # directamente sale al sistema 111 | sys.exit() 112 | 113 | print('Recopilando... TOTAL %d bloques' % n_max) 114 | print('%d Lotes de: %d bloques' % (n-1, lote)) 115 | print('El último bloque en DB: ', int(last)) 116 | 117 | # encontramos el indice de lote donde corresponde la busqueda 118 | if last <= lote: 119 | a = 1 120 | else: 121 | a = (last // lote)+1 122 | 123 | for ix in range(int(a), int(n)+1): 124 | print('-> lote: ', ix-1) 125 | 126 | if ix == n: 127 | stop = n_max 128 | else: 129 | stop = ix*lote 130 | 131 | for n_b in tqdm(range(int(last)+1, stop+1), desc="Recopilando lote: %d" % ix): 132 | # ahora que tenemos el puntero en el bloque correcto empezamos a hacer calls al nodo 133 | # lo q puede fallar por algún x motivo. De ser así vuelve a ejecutar el call 3 seg despúes. 134 | flag = True 135 | while(flag): 136 | try: 137 | hash = node.getblockhash(n_b) 138 | logging.info('nuevo bloque encontrado %d', n_b) 139 | block = node.getblock(hash) 140 | a_b, b, c, d, e, f, g, h = block['height'], block['time'], block['size'], block[ 141 | 'nTx'], block['bits'], block['chainwork'], block['strippedsize'], block['weight'] 142 | n_block = np.append(n_block, a_b) 143 | date = datetime.datetime.utcfromtimestamp( 144 | b).strftime('%Y-%m-%d %H:%M:%S') 145 | time_b = np.append(time_b, date) 146 | size = np.append(size, c) 147 | ntx = np.append(ntx, d) 148 | bits = np.append(bits, e) 149 | chainwork = np.append(chainwork, f) 150 | strippedsize = np.append(strippedsize, g) 151 | weight = np.append(weight, h) 152 | flag = False 153 | except KeyboardInterrupt: 154 | print('\nExiting by user request.\n') 155 | # np.savez('bins/database.npz',n_block=n_block,time_b=time_b,size=size,ntx=ntx,bits=bits,chainwork=chainwork,strippedsize=strippedsize,weight=weight) 156 | logging.info('Forzando salida... ') 157 | logging.info( 158 | '**************** Salida Forzada ****************') 159 | sys.exit(0) 160 | except: 161 | time.sleep(2) 162 | try: 163 | logging.info( 164 | 'Error conección server, reconectando... ') 165 | node = RawProxy( 166 | service_url='http://%s:%s@nodeone.local:8332' % (rpc_user, rpc_password)) 167 | # si todo esta ok y el nodo esta online acepta sesion 168 | except: 169 | # si el nodo esta offline o no se logra autentificar 170 | logging.info('Error de auth sesion') 171 | logging.info('Cerrando Todo') 172 | logging.info( 173 | '********************************************') 174 | # directamente sale al sistema 175 | sys.exit() 176 | 177 | last = stop 178 | 179 | print('LOTE TERMINADO: ', ix) 180 | logging.info('Guardado en DB Last Block %d ', last) 181 | np.savez('/home/ghost/BitcoinResearch/scripts/bins/database.npz', n_block=n_block, time_b=time_b, size=size, ntx=ntx, bits=bits, 182 | chainwork=chainwork, strippedsize=strippedsize, weight=weight, total=total_size) 183 | 184 | logging.info('******************EXIT**************************') 185 | 186 | 187 | if __name__ == '__main__': 188 | func() 189 | -------------------------------------------------------------------------------- /scripts/analisis/app/bot.py: -------------------------------------------------------------------------------- 1 | # este script tiene funciones para analizar 2 | # los datos nuevos y bloques. 3 | 4 | # esta funcion actualiza la base de datos al valor mas actual 5 | # primero se debe correr el scrip 'recopilador.py' para empezar 6 | # desde el bloque 1. Este proceso demora entre 5 a 6 horas 7 | # por lo que esta función actualiza al último bloque. 8 | import os 9 | import datetime 10 | import requests 11 | import numpy as np 12 | import pandas as pd 13 | from time import sleep 14 | from dotenv import load_dotenv 15 | from bitcoinrpc.authproxy import AuthServiceProxy 16 | #esta libreria se carga desde el otro script que invoca 17 | #por eso lleva app.readata 18 | from readata import time_data, leer_data 19 | 20 | os.chdir('D://proyectos//BitcoinResearch//BitcoinResearch//scripts') 21 | #os.chdir('/home/ghost/BitcoinResearch/scripts/') 22 | 23 | load_dotenv('bins/.env') 24 | 25 | rpc_user = os.getenv('user') 26 | rpc_pass = os.getenv('pass') 27 | 28 | 29 | def actualizar_server(user=rpc_user,passw=rpc_pass,db=False): 30 | # este metodo actualiza la db. 31 | print('Actualizando la DB') 32 | aux = np.load('bins/database.npz', allow_pickle='TRUE') 33 | time_b = aux['time_b'] 34 | bits = aux['bits'] 35 | chainwork = aux['chainwork'] 36 | flag = True 37 | print(f'last block en db {aux["n_block"][-1]}') 38 | # conectarse al nodo 39 | if db: 40 | 41 | while(flag): 42 | try: 43 | print('Conectando al nodo ... ') 44 | node = AuthServiceProxy("http://%s:%s@nodeone.local:8332" % (user,passw)) 45 | print('nodo conectado') 46 | last_block = node.getblockcount() 47 | last_db_block = int(aux['n_block'][-1]) 48 | print(f'last block en red {last_block}') 49 | 50 | if(last_block==last_db_block): 51 | print('Todo sinctronizado') 52 | return 53 | else: pass 54 | while(last_block!=last_db_block): 55 | print('Actualizando ...') 56 | #este bucle recopila rapidamente los valores del DB 57 | print(f'Nuevo bloque encontrado {last_db_block+1}') 58 | hash = node.getblockhash(last_db_block+1) 59 | block = node.getblock(hash) 60 | b, e, f = block['time'], block['bits'], block['chainwork'] 61 | date = datetime.datetime.utcfromtimestamp(b).strftime('%Y-%m-%d %H:%M:%S') 62 | time_b=np.append(time_b, date) 63 | bits=np.append(bits, e) 64 | chainwork=np.append(chainwork, f) 65 | last_db_block+=1 66 | last_block = node.getblockcount() 67 | sleep(1) 68 | print(f'Actualizado a {last_block}') 69 | flag=False 70 | 71 | except: 72 | # si el nodo no esta funcionando espera 73 | # 5 minutos antes de volver a intentarlo 74 | print('error autentificando al nodo') 75 | print('Probando otra vez en 5 min...') 76 | sleep(300) 77 | data_metrics(time_b, bits, chainwork) 78 | return time_b, bits, chainwork 79 | 80 | 81 | 82 | def bits_to_difficulty(bits): 83 | bits = int(bits, 16) 84 | # Convertir bits a un número de 256 bits en formato big-endian 85 | target = (bits & 0x007fffff) * 2 ** (8 * ((bits >> 24) - 3)) 86 | # Calcular la dificultad como el cociente entre el objetivo máximo y el objetivo actual 87 | max_target = 0xffff * 2 ** (8 * (0x1d - 3)) 88 | difficulty = max_target / target 89 | return difficulty 90 | 91 | 92 | def data_metrics(time_b,bits,chainw): 93 | #al llamar a esta función los valores ya estaran actualizados. 94 | print('Calculando ATH en database') 95 | difficulty = np.array([bits_to_difficulty(a) for a in bits]) 96 | time_block = pd.Series(pd.to_datetime(time_b)).diff().dt.total_seconds().dropna().replace(0, 1) 97 | window=1008 98 | chainwork = pd.Series([int(a,16) for a in chainw]).diff().dropna() 99 | hashrate = pd.Series([(chainwork[a]/1e18)/time_block[a] for a in range(1,len(time_block)+1)]) 100 | hashrate_smoothed = hashrate.rolling(window).median().fillna(np.mean(hashrate[:window])) 101 | hashrate_ath = hashrate_smoothed.max() 102 | time_ath = time_block.max() 103 | time_ath_halv = time_block[3*210000:].max() 104 | dif_ath = difficulty.max() 105 | np.save('bins/ath_hr.npy',hashrate_ath) 106 | np.save('bins/ath_tt.npy',time_ath) 107 | np.save('bins/ath_th.npy',time_ath_halv) 108 | np.save('bins/ath_d.npy',dif_ath) 109 | np.save('bins/ath_hrs.npy',hashrate_smoothed[-1008:]) 110 | return print('Guardado en disco') 111 | 112 | 113 | def alerta(n=0,data=0): 114 | url = "http://nodetwo.local:8001/notify" 115 | headers = {"accept": "application/json", 116 | "Content-Type": "application/json"} 117 | alerta_tipo = ['hashrate','time_b total','time_b halv','difficulty'] 118 | data = {"tipo": alerta_tipo[n] , "data":data} 119 | try: 120 | response = requests.post(url, json=data, headers=headers) 121 | response.raise_for_status() # Lanza una excepción si la respuesta tiene un código de estado de error 122 | print("Notification sent successfully") 123 | 124 | except requests.exceptions.RequestException: 125 | print('Servidor 2 OFFLINE') 126 | 127 | def analisis(hash=0,user=rpc_user,passw=rpc_pass): 128 | #con el nuevo hash debemos volver a consultar al nodo 129 | print('analizando nuevo bloque') 130 | flag=True 131 | while(flag): 132 | try: 133 | print('Conectando al nodo ... ') 134 | node = AuthServiceProxy("http://%s:%s@nodeone.local:8332" % (user,passw)) 135 | print('nodo conectado') 136 | block = node.getblock(hash) 137 | block_anterior_hash = node.getblockhash(block['height']-1) 138 | block_anterior = node.getblock(block_anterior_hash) 139 | flag=False 140 | except: 141 | # si el nodo no esta funcionando espera 142 | # 5 minutos antes de volver a intentarlo 143 | print('error autentificando al nodo') 144 | print('Probando otra vez en 5 min...') 145 | sleep(120) 146 | aux1 = np.load('bins/ath_hr.npy') 147 | aux2 = np.load('bins/ath_tt.npy') 148 | aux3 = np.load('bins/ath_th.npy') 149 | aux4 = np.load('bins/ath_d.npy') 150 | aux5 = np.load('bins/ath_hrs.npy') 151 | print(aux1,aux2,aux3,aux4) 152 | print('Buscando ATH en bloque...') 153 | # el primer y mas facil de calcular es el tiempo de llegada 154 | # para lo cual recuperamos los valores de tiempo 155 | tiempo_anterior = datetime.datetime.utcfromtimestamp(block_anterior['time']).strftime('%Y-%m-%d %H:%M:%S') 156 | tiempo_nuevo = datetime.datetime.utcfromtimestamp(block['time']).strftime('%Y-%m-%d %H:%M:%S') 157 | tiempo_bloque = pd.Series(pd.to_datetime([tiempo_anterior,tiempo_nuevo])).diff().dt.total_seconds().iloc[-1] 158 | hashrate_new_block = (int(block['chainwork'],16)-int(block_anterior['chainwork'],16))/(1e18*tiempo_bloque) 159 | print(f'hashrate bloque nuevo {hashrate_new_block}') 160 | hashrate_mobil = aux5[-1007:] 161 | #calculamos el hashrate con media movil incluyendo al nuevo dato 162 | hsrt = np.append(hashrate_mobil,hashrate_new_block) 163 | hashrate_new_block = hsrt.mean() 164 | print(f'hashrate promediado {hashrate_new_block}') 165 | hashrate_mobil = np.append(hashrate_mobil,hashrate_new_block) 166 | np.save('bins/ath_hrs.npy',hashrate_mobil) 167 | if(hashrate_new_block>aux1): 168 | print('Nuevo ATH hashrate') 169 | alerta(data=hashrate_new_block,n=0) 170 | else: pass 171 | if(tiempo_bloque>aux2): 172 | print('Nuevo ATH total time') 173 | alerta(n=1,data=tiempo_bloque) 174 | else: pass 175 | if(tiempo_bloque>aux3): 176 | print('Nuevo ATH time halv') 177 | alerta(n=2,data=tiempo_bloque) 178 | else: pass 179 | diff = bits_to_difficulty(block['bits']) 180 | if(diff>aux4): 181 | print('Nuevo ATH diff') 182 | alerta(n=3,data=diff) 183 | else: pass 184 | 185 | print('Sin cambios en ATH') 186 | return print('fin analisis bloque nuevo') 187 | 188 | 189 | if __name__=='__main__': 190 | actualizar_server(user=rpc_user,passw=rpc_pass) 191 | 192 | 193 | -------------------------------------------------------------------------------- /scripts/analisis/ntx.py: -------------------------------------------------------------------------------- 1 | import os 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | import matplotlib.dates as mdates 6 | 7 | from datetime import datetime, timedelta 8 | from app.styles import Estilos, colores 9 | from app.readata import leer_data, last_block 10 | from matplotlib import font_manager as fm 11 | from PIL import Image 12 | 13 | 14 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 15 | prop = fm.FontProperties(fname=fpath) 16 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 17 | title = fm.FontProperties(fname=fpatht) 18 | 19 | fname = os.path.split(fpath)[1] 20 | 21 | 22 | 23 | #=======NTX VS N_BLOCKS 24 | def crear_imagen_total(tipo='estilo_dark'): 25 | fig, ax = plt.subplots(1,2,figsize=(20,5), dpi=200) 26 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 27 | ntx,n_block = leer_data('ntx','n_block') 28 | indice=np.where((ntx==np.max(ntx)))[0][0] 29 | ntx_max=int(ntx[indice]) 30 | fig.patch.set_facecolor(Estilos[tipo][1]) 31 | ax[0].patch.set_facecolor(Estilos[tipo][1]) 32 | ax[1].patch.set_facecolor(Estilos[tipo][1]) 33 | 34 | for spine in ax[0].spines.values(): 35 | spine.set_color(Estilos[tipo][0]) 36 | for spine in ax[1].spines.values(): 37 | spine.set_color(Estilos[tipo][0]) 38 | 39 | 40 | plt.suptitle("Number of\n Transactions",fontsize=50,y=1.5,x=0.25,color=Estilos[tipo][0],fontproperties=title) 41 | if tipo[7:8]=='d': 42 | ax[0].plot(n_block,ntx,alpha=0.8,color=colores[3]) 43 | ax[0].scatter(n_block[indice], ntx_max, color ='white',s=180) 44 | ax[0].scatter(n_block[indice], ntx_max, color ='red',s=50) 45 | ax[0].text(n_block[indice]+100000,ntx_max+2000,f'ATH: {ntx_max} Tx\nin Block {round(n_block[indice])}',color='white', ha='right', va='center',size=18) 46 | 47 | ax[0].spines['top'].set_visible(False) 48 | ax[0].spines['right'].set_visible(False) 49 | 50 | ax[1].spines['top'].set_visible(False) 51 | ax[1].spines['right'].set_visible(False) 52 | 53 | a_np = np.array(ntx) 54 | b_np = np.cumsum(a_np) 55 | ax[1].plot(n_block,b_np,label="# of tx per block",alpha=0.8,color=colores[3]) 56 | ax[1].fill_between(n_block,b_np, color='lightblue', alpha=0.5) 57 | else: 58 | ax[0].plot(n_block,ntx ,label="number of transactions per block",alpha=0.8,color=colores[10]) 59 | ax[0].scatter(n_block[indice], ntx_max, color ='black',label='Máximo', s=40) 60 | ax[0].annotate(f'Max: {int(ntx_max)}', (n_block[indice], ntx_max), xytext=(20, 30), textcoords='offset points', 61 | arrowprops=dict(arrowstyle='->', color='black', linewidth=3), fontsize=18, color='black') 62 | 63 | #VALOR_MAX="{:.2f}".format(np.log(ntx.max)) 64 | 65 | ax[1].plot(n_block,np.log(ntx) ,label="number of transactions per block",alpha=0.8,color=colores[10]) 66 | ax[1].scatter(n_block[indice], np.log(ntx_max), color ='black',label='Máximo', s=40) 67 | ax[1].annotate(f'Max: {np.log(float(ntx_max)):.2f}', (n_block[indice], np.log(ntx_max)), xytext=(20, 30), textcoords='offset points', 68 | arrowprops=dict(arrowstyle='->', color='black', linewidth=3), fontsize=18, color='black') 69 | 70 | 71 | 72 | 73 | ax[0].text(n_block[210000*1]+1.2e5,8000,'1st\nHalv', color=Estilos[tipo][0], ha='right', va='center',size=18) 74 | ax[0].text(n_block[210000*2]+1.2e5,8000,'2nd\nHalv', color=Estilos[tipo][0], ha='right', va='center',size=18) 75 | ax[0].text(n_block[210000*3]+1.2e5,8000,'3rd\nHalv', color=Estilos[tipo][0], ha='right', va='center',size=18) 76 | 77 | 78 | sentence = f"During block {last_block()} there\nwas a total accumulation\nof "+'{:,}'.format(round(max(b_np))).replace(",", "'")+" tx" 79 | ax[1].text(max(n_block)*.8,max(b_np)*1.1,sentence, color=Estilos[tipo][0], ha='right', va='center',size=18) 80 | ax[1].scatter(n_block[-1],b_np[-1], color ='white',s=180) 81 | ax[1].scatter(n_block[-1],b_np[-1], color ='red',s=50) 82 | 83 | ax[0].set_ylabel('# Tx\n', fontsize=25,**preferencias) 84 | ax[0].set_xlabel('Block\n', fontsize=25,**preferencias,labelpad=20) 85 | ax[0].axvline(x=210000,ymax=0.75, color=Estilos[tipo][0], linestyle='--', linewidth=1, zorder=0) 86 | ax[0].axvline(x=210000*2,ymax=0.75,color=Estilos[tipo][0], linestyle='--', linewidth=1) 87 | ax[0].axvline(x=210000*3,ymax=0.75,color=Estilos[tipo][0], linestyle='--', linewidth=1) 88 | ax[0].tick_params(axis='both',colors=Estilos[tipo][0],labelsize=14) 89 | 90 | ax[0].set_xticks([0,1e5,2e5,3e5,4e5,5e5,6e5,7e5,8e5]) 91 | ytick_labels = ['0','100k','200k','300k','400k','500k','600k','700k','800k'] 92 | ax[0].set_xticklabels(ytick_labels,fontsize=18) 93 | ax[0].tick_params(axis='both', length=5,width=3) 94 | 95 | 96 | 97 | ax[1].set_ylabel('Accumulated Tx\n', fontsize=23,**preferencias) 98 | ax[1].set_xlabel('Block\n', fontsize=23,**preferencias,labelpad=20) 99 | 100 | ax[1].set_xticks([0,1e5,2e5,3e5,4e5,5e5,6e5,7e5,8e5]) 101 | ytick_labels = ['0','100k','200k','300k','400k','500k','600k','700k','800k'] 102 | ax[1].set_xticklabels(ytick_labels,fontsize=18) 103 | ax[1].tick_params(axis='both',colors=Estilos[tipo][0],labelsize=14) 104 | 105 | 106 | 107 | 108 | if tipo[7:8]=='d': 109 | tw1 = Image.open('bins/br_w.png') 110 | else: 111 | tw1 = Image.open('bins/br_d.png') 112 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) 113 | tw1_array = np.array(tw1_resized) 114 | fig.figimage(tw1_array, xo=2750, yo=1300, alpha=0.55, zorder=1) 115 | plt.subplots_adjust(wspace=0.4) 116 | 117 | plt.savefig('analisis/resultados/Numero_de_transacciones_'+tipo+'.png',bbox_inches='tight',pad_inches=0.5) 118 | #plt.show() 119 | 120 | 121 | def crear_imagen_h(tipo='estilo_dark'): 122 | 123 | fig, ax = plt.subplots(2,2,figsize=(13,6), dpi=200) 124 | ntx,n_block = leer_data('ntx','n_block') 125 | fig.patch.set_facecolor(Estilos[tipo][1]) 126 | ax[0,0].patch.set_facecolor(Estilos[tipo][1]) 127 | ax[0,1].patch.set_facecolor(Estilos[tipo][1]) 128 | ax[1,0].patch.set_facecolor(Estilos[tipo][1]) 129 | ax[1,1].patch.set_facecolor(Estilos[tipo][1]) 130 | 131 | 132 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 133 | 134 | plt.suptitle("Number of blocks\nper Halving",fontsize=35,x=0.20,y=1.23,**preferencias) 135 | #ntx,n_block = leer_data('ntx','n_block') 136 | 137 | 138 | for spine in ax[0,0].spines.values(): 139 | spine.set_color(Estilos[tipo][0]) 140 | for spine in ax[0,1].spines.values(): 141 | spine.set_color(Estilos[tipo][0]) 142 | for spine in ax[1,0].spines.values(): 143 | spine.set_color(Estilos[tipo][0]) 144 | for spine in ax[1,1].spines.values(): 145 | spine.set_color(Estilos[tipo][0]) 146 | ############################ 147 | ######## 148 | hist, edges = np.histogram(ntx[:210000],bins=50) 149 | 150 | top_10_indices = np.argpartition(hist, -5)[-5:] 151 | 152 | ax[0, 0].bar(range(0,10), hist[:10], color=Estilos[tipo][0], edgecolor='black', width=0.4, align='edge') 153 | 154 | for i in top_10_indices: 155 | freq = hist[i] 156 | label = f'{(freq / np.sum(hist)) * 100:.2f}%' 157 | ax[0, 0].text(i, freq, label, ha='center', va='bottom', fontsize=9, color=Estilos[tipo][0], rotation=0) 158 | 159 | 160 | xticks_positions = [i for i in range(0, 10)] 161 | interval_labels = [f'{int(edges[i])} - {int(edges[i+1])}' for i in range(len(edges)-1)] 162 | ax[0, 0].set_xticks(xticks_positions) 163 | ax[0, 0].set_xticklabels([interval_labels[i] for i in xticks_positions], color=Estilos[tipo][0], rotation=45) 164 | ax[0, 0].tick_params(axis='y', colors=Estilos[tipo][0]) 165 | ax[0, 0].set_ylim(0, 185000) 166 | #________________________ 167 | hist, edges = np.histogram(ntx[210000:2*210000],bins=50) 168 | 169 | top_10_indices = np.argpartition(hist, -5)[-5:] 170 | 171 | ax[0, 1].bar(range(0,15), hist[:15], color=Estilos[tipo][0], edgecolor='black', width=0.4, align='edge') 172 | 173 | for i in top_10_indices: 174 | freq = hist[i] 175 | label = f'{(freq / np.sum(hist)) * 100:.2f}%' 176 | ax[0, 1].text(i, freq, label, ha='center', va='bottom', fontsize=9, color=Estilos[tipo][0], rotation=45) 177 | 178 | xticks_positions = [i for i in range(0, 15)] 179 | interval_labels = [f'{int(edges[i])} - {int(edges[i+1])}' for i in range(len(edges)-1)] 180 | ax[0, 1].set_xticks(xticks_positions) 181 | ax[0, 1].set_xticklabels([interval_labels[i] for i in xticks_positions], color=Estilos[tipo][0], rotation=45) 182 | ax[0, 1].tick_params(axis='y', colors=Estilos[tipo][0]) 183 | ax[0, 1].set_ylim(0, 98000) 184 | #_______________________ 185 | hist, edges = np.histogram(ntx[2*210000:3*210000],bins=50) 186 | 187 | top_10_indices = np.argpartition(hist, -5)[-5:] 188 | 189 | ax[1, 0].bar(range(0, 35), hist[:35], color=Estilos[tipo][0], edgecolor='black', width=0.4, align='edge') 190 | 191 | for i in top_10_indices: 192 | freq = hist[i] 193 | label = f'{(freq / np.sum(hist)) * 100:.2f}%' 194 | ax[1, 0].text(i, freq, label, ha='center', va='bottom', fontsize=9, color=Estilos[tipo][0], rotation=75) 195 | 196 | 197 | xticks_positions = [i for i in range(0, 35, 2)] 198 | interval_labels = [f'{int(edges[i])} - {int(edges[i+1])}' for i in range(len(edges)-1)] 199 | ax[1, 0].set_xticks(xticks_positions) 200 | ax[1, 0].set_xticklabels([interval_labels[i] for i in xticks_positions], color=Estilos[tipo][0], rotation=45) 201 | ax[1, 0].tick_params(axis='y', colors=Estilos[tipo][0]) 202 | ax[1, 0].set_ylim(0, 15000) 203 | #================================= 204 | hist, edges = np.histogram(ntx[3*210000:], bins=50) 205 | 206 | top_10_indices = np.argpartition(hist, -5)[-5:] 207 | 208 | ax[1, 1].bar(range(0,35), hist[:35], color=Estilos[tipo][0], edgecolor='black', width=0.4, align='edge') 209 | 210 | for i in top_10_indices: 211 | freq = hist[i] 212 | label = f'{(freq / np.sum(hist)) * 100:.2f}%' 213 | ax[1, 1].text(i, freq, label, ha='center', va='bottom', fontsize=9, color=Estilos[tipo][0], rotation=75) 214 | 215 | xticks_positions = [i for i in range(0, 35, 2)] 216 | interval_labels = [f'{int(edges[i])} - {int(edges[i+1])}' for i in range(len(edges)-1)] 217 | ax[1, 1].set_xticks(xticks_positions) 218 | ax[1, 1].set_xticklabels([interval_labels[i] for i in xticks_positions], color=Estilos[tipo][0], rotation=45) 219 | ax[1, 1].tick_params(axis='y', colors=Estilos[tipo][0]) 220 | ax[1, 1].set_ylim(0, 16500) 221 | #==================================== 222 | 223 | ax[0,0].set_title("1st Halving\n2009-2012",fontsize=25,loc='left', **preferencias) 224 | ax[0,1].set_title("2nd Halving\n2012-2016",fontsize=25,loc='left', **preferencias) 225 | ax[1,0].set_title("3rd Halving\n2016-2020",fontsize=25,loc='left', **preferencias) 226 | ax[1,1].set_title("4th Halving\n2020-2024",fontsize=25,loc='left', **preferencias) 227 | 228 | 229 | if tipo[7:8]=='d': 230 | tw1 = Image.open('bins/br_w.png') 231 | else: 232 | tw1 = Image.open('bins/br_d.png') 233 | 234 | tw1_resized = tw1.resize((int(tw1.width * 0.35), int(tw1.height * 0.35))) # Reduce el tamaño de la imagen a la mitad 235 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 236 | tw1_array = np.array(tw1_resized) 237 | 238 | fig.figimage(tw1_array, xo=1850, yo=1300, alpha=0.55, zorder=1) 239 | 240 | plt.subplots_adjust(wspace=0.3, hspace=1) 241 | plt.savefig('analisis/resultados/Numero_de_transacciones_halv_'+tipo+'.png',bbox_inches='tight',pad_inches=0.5) 242 | 243 | 244 | # for a in Estilos.keys(): 245 | # crear_imagen_h(a) 246 | # crear_imagen_total(a) 247 | crear_imagen_total('estilo_dark') 248 | crear_imagen_h('estilo_dark') -------------------------------------------------------------------------------- /scripts/README.md: -------------------------------------------------------------------------------- 1 | # Análisis Onchain 2 | 3 | 4 | jpg 6 | 7 | [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/drkostas/Youtube-FirstCommentBot/master/LICENSE) 8 | 9 | --- 10 | ## Tabla de Contenido 11 | 12 | + [Introducción](#intro) 13 | + [Instalando](#instalando) 14 | + [Prerequisitos](#req) 15 | + [Transacciones en Bitcoin](#tx) 16 | + [Scripts en python](#py) 17 | + [hello world](#helloworld) 18 | + [Tamaño de Bloque](#size) 19 | 20 | --- 21 | ## Introducción 22 | 23 | Con un nodo completo de Bitcoin que tenga completada la sinctronización se tiene una copia completa (y auto verificada) del blockchain, desde el primer bloque hasta el último nuevo que se genere. 24 | 25 | Este se configuró (ver [Bitcoin.conf](https://github.com/CobraPython/BitcoinResearch/blob/main/Apuntes/Manuales/Bitcoin.conf.pdf)) de tal forma que admite solicitudes RPC-Json desde otros ordenadores de la red interna para luego procesar data. 26 | 27 | ## Instalando 28 | 29 | Ya corriendo el nodo y esperando solicitudes, se debe tener en cuenta a las credenciales de acceso que se configuraron en el `bitcoin.conf`. Se deben disponer de `user y password` para acceder al RPC de Bitcoin Core, para no dejarlos explicitamente los declaramos en como variables de entorno `.env`. 30 | 31 | Se hace uso de dos librerías para acceder a bitcoin-core usando python: 32 | 33 | 1. python-bitcoinlib 34 | 2. python-bitcoinrpc 35 | 36 | Los scripts [helloworld1.py](https://github.com/CobraPython/BitcoinResearch/blob/main/Onchain/helloworld1.py) y [helloworld1.py](https://github.com/CobraPython/BitcoinResearch/blob/main/Onchain/helloworld2.py) son un ejemplo para probar la conexión y funcionamiento de cada librería respectivamente. 37 | 38 | ### Pre requisitos 39 | 40 | Se crea un entorno de trabajo para Python `python3 venv -m rpibots` y se activa el entorno con `cd rpibots && source bin/python`. (Se pueden usar otros gestores de entornos virtuales como virtualenv o conda, en nuestro caso se usa venv). 41 | 42 | ``` sh 43 | git clone https://github.com/CobraPython/BitcoinResearch 44 | cd BitcoinResearch/Onchain 45 | pip install -r req.txt 46 | ``` 47 | Si todo esta correctamente, al ejecutar cualquier script obtenemos el resultado de ejecutar `bitcoin-cli getblockchaininfo`. 48 | 49 | 50 | ## Transacciones en Bitcoin 51 | 52 | Las transacciones son la parte más importante del sistema bitcoin. Todo lo demás en bitcoin fue 53 | diseñado para asegurar que las transacciones puedan ser creadas, propagadas por la red, validadas y 54 | finalmente añadidas al libro contable global (la cadena de bloques). 55 | 56 | Las transacciones son estructuras 57 | de datos que codifican la transferencia de valor entre los participantes en el sistema bitcoin. Cada 58 | transacción es una entrada pública en la cadena de bloques de bitcoin, el libro contable global de 59 | contabilidad por partida doble. 60 | 61 | Bitcoin tiene unas características como (crypto) moneda: 62 | 63 | - Cada bloque debe ser generado con las transacciones que se solicitan, en competencia. Las transacciones no pueden ser censuradas. 64 | - Una transacción que es confirmada por un bloque es virtualmente irreversible (a partir de 2 a 6 bloques encima se considera como tal). 65 | - Tiene una politica de emisión restringida a menos de 21 millones de bitcoins. 66 | - Cada bitcoin tiene su historial desde su creación (al generar bloques con minería) hasta el último poseedor, todos los movimientos son transparentes para todos en la red. 67 | 68 | Estas características la convierten en una moneda muy segura en comparación a otras. No se pueden falsificar ni generar de otra forma. El transferir valor a través de Bitcoin es lo más parecido a pagar en efectivo, en persona. Una vez solicitada una transacción a la red esta se ejecutará directamente a la otra persona. 69 | 70 | 71 | 72 | jpg 74 | 75 | ### Ciclo de Vida de una Transacción 76 | 77 | 78 | jpg 79 | 80 | 81 | 1. Creación de la transacción (generación). Para crear una transacción se deben ordenar en formato de entradas/salidas. 82 | 83 | 2. Firma. Para autorizar el gasto se debe presentar las firmas de propiedad. 84 | 85 | 3. Transimisión de la transacción a la red (propagación). Cada nodo de la red (participante) valida y propaga la transacción hasta que alcanza a 86 | (casi) todos los nodos en la red 87 | 88 | 89 | 4. Finalmente la transacción es verificada por un nodo minero e incluida en un bloque de transacciones que es registrado en la cadena de bloques. 90 | 91 | 5. Este bloque se propaga a toda la red, siendo validada por cada nodo en la red. 92 | 93 | La información de una transacción no es sensible pues no lleva la clave privada sino únicamente la firma. El peso promedio de una transacción esta entre los 300 y 400 bytes de datos. 94 | Una transacción bitcoin puede ser enviada sobre cualquier red. Siempre y cuando la transacción pueda 95 | alcanzar un nodo de la red bitcoin que la propague, no importa cómo es transportada al primer nodo. 96 | 97 | ### Estructura de una Transacción 98 | 99 | Una transacción es una estructura de datos que codifica una transferencia de valor de una fuente de 100 | fondos, llamada entrada (input), a un destinatario, llamado una salida (output). Las entradas y salidas 101 | de una transacción no se encuentran relacionadas a cuentas ni identidades. En cambio debes pensar 102 | en ellas como montos de bitcoin—trozos de bitcoin—asegurados con un secreto específico que solo su dueño, o persona que conoce el secreto, puede liberar. 103 | 104 | #### Entradas y Salidas 105 | 106 | La pieza fundamental de una transacción bitcoin es una salida de **transacción no gastada (unspent 107 | transaction output), o UTXO**. 108 | 109 | El modelo de una transacción se puede ver en el siguiente esquema. 110 | 111 | jpg 113 | 114 | Una transacción cuenta de entradas y salidas. Las entradas son los ingresos de dinero y las salidas los gastos que se efectúan. Los bitcoins estan en **la salida** de cada transacción. 115 | 116 | Es bastante parecido al manejo de efectivo. Las salidas no gastadas no pueden dividirse. Es como pagar una cuenta de 5 dolares con un billete de 10. No se puede fraccionar el billete en pedazos, por lo que el cambio será otro billete de 5. 117 | 118 | Los bitcoins no se almacenan en una cuenta o una se agrupan por etiqueta. Los bitcoins son las salidas UTXO y están distribuidas en desorden en los bloques. Cada nuevo gasto viene de una entrada anterior, y esta a su vez viene de otra entrada y así hasta un punto inicial: Los bitcoin nacen cuando se genera un nuevo bloque. 119 | 120 | 121 | jpg 123 | 124 | 125 | Cada salida tiene asociada una dirección (wallet) que tendra el control de los bitcoins. Las transacciones son conjuntos de datos que no contienen información sensible. Una analogía es el cheque. Estos se pueden escribir pero no tienen validez hasta que alguien propietario estampa su firma. 126 | 127 | De igual forma, las transacciones en bitcoin no tienen información sensible. La firma se verifica por el sistema usando funciones criptográficas (Curva Elíptica). Solo quien es dueño de la llave privada puede firmar correctamente la transacción. 128 | 129 | Un nodo bitcoin core tiene en su base de datos la información únicamente de las UTXO, transacciones no gastadas. Un bloque pesa en promedio 1 MB (y se limita a 4 MB con Segwit). Las transacciones tienen un costo de comisión que no depende del monto a transferir (como lo hace WesterUnion) sino de cuanto pesa (bytes) la transacción. Una transacción de muchas entradas y salidas será mas costosa que una simple entrada simple salida. El fee se ajusta por cada byte que tenga la transacción (una transacción simple llega a los ~220 bytes) y los mineros toman con mas prioridad a los que dejan un fee mayor. 130 | 131 | Una vez que un minero encuentra un bloque que cumpla la prueba de trabajo y sea válido este se propaga en toda la red. Cada nodo recibe el bloque y verifica que este correcto, luego lo sigue propagando. 132 | ## Scripts en Python 133 | 134 | Una vez que se sabe como se conforma la estructura de datos en el blockchain de Bitcoin a través de los UTXO, vamos a usar Python para obtener información y procesarla. 135 | 136 | ### Hello World (Intro) 137 | 138 | #### Bitcoin-cli 139 | 140 | Mediante los comandos bitcoin-cli accedemos a información solicitandola directamente a Bitcoin Core en el nodo. 141 | 142 | Como vimos los bloques guardan información de las transacciones. En promedio toma 10 minutos producir un nuevo bloque. Podemos acceder a la información de un bloque con el siguiente procedimiento: 143 | 1. `bitcoin-cli getblockhash 0` entrega la firma del bloque solicitado, en el caso el bloque génesis (bloque 0). El resultado es `000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f` y con este hash podemos solicitar información completa. 144 | 2. `bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f` Con este comando accedemos al bloque por su hash. 145 | 146 | jpg 148 | 149 | 3. Se puede ver que el bloque tiene una única transacción identificada con un hash `4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b` 150 | 151 | 4. El comando `bitcoin-cli getrawtransaction 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b` nos devuelve un código de error. Pues esta transacción es especial. Para acceder usamos el mismo comando de punto «2» con un argumento '2' `bitcoin-cli getblock 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 2`, para entregar más información. 152 | 153 | jpg 155 | 156 | En la sección 'vout' se pueden ver los 50 BTC y su pago en la dirección: `1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa` 157 | 158 | 5. El comando `getrawtransaction` para otros bloques nos da una respuesta válida en HEX. Con el comando `decoderawtransaction` y el HEX se obtiene toda la información de la transacción. 159 | 160 | Resumiendo, cada bloque contiene una cantidad variable de transacciones identificadas con un 'tx_id'. Las transacciones contienen adentro las entradas 'vin' y las salidas 'vout'. 161 | En las entradas si repetimos el comando `getrawtransaction` con la salida de 'tx' 162 | 163 | 164 | ##### El mensaje secreto de Satoshi: 165 | 166 | Si convertimos el mensaje de HEX a ASCII encontramos el mensaje secreto de satoshi: 167 | 168 | ```sh 169 | echo "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff 170 | 4d04ffff001d0104455468652054696d65732030332f4a616e2f32303039204368616e63656c6c6f72206f6e20 171 | 6272696e6b206f66207365636f6e64206261696c6f757420666f722062616e6b73ffffffff0100f2052a010000 172 | 00434104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f3 173 | 5504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac00000000" | xxd -r -p 174 | ``` 175 | jpg 176 | 177 | >“The Times 03/Jan/2009 Chancellor on brink of second bailout for banks" 178 | 179 | ### Tamaño de Bloque 180 | 181 | Para hacer un primer análisis encontraremos el número de bloque y el tamaño de memoria que ocupa en el blockchain. 182 | 183 | El número de bloques ronda los 770 mil y el tamaño que se espera en promedio es de 1 MB aunque puede aceptar bloques hasta de 4 MB. 184 | 185 | Luego se grafica la relación número vs tamaño para ver como cambió el uso de memoria a lo largo del tiempo. 186 | 187 | 188 | conmutatividad: 189 | 190 | $$A+B = B+A$$ 191 | 192 | 193 | otros cambios 194 | 195 | $$A\cdot B = B\cdot A$$ 196 | 197 | 198 | 199 | 200 | otros cambios 201 | 202 | -------------------------------------------------------------------------------- /scripts/analisis/dificultad.py: -------------------------------------------------------------------------------- 1 | # este script construye la gráfica histórica 2 | # del tamaño de bloques en Bitcoin 3 | 4 | # este script construye un gráfico de la evolución del tamaño de bloques 5 | # a lo largo del cada bloque 6 | 7 | # librerias a usar 8 | import os 9 | import numpy as np 10 | import matplotlib.pyplot as plt 11 | import matplotlib.dates as mdates 12 | from matplotlib import font_manager as fm 13 | from PIL import Image 14 | from datetime import datetime 15 | from app.styles import Estilos, colores 16 | from app.readata import leer_data,time_data,estado_data,last_block 17 | 18 | 19 | # Cambiar la tipografia 20 | fpath = os.path.join('bins/MonoLisaSimpson.ttf') 21 | prop = fm.FontProperties(fname=fpath) 22 | 23 | fpatht = os.path.join('bins/BigBlueTerm437NerdFont-Regular.ttf') 24 | title = fm.FontProperties(fname=fpatht) 25 | 26 | fname = os.path.split(fpath)[1] 27 | 28 | def bits_to_difficulty(bits): 29 | bits = int(bits, 16) 30 | # Convertir bits a un número de 256 bits en formato big-endian 31 | target = (bits & 0x007fffff) * 2 ** (8 * ((bits >> 24) - 3)) 32 | # Calcular la dificultad como el cociente entre el objetivo máximo y el objetivo actual 33 | max_target = 0xffff * 2 ** (8 * (0x1d - 3)) 34 | difficulty = max_target / target 35 | return difficulty 36 | 37 | def crear_imagen_total(tipo='estilo_dark'): 38 | # Color del fondo 39 | fig, ax = plt.subplots(1,2,figsize=(20,5), dpi=200) 40 | fig.patch.set_facecolor(Estilos[tipo][1]) 41 | ax[0].patch.set_facecolor(Estilos[tipo][1]) 42 | ax[1].patch.set_facecolor(Estilos[tipo][1]) 43 | 44 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 45 | 46 | plt.suptitle("Bitcoin\n Difficulty",fontsize=50,y=1.5,x=0.18,color=Estilos[tipo][0],fontproperties=title) 47 | bits,time_s = leer_data('bits','time_b') 48 | difficulty = np.array([bits_to_difficulty(a) for a in bits]) 49 | time = time_data(time_s) 50 | 51 | if tipo[7:8]=='d': 52 | ax[0].plot(time,difficulty,color=colores[3],zorder=1,linewidth=3) 53 | else: 54 | ax[0].plot(time,difficulty,color=colores[10],zorder=1,linewidth=3) 55 | 56 | ##ax[0].plot(time,difficulty,color=colores[8],zorder=1,linewidth=7) 57 | #ax[0].plot(time,difficulty,color=colores[2],zorder=1,linewidth=3) 58 | #ax[0].plot(time,difficulty,color=colores[1],zorder=1,linewidth=0.5) 59 | 60 | 61 | 62 | #ax[0].set_yscale('log') 63 | locator = mdates.MonthLocator(interval=23) 64 | formatter = mdates.DateFormatter('%b\n%Y') 65 | ax[0].xaxis.set_major_locator(locator) 66 | ax[0].xaxis.set_major_formatter(formatter) 67 | ax[0].xaxis.set_tick_params(labelsize=18, rotation=30,length=5,width=3) 68 | ax[0].tick_params(axis='both',colors=Estilos[tipo][0]) 69 | ax[0].set_ylabel('Difficulty\n', fontsize=23,**preferencias) 70 | ax[0].set_title("Scale:'linear'",loc='right',fontsize=15,color='white') 71 | ax[0].axhline(difficulty.max(),linestyle='dashed',color='red',linewidth=1) 72 | ax[1].axhline(difficulty.max(),linestyle='dashed',color='red',linewidth=1) 73 | 74 | #ax[1].plot(time,difficulty,color=colores[3],zorder=1,linewidth=7) 75 | 76 | if tipo[7:8]=='d': 77 | ax[1].plot(time,difficulty,color=colores[3],zorder=1,linewidth=3) 78 | else: 79 | ax[1].plot(time,difficulty,color=colores[10],zorder=1,linewidth=3) 80 | #ax[1].plot(time,difficulty,color=colores[8],zorder=1,linewidth=7) 81 | #ax[1].plot(time,difficulty,color=colores[2],zorder=1,linewidth=3) 82 | #ax[1].plot(time,difficulty,color=colores[1],zorder=1,linewidth=0.5) 83 | 84 | 85 | 86 | ax[1].set_yscale('log') 87 | locator = mdates.MonthLocator(interval=23) 88 | formatter = mdates.DateFormatter('%b\n%Y') 89 | ax[1].xaxis.set_major_locator(locator) 90 | ax[1].xaxis.set_major_formatter(formatter) 91 | ax[1].xaxis.set_tick_params(labelsize=18, rotation=30,length=5,width=3) 92 | ax[1].tick_params(axis='both',colors=Estilos[tipo][0]) 93 | ax[1].set_ylabel('Difficulty log\n', fontsize=23,**preferencias) 94 | 95 | ax[1].set_title("Scale:'logy'",loc='right',fontsize=15,color='white') 96 | 97 | ax[0].set_yticks([0,1e13,2e13,3e13,4e13,5e13]) 98 | ytick_labels = ['0',r"$1\times10^{13}$",r"$2\times10^{13}$",r"$3\times10^{13}$",r"$4\times10^{13}$",r"$5\times10^{13}$"] 99 | ax[0].set_yticklabels(ytick_labels,rotation=23,**preferencias) 100 | ax[0].yaxis.set_tick_params(labelsize=18) 101 | ax[1].tick_params(axis='y',labelsize=18,rotation=25) # Cambia 20 al tamaño que prefieras 102 | 103 | ax[0].grid(axis='y', linewidth=.5,linestyle='--') 104 | ax[1].grid(axis='y', linewidth=.5,linestyle='--') 105 | 106 | 107 | for spine in ax[0].spines.values(): 108 | spine.set_color(Estilos[tipo][0]) 109 | for spine in ax[1].spines.values(): 110 | spine.set_color(Estilos[tipo][0]) 111 | 112 | if tipo[7:8]=='d': 113 | tw1 = Image.open('bins/br_w.png') 114 | else: 115 | tw1 = Image.open('bins/br_d.png') 116 | 117 | 118 | tw1_resized = tw1.resize((int(tw1.width * 0.4), int(tw1.height * 0.4))) # Reduce el tamaño de la imagen a la mitad 119 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 120 | tw1_array = np.array(tw1_resized) 121 | 122 | 123 | # Usa el índice para obtener la fecha correspondiente 124 | fecha_datetime = datetime.strptime(time_s[np.argmax(difficulty)][:10],'%Y-%m-%d') 125 | formatted_date = fecha_datetime.strftime('%d of %B %Y') 126 | mss1 = '*Up to block ' + str(last_block())+'\nthe All-Time High\nwas ' 127 | mss2 = str(round(difficulty.max()/1e12,2))+' T on\n'+str(formatted_date) 128 | 129 | fig.text(0.5,1.15,mss1+mss2, ha='center', va='center', fontsize=20,**preferencias) 130 | 131 | fig.figimage(tw1_array, xo=3100, yo=1250, alpha=0.55, zorder=1) 132 | plt.subplots_adjust(wspace=0.25) 133 | plt.savefig('analisis/resultados/dificultad_total_'+tipo+'.png',bbox_inches='tight',pad_inches=0.5) 134 | 135 | 136 | 137 | 138 | 139 | def crear_imagen_h(tipo='estilo_dark'): 140 | # # Color del fondo 141 | fig, ax = plt.subplots(2,2,figsize=(13,6), dpi=200) 142 | 143 | fig.patch.set_facecolor(Estilos[tipo][1]) 144 | ax[0,0].patch.set_facecolor(Estilos[tipo][1]) 145 | ax[0,1].patch.set_facecolor(Estilos[tipo][1]) 146 | ax[1,0].patch.set_facecolor(Estilos[tipo][1]) 147 | ax[1,1].patch.set_facecolor(Estilos[tipo][1]) 148 | 149 | 150 | preferencias = {'color':Estilos[tipo][0],'fontproperties':prop} 151 | 152 | plt.suptitle("Difficulty\nper Halving",fontsize=45,x=0.28,y=1.4,fontproperties=title,color=Estilos[tipo][0]) 153 | bits,time = leer_data('bits','time_b') 154 | 155 | difficulty_1 = np.array([bits_to_difficulty(a) for a in bits[:210000-1]]) 156 | time_1 = time_data(time[:210000-1]) 157 | 158 | difficulty_2 = np.array([bits_to_difficulty(a) for a in bits[210000:2*210000-1]]) 159 | time_2 = time_data(time[210000:2*210000-1]) 160 | 161 | difficulty_3 = np.array([bits_to_difficulty(a)/10**12 for a in bits[2*210000:3*210000-1]]) 162 | time_3 = time_data(time[210000*2:3*210000-1]) 163 | 164 | difficulty_4 = np.array([bits_to_difficulty(a)/10**12 for a in bits[3*210000:]]) 165 | time_4 = time_data(time[3*210000:]) 166 | 167 | 168 | 169 | 170 | 171 | for spine in ax[0,0].spines.values(): 172 | spine.set_color(Estilos[tipo][0]) 173 | for spine in ax[0,1].spines.values(): 174 | spine.set_color(Estilos[tipo][0]) 175 | for spine in ax[1,0].spines.values(): 176 | spine.set_color(Estilos[tipo][0]) 177 | for spine in ax[1,1].spines.values(): 178 | spine.set_color(Estilos[tipo][0]) 179 | 180 | 181 | 182 | locator1 = mdates.MonthLocator(interval=9) 183 | formatter1 = mdates.DateFormatter('%b\n%Y') 184 | ax[0,0].xaxis.set_major_locator(locator1) 185 | ax[0,0].xaxis.set_major_formatter(formatter1) 186 | ax[0,0].xaxis.set_tick_params(labelsize=12, rotation=30,length=5,width=3) 187 | ax[0,0].tick_params(axis='both',colors=Estilos[tipo][0]) 188 | ax[0,0].set_ylabel('Difficulty\n', fontsize=13,**preferencias) 189 | 190 | 191 | date = datetime(2010, 7, 18) 192 | x_value = mdates.date2num(date) 193 | ax[0,0].scatter(x_value,5e1,s=300,color=colores[3]) 194 | ax[0,0].scatter(x_value,5e1,s=75,color=colores[8]) 195 | ax[0,0].scatter(x_value,5e1,s=5,color=colores[5]) 196 | ax[0,0].vlines(x_value,0,1e1, colors=Estilos[tipo][0], linestyles='dashed') 197 | date = datetime(2010,4,1) 198 | x_value = mdates.date2num(date) 199 | ax[0,0].text(x_value,1e3, 'GPU Art Fozt\nOpenCL GPU', color=Estilos[tipo][0], ha='right', va='center',size=13) 200 | 201 | 202 | date = datetime(2011, 5, 20) 203 | x_value = mdates.date2num(date) 204 | ax[0,0].scatter(x_value,5e5,s=300,color=colores[3], zorder=0) 205 | ax[0,0].scatter(x_value,5e5,s=75,color=colores[8], zorder=0) 206 | ax[0,0].scatter(x_value,5e5,s=5,color=colores[5], zorder=0) 207 | ax[0,0].vlines(x_value,0,1e5, colors=Estilos[tipo][0], linestyles='dashed') 208 | date = datetime(2012,1, 20) 209 | x_value = mdates.date2num(date) 210 | ax[0,0].text(x_value,1e4, 'FPGA\nMiner', color=Estilos[tipo][0], ha='right', va='center',size=13) 211 | 212 | 213 | locator2 = mdates.MonthLocator(interval=8) 214 | formatter2 = mdates.DateFormatter('%b\n%Y') 215 | ax[0,1].xaxis.set_major_locator(locator2) 216 | ax[0,1].xaxis.set_major_formatter(formatter2) 217 | ax[0,1].xaxis.set_tick_params(labelsize=12, rotation=30,length=5,width=3) 218 | ax[0,1].tick_params(axis='both',colors=Estilos[tipo][0]) 219 | ax[0,1].set_ylabel('Difficulty\n', fontsize=13,**preferencias) 220 | 221 | 222 | date = datetime(2013, 5, 1) 223 | x_value = mdates.date2num(date) 224 | ax[0,1].scatter(x_value,1e7,s=300,color=colores[3]) 225 | ax[0,1].scatter(x_value,1e7,s=75,color=colores[8]) 226 | ax[0,1].scatter(x_value,1e7,s=5,color=colores[5]) 227 | ax[0,1].vlines(x_value,0,1e7, colors=Estilos[tipo][0], linestyles='dashed') 228 | date = datetime(2013,12, 1) 229 | x_value = mdates.date2num(date) 230 | ax[0,1].text(x_value,9e9, 'First ASIC\nCannan miner\nChip 130nm', color=Estilos[tipo][0], ha='right', va='center',size=12) 231 | 232 | 233 | 234 | date = datetime(2015, 1, 1) 235 | x_value = mdates.date2num(date) 236 | ax[0,1].scatter(x_value,5e10,s=300,color=colores[3]) 237 | ax[0,1].scatter(x_value,5e10,s=75,color=colores[8]) 238 | ax[0,1].scatter(x_value,5e10,s=5,color=colores[5]) 239 | ax[0,1].vlines(x_value,0,5e10, colors=Estilos[tipo][0], linestyles='dashed') 240 | date = datetime(2015,9, 1) 241 | x_value = mdates.date2num(date) 242 | ax[0,1].text(x_value,1e9, 'ASIC\n16nm', color=Estilos[tipo][0], ha='right', va='center',size=13) 243 | 244 | 245 | locator3 = mdates.MonthLocator(interval=9) 246 | formatter3 = mdates.DateFormatter('%b\n%Y') 247 | ax[1,0].xaxis.set_major_locator(locator3) 248 | ax[1,0].xaxis.set_major_formatter(formatter3) 249 | ax[1,0].xaxis.set_tick_params(labelsize=12, rotation=30,length=5,width=3) 250 | ax[1,0].tick_params(axis='both',colors=Estilos[tipo][0]) 251 | ax[1,0].set_ylabel('Difficulty\n', fontsize=13,**preferencias) 252 | 253 | ax[1,0].set_yticks([0,5,10,15]) 254 | ytick_labels = ['0.1T','5T','10T','15T'] 255 | ax[1,0].set_yticklabels(ytick_labels,**preferencias) 256 | ax[1,0].yaxis.set_tick_params(labelsize=12) 257 | 258 | 259 | date = datetime(2017, 1, 1) 260 | x_value = mdates.date2num(date) 261 | ax[1,0].scatter(x_value,.21,s=100,color=colores[3]) 262 | ax[1,0].scatter(x_value,.21,s=50,color=colores[8]) 263 | ax[1,0].scatter(x_value,.21,s=5,color=colores[5]) 264 | ax[1,0].vlines(x_value,0,.21, colors=Estilos[tipo][0], linestyles='dashed') 265 | date = datetime(2017,3,1) 266 | x_value = mdates.date2num(date) 267 | ax[1,0].text(x_value,4, 'ASIC\n14nm', color=Estilos[tipo][0], ha='right', va='center',size=13) 268 | 269 | date = datetime(2019, 1, 1) 270 | x_value = mdates.date2num(date) 271 | ax[1,0].scatter(x_value,5,s=300,color=colores[3]) 272 | ax[1,0].scatter(x_value,5,s=75,color=colores[8]) 273 | ax[1,0].scatter(x_value,5,s=5,color=colores[5]) 274 | ax[1,0].vlines(x_value,0,5, colors=Estilos[tipo][0], linestyles='dashed') 275 | date = datetime(2019,3, 1) 276 | x_value = mdates.date2num(date) 277 | ax[1,0].text(x_value,10, 'ASIC\n7nm', color=Estilos[tipo][0], ha='right', va='center',size=13) 278 | 279 | 280 | locator4 = mdates.MonthLocator(interval=7) 281 | formatter4 = mdates.DateFormatter('%b\n%Y') 282 | ax[1,1].xaxis.set_major_locator(locator4) 283 | ax[1,1].xaxis.set_major_formatter(formatter4) 284 | ax[1,1].xaxis.set_tick_params(labelsize=12, rotation=30,length=5,width=3) 285 | ax[1,1].tick_params(axis='both',colors=Estilos[tipo][0]) 286 | ax[1,1].set_ylabel('Difficulty\n', fontsize=13,**preferencias) 287 | 288 | ax[1,1].set_yticks([10,20,30,40,50]) 289 | ytick_labels = ['10T','20T','30T','40T','50T'] 290 | ax[1,1].set_yticklabels(ytick_labels,**preferencias) 291 | ax[1,1].yaxis.set_tick_params(labelsize=12) 292 | 293 | date = datetime(2023, 1, 1) 294 | x_value = mdates.date2num(date) 295 | ax[1,1].scatter(x_value,38,s=300,color=colores[3]) 296 | ax[1,1].scatter(x_value,38,s=75,color=colores[8]) 297 | ax[1,1].scatter(x_value,38,s=5,color=colores[5]) 298 | ax[1,1].vlines(x_value,0,38, colors=Estilos[tipo][0], linestyles='dashed') 299 | date = datetime(2023,7, 1) 300 | x_value = mdates.date2num(date) 301 | ax[1,1].text(x_value,20,'ASIC\n5nm', color=Estilos[tipo][0], ha='right', va='center',size=13) 302 | 303 | if tipo[7:8]=='d': 304 | ax[0,0].plot(time_1,difficulty_1,color=colores[3],zorder=1)#,linewidth=1) 305 | ax[0,1].plot(time_2,difficulty_2,color=colores[3]) 306 | ax[1,0].plot(time_3,difficulty_3,color=colores[3]) 307 | ax[1,1].plot(time_4,difficulty_4,color=colores[3]) 308 | else: 309 | ax[0,0].plot(time_1,difficulty_1,color=colores[10],zorder=1)#,linewidth=1) 310 | ax[0,1].plot(time_2,difficulty_2,color=colores[10]) 311 | ax[1,0].plot(time_3,difficulty_3,color=colores[10]) 312 | ax[1,1].plot(time_4,difficulty_4,color=colores[10]) 313 | 314 | #ax[0,0].plot(time_1,difficulty_1,color=colores[3],zorder=1,linewidth=3) 315 | #ax[0,0].plot(time_1,difficulty_1,color=colores[2],zorder=1,linewidth=2) 316 | #ax[0,0].plot(time_1,difficulty_1,color=colores[1],zorder=1,linewidth=0.5) 317 | 318 | 319 | ax[0,0].set_yscale('log') 320 | ####ax[0,1].plot(time_2,difficulty_2,color=colores[3]) 321 | ax[0,1].set_yscale('log') 322 | ####ax[1,0].plot(time_3,difficulty_3,color=colores[3]) 323 | #ax[1,0].set_yscale('log') 324 | ####ax[1,1].plot(time_4,difficulty_4,color=colores[3]) 325 | #ax[1,1].set_yscale('log') 326 | 327 | ax[0,0].set_title("1st Halving\n2009-2012 scale:'logy'",fontsize=25,loc='left', **preferencias) 328 | ax[0,1].set_title("2nd Halving\n2012-2016 scale:'logy'",fontsize=25,loc='left', **preferencias) 329 | ax[1,0].set_title("3rd Halving\n2016-2020",fontsize=25,loc='left', **preferencias) 330 | ax[1,1].set_title("4th Halving\n2020-2024",fontsize=25,loc='left', **preferencias) 331 | 332 | if tipo[7:8]=='d': 333 | tw1 = Image.open('bins/br_w.png') 334 | else: 335 | tw1 = Image.open('bins/br_d.png') 336 | 337 | total_diff = np.array([bits_to_difficulty(a)/10**12 for a in bits]) 338 | me1 = 'All-time High: '+str(round(total_diff.max(),2))+' T' 339 | me2 = '\nLast Block '+str(last_block())+' : '+str(round(total_diff[-1],2))+' T' 340 | 341 | 342 | 343 | date = datetime(2013, 1, 1) 344 | x_value = mdates.date2num(date) 345 | ax[0,0].text(x_value,1e10,me1+me2, color=Estilos[tipo][0], ha='right', va='center',size=13) 346 | 347 | 348 | tw1_resized = tw1.resize((int(tw1.width * 0.3), int(tw1.height * 0.3))) # Reduce el tamaño de la imagen a la mitad 349 | # Convierte la imagen de PIL a una matriz de numpy para que matplotlib pueda trabajar con ella 350 | tw1_array = np.array(tw1_resized) 351 | 352 | 353 | 354 | 355 | fig.figimage(tw1_array, xo=1800, yo=1550, alpha=0.55, zorder=1) 356 | plt.subplots_adjust(wspace=0.3, hspace=1) 357 | plt.savefig('analisis/resultados/dificultad_halv_'+tipo+'.png',bbox_inches='tight',pad_inches=0.75) 358 | 359 | 360 | 361 | # for a in Estilos.keys(): 362 | # crear_imagen_h(a) 363 | # crear_imagen_total(a) 364 | crear_imagen_h('estilo_dark') 365 | crear_imagen_total('estilo_dark') 366 | -------------------------------------------------------------------------------- /scripts/analisis/app/main.tex: -------------------------------------------------------------------------------- 1 | % ****** rbf.tex ****** 2 | % Este archivo provee el formato para la Revista Boliviana de Fisica, RBF. 3 | \documentclass{rbf} 4 | \usepackage{amsmath} 5 | %\usepackage{natbib} 6 | \usepackage[utf8]{inputenc} 7 | \usepackage{subfig} 8 | 9 | \begin{document} 10 | 11 | \title{Bitcoin} 12 | 13 | \author{Brandom Chicharito\marca{*}} 14 | \afil{Carrera de Física, Universidad Mayor de San Andrés}% 15 | \alpie{*}{brandoum@fiumsa.edu.bo} 16 | 17 | \author{J. C. Vargas\marca{\dag}} 18 | \afil{Fellow Researcher, Bitcoin Research} 19 | \alpie{\dag}{jp.cr3spo@pm.me} 20 | 21 | \author{Alfredo Carrillo Mendoza\marca{\ddag}} 22 | \afil{Fellow Researcher, Bitcoin Research} 23 | \alpie{\ddag}{acarrillom@proton.me}% Las lineas se cortan automaticamente o puede obligarse esto con 24 | 25 | \begin{abstract} 26 | \Resumen 27 | En este artículo se presenta los resultados de tres metodologías diferentes en las que se aplicó una simulación Monte Carlo para estimar el valor de $\pi$: el método de comparación de áreas, el método propuesto por Buffon y la extensión de Laplace al método de Buffon. Se estudió con detalle el resultado no determinista de las simulaciones y se demostró que cumplen con los teoremas fundamentales de la probabilidad. Los tres casos se desarrollaron en un lenguaje de programación de alto nivel: Python, junto con la librería Numpy que le otorga un performance optimizado. 28 | 29 | \descriptores{Simulación, Monte Carlo, $\pi$, Buffon, Buffon-Laplace, Python, Numpy} 30 | 31 | 32 | \Abstract 33 | This article represents the result of three different metodologies for estimate the $\pi$ value with Monte Carlo's simulation: the comparition area's method, the method proposed by Buffon and the Laplace's extention. The non-deterministic result of the simulations was studied in detail and it was shown that they comply with the fundamental theorems of probability. The three cases had been developed in Python (high level language) and Numpy library which give it an optimized performance. 34 | 35 | \keywords{Simulation, Monte Carlo, $\pi$, Buffon, Buffon-Laplace, Python, Numpy} 36 | \end{abstract} 37 | 38 | \maketitle 39 | 40 | %%%%%%%%%%%%%%%%%%%%%%%%%%% 41 | % INTRODUCCION 42 | %%%%%%%%%%%%%%%%%%%%%%%%%%% 43 | 44 | 45 | 46 | \section{Introducción} 47 | 48 | Bitcoin es el nombre de la primera solución tecnológica descentralizada en la historia de la humanidad. Se compone de computadoras que corren un software desarrollado bajo el paradigma FOSS (Free and y Open Source Software), llamado «cliente Bitcoin-Core», disponible en distintos sistemas operativos (Windows, Linux y Mac) con requisitos mínimos de hardware capaz de correr desde un Raspberry en adelante. Se denomina descentralizado pues la red no tiene ni un solo punto (o nodo) de confianza, lo que significa que todos los nodos tienen la misma prioridad en la validación de información en la red. 49 | 50 | 51 | La palabra 'Bitcoin' se registra por primera vez el 18 de agosto de 2008 al reservarse el dominio de Internet "www.bitcoin.org". Días después se detalla su propuesta de funcionamiento en un White Paper publicado en Internet bajo el pseudónimo de 'Satoshi Nakamoto', en un foro de discusión sobre criptografía el 31 de octubre de 2008 con el título "Bitcoin: A Peer-to-Peer Electronic Cash System". 52 | 53 | Bitcoin tiene una raíz etimologíca compuesta por dos palabras: "bit" que es la unidad mínima de informacion digital y "coin" una palabra en ingles para referirse a «moneda» que es un medio de intercambio indirecto de bienes económicos. Bitcoin busca crear un sistema económico nuevo cuyo título del paper expresa precisamente, traduciendo al español: «Efectivo electrónico transmitido de punto a punto». No se propuso para ser el primer dinero digital (en 2008 la banca digital ya existía) sino ser el primer sistema monetario digital que tenga propiedades que se comparen al dinero en efectivo, como ser: la portabilidad, la privacidad más no anonimidad (una transacción personal no recopila datos), la fácil verificación de autenticidad (sellos acuñados en monedas o marcas de seguridad en billetes), la disposición (liquidez), etc. pero principalmente destacamos una propiedad que únicamente emerge en esta red: Ser "Permisionless", que significa 'no necesitar el permiso' de ningún punto o actor en la red para efectuar una transacción. Esta caracteristica implica eliminar todo punto de confianza dando la capacidad de auditar a cualquier componente de la red (código, base de datos, firmas digitales, etc) en cualquier momento. Esta propiedad y sus efectos surgen al mantener un consenso sobre los 'estados' denominados UTXOs de la red. Esta información común y compartida por toda la red se almacena en una base de datos llamada Blockchain. Se divide en trozos 'encadenados' uno tras otro en bloques de información del tamaño de 1 MB. Cada bloque nuevo actualiza estos 'estados' aportando nueva información compartida por toda la red. 54 | 55 | Esta nueva actualización de estados es común en toda la red mediante el protocolo de consenso. Al tener todos los participantes el mismo rango en la red ninguno tiene el poder de imponer su historial e independientemente de si hay un participante malicioso todo el conjunto debe mantener la misma información. Este problema es conocido al estudiar la información en sistemas distribuidos como 'El problema de los generales bizantinos' \cite{bizantinos}. Bitcoin hace uso de un protocolo de consenso de red llamado Proof of Work o Prueba de Trabajo que consiste en la necesidad de reunir una cantidad de trabajo en el mundo físico (en forma de energía eléctrica) para producir un nuevo estado de información digital común en el ciberespacio. Esta prueba de trabajo que se solicita es variable y depende del tamaño y poder computacional de toda la red. 56 | 57 | La energía eléctrica necesaria para el cambio de estados UTXO es un respaldo de seguridad para la inmutabilidad 58 | de la información compartida en el ciberespacio. La información que contiene el blockchain es computada por toda la red. Es por esta razón que la red Bitcoin se compara a una sola máquina universal de estados turing completo de escala global, una especie de superordenador con la capacidad de realizar la verificación mediante computo y mantenerlo inmutable en la red. 59 | 60 | 61 | En la siguiente sección II explicamos como funciona internamente el sistema rígido y predecible de reglas que expresa el código fuente de Bitcoin-Core. Para la seccion III estudiamos la red con los datos auditados propiamente y finalmente en la sección IV exponemos los resultados. 62 | 63 | 64 | \section{Diseño de la Red} 65 | 66 | Bitcoin se diseño bajo un cierto esquema de principios e incentivos que mantienen segura a la red. Mientras una sola entidad no contenga la mayoría del poder computacional de la red, está es segura. 67 | 68 | El funcionamiento sobre los incentivos esta expresado en el código de Bitcoin Core, cuyo punto fuerte es la limitación de la red para emitir un activo que funge para los intercambios de la red: 'bitcoin'. Cabe notar la diferencia en la nomenclatura radica en el uso de la mayúscula para diferenciar la red Bitcoin del activo subyacente de esta, bitcoin. Este activo se denomida como 'real e intangible' en el sentido que no tiene un carácter contractual (no tiene una obligación de pago ni derecho de cobro) y que al ser digital no guarda relación física en nuestro medioambiente físico (tierra agua aire) pero tiene una existencia irreproducible en un nuevo ámbito de interacción humana: el ciberespacio. 69 | 70 | El activo de bitcoin hace una definición nueva del concepto de propiedad privada al hacerla absoluta. Esta propiedad es inexistente en el campo físico pues cualquier recurso puede ser tomado por la fuerza o su proyección, sin embargo, bitcoin se puede considerar con un landing digital que no puede ser violado de ninguna manera por ningún tipo de fuerza o proyección de poder, al fundamentar su seguridad digital en la producción de energía eléctrica (medida en Julios) del mundo físico. Se necesita de una cantidad colosal de energía eléctrica para generar el cambio en un bloque de la red. 71 | 72 | El sistema de incentivos que mantiene la seguridad de la red toma como punto central la escasez digital. La cantidad de bitcoin que puede ser emitido es finito y calculable en todo momento siendo cercano a los 21 millones de unidades en total. Esto lo logra mediante código de computadora que no puede cambiado por algún actor de red pues implicaría que todos (el 100\% de la red) la asuma voluntariamente. 73 | 74 | La regla de emisión de bitcoin se da mediante el siguiente esquema: 75 | 76 | 1. Cada bloque generado por cualquier componente de la red debe demostrar una cantidad de trabajo computacional (que consume energía eléctrica). 77 | 78 | 2. Como recompensa al gasto de energía el sistema entrega un incentivo de nuevo bitcoin, que inicia en 50 btc. 79 | 80 | 3. Cada 210 000 bloques este incentivo se ajusta reduciendose a la mitad. Cada bitcoin puede ser dividio en partes más pequeñas hasta un factor de 100 millones, unidad que se denomina satoshis o sats. Este evento donde la emisión se reduce a la mitad se denomina HALVING. 81 | 82 | 4. La división a la mitad del incentivo se detiene cuando llega a la 100 millonesima parte (o lo que es 1 sat), terminando la emisión de nuevos bitcoins en la red. 83 | 84 | 5. La salida temporal de nuevos bloques es altamente variable con un valor esperado de 10 minutos. Si se hace una cuenta rápida cada 210 mil bloques representa cerca de 4 años. 85 | 86 | 6. Una cuenta rápida muestra que todo el bitcoin existente se emitirá hasta el año 2140. Luego de este el beneficio para seguir generando nuevos bloques será unicamente la comisión que los usuarios decidan dar al productor, recirculando la cantidad fija de bitcoin existentes. 87 | 88 | 89 | INSERTAR GRAFICO DE EMISION Y EXPLICAR CADA PERIODO COMO HALVING 90 | 91 | 92 | 93 | 94 | 95 | 96 | \section{Como se pensó la red} 97 | 98 | La red se pensó para tener un número finito de cuentas pero humanamente infinito, el número de cuentas se pensó para que cualquiera pueda tener una cuenta pensando solo en un número de 256 cifras binarias(uno o cero), equivalente a un número de 77 cifras, a este número le vamos a llamar contraseña privada,la cantidad máxima de números posibles con 256 ceros y unos es: 99 | 100 | \begin{equation} 101 | 2^{256} -1= N_{max} 102 | \end{equation} 103 | 104 | 105 | $N_{max}$ representa el límite superior de cuentas posibles, y es tan inmenso que incluso si alguien creara 10 cuentas por segundo, llevaría más de un siglo para agotar todas las posibles cuentas. Debido a la extraordinaria magnitud de este número, si una persona eligiera un número al azar, la probabilidad de que otra persona elija el mismo es extremadamente remota, casi inexistente. 106 | \\ 107 | Tras elegir un número este pasa a ser transformado mediante la criptografía de curva elíptica por sus siglas en ingles(ECC), la curva que se usa en la red bitcoin es secp256k1 (Standards of Efficient Cryptography), el número 256 indica la longitud en bits del campo primo p , el termino k1 indica que es la primera curva de este tipo recomendada por el SECG (Standards of Efficient Cryptography Group),que corresponde a la ecuación: 108 | \\ 109 | \begin{equation} 110 | \centering 111 | y^{2} = X^{3}+7(mod \quad p) 112 | \end{equation} 113 | \\ 114 | Donde la operación mod(p) garantiza que todos los cálculos se realizan dentro de un campo finito que es conjunto finito de elementos sobre el cual se pueden realizar operaciones aritméticas básicas, p es un número primo grande. 115 | \\ 116 | \begin{figure} [h] 117 | \subfloat[$y^{2} = X^{3}+7$]{\includegraphics[width=0.2\textwidth]{ECC_continuo.png}} 118 | \hspace{1cm} 119 | \subfloat[$y^{2} = X^{3}+7(mod p)$]{\includegraphics[width=0.2\textwidth]{ECC_discreto.png}} 120 | 121 | \caption{Curva elíptica $y^{2} = X^{3}+7$ a) campo continuo b) campo finito } 122 | 123 | \end{figure} 124 | \\ 125 | El número que nosotros elegimos como clave privada se multiplica por un número generador G(x,y), y se procede a calcular su suma de la siguiente manera, si nosotros elegimos el número n , este se calcula como n*G como la suma de G así mismo n veces. El número generador en base hexadecimal y en base decimal que se utiliza es : 126 | \\ 127 | {\small 128 | G=(0x79BE667EF9DCBBAC55A06295CE870B0 129 | \\7029BFCDB2DCE28D959F2815B16F81798, 130 | \\0x483ADA7726A3C4655DA4FBFC0E1108A 131 | 8FD17B448A68554199C47D08FFB10D4B8).\\ 132 | 133 | } 134 | que en base decimal es: 135 | {\small 136 | G=(5506626302227734366957871889516853432625 137 | 0603453777572733226205679730040724416, 138 | \\3267051002075881697808308513050704318447 139 | 1273380659243275938904335757337482424).\\ 140 | 141 | } 142 | 143 | Dicho algoritmo transforma un número en una salida que no se puede predecir y además es irreversible, es decir no se pueden obtener las entradas(clave privada) conociendo la salida y si nosotros solo tuviéramos acceso a esas salidas no podríamos decir que número produce dicha salida. 144 | 145 | Por ultimo necesitamos una dirección publica, la cual se puede obtener a través del algoritmo SHA-256 (Secure Hash Algorithm 256 bits), el cual es un algoritmo que transforma cualquier conjunto de datos en una cadena de salida fija de 256 bits es decir 64 caracteres a esta salida s la denomina Hash. 146 | \\ 147 | Es importante distinguir entre estos dos conceptos en el contexto de Bitcoin: SHA-256 es utilizado para operaciones Hash, mientras que la criptografía de curva elíptica se utiliza para la generación y verificación de claves. 148 | \\ 149 | También se debe entender como funcionan los nodos de la red, al igual que cualquier persona puede crearse una clave privada y una dirección, cualquier persona es capaz de conectar su nodo a la red, las acciones que este nodo puede hacer dentro la red son: 150 | \\ 151 | \textbf{Validación de bloques:} Los nodos pueden verificar y validar todas las transacciones que se envían a la red, verifican que las firmas digitales sean validas y que los fondos no se gasten dos veces 152 | \\ 153 | \textbf{Mantenimiento del libro mayor:} Cada nodo mantiene una copia de la cadena de bloques y esta información es publica. 154 | \\ 155 | \textbf{Difusión de información:} Los nodos trasmiten nuevas transacciones y bloques validados a otros nodos, esto mantiene la red actualizada. 156 | \\ 157 | \textbf{Acceso a la red:} Cada nodo permite interactuar con la red y consultar los estados de la red, estos estados se verifican a través de los UTXO (Unspent Transaction Output), que representan la parte no gastada de una transacción, cada entrada de una transacción se dirige a un UTXO y cada uno de estos tiene una cantidad de Bitcoin asociada y se puede usar en una nueva transacción, estos UTXO's son esenciales para el seguimiento y seguridad de la red, así se verifican que las entradas de una transacción apunten a un UTXO valido y que la cantidad de Bitcoin gastada no exceda los UTXO de entrada, de esta manera se garantiza que no se puedan crear Bitcoin de la nada y que estos no se gasten dos veces. 158 | \\ 159 | \textbf{Participación en la minería:} Un nodo se puede configurar para que participe en la minería de Bitcoin, estos nodos se conocen como mineros, esto implica competir para agregar nuevos bloques a la cadena, para lograr esto deben resolver pruebas de Hash y de tener éxito se obtiene una recompensa en forma de monedas digitales Bitcoin. 160 | \\ 161 | \section{Nodos configurados como mineros} 162 | Los nodos reciben múltiples solicitudes de transacciones, las cuales, después de ser validadas por los nodos, se almacenan en una memoria temporal y pública conocida como mempool o pool de transacciones sin confirmar. Cuando un minero decide crear un nuevo bloque, selecciona ciertas transacciones de esta memoria, generalmente priorizando aquellas que ofrecen una comisión más alta. A esta comisión se le denomina "fee de transacción", donde "fee" es un término inglés que significa "comisión". 163 | \\ 164 | Posteriormente se procede a la resolución de las pruebas de Hash que se realizan empezando por ver la dificultad de la red, esta dificultad es una condición que impone la red y consiste en que el Hash que se saque al siguiente bloque debe comenzar por una cantidad de ceros impuesta por la red, para poder crear un Hash que tenga la cantidad de ceros impuesta por la red se debe agregar al encabezado del bloque un número de 32 bits conocido como 'nonce' por su significado en ingles(number used once), este valor se va cambiando en el bloque a medida que se le saca el Hash una y otra vez hasta que produzca un Hash que cumpla el número de ceros de la red, puede darse el caso de que al pasar por todos lo valores posibles del nonce $2^{32}-1$, no se produzca ningún Hash con la cantidad de ceros necesarios, en ese caso el minero saca algunas transacciones de su bloque y adhiere otras de la memoria mempool y comienza nuevamente a realizar las pruebas de Hash, en caso de que este logre crear un bloque que cumpla con la dificultad de la red este bloque contendrá la información de las transacciones y además se obtendrá una recompensa por el minado de este bloque, la recompensa se llama Coinbase y el minero agrega estas monedas recompensa del Coinbase a una dirección(generalmente la dirección del minero), posteriormente este nuevo bloque pasa a ser verificado y difundido en la red a través de los nodos. 165 | \\ 166 | Puede darse el caso que dos nodos en distintas partes del mundo logren crear un bloque valido para la red, en ese caso se sigue la regla de la cadena mas larga y el bloque que se queda en la red sera el bloque que logre ser validado por mas nodos, y la red se quedara con el bloque sobre el que se escriba otro bloque valido. 167 | \\ 168 | Visto de esta manera es erróneo pensar en la prueba de Hash como trabajo realizado o como la resolución de rompecabezas criptográficos o como el calculo de algún problema, pues la manera en la que se crean los nuevos bloques en la cadena esta fuertemente ligado al azar, es decir tener mas poder computacional te asegura realizar mas Hashe's por segundo pero no te garantiza el minado del bloque y al no ser poder de calculo su coste es netamente el precio de la energía. 169 | \\ 170 | La red ajusta la dificultad en función al tiempo de minado de los bloques de manera que se trate de tener un promedio en el tiempo de llegada entre bloques de 10 minutos este ajuste ocurre cada 2016 bloques que corresponde a 14 días aproximadamente. 171 | 172 | \begin{figure} [h] 173 | \includegraphics[width=0.5\textwidth]{supply_btc_estilo_blanco.png} 174 | 175 | \caption{Cantidad de BTC minados.-Al igual que la red ajusta la dificultad también ajusta la recompensa del Coinbase cada 210000 bloques, a este evento se lo denomina Halving, y reduce la recompensa por minado de los bloques a la mitad, la red empezó en 50BTC por minado de Bloque. } 176 | 177 | 178 | \end{figure} 179 | 180 | 181 | \section{Análisis de la red} 182 | Para realizar un análisis histórico de la red, se tuvo acceso a un nodo dentro de la red y mediante este se obtuvieron los datos de los bloques de la red. 183 | 184 | \begin{figure} [h] 185 | \includegraphics[width=0.6\textwidth]{blocksize_estilo_blanco.png} 186 | 187 | \caption{Tamaño en Bits por bloque desde el inicio de la red hasta el presente.- } 188 | El día de la pizza se refiere al día en que un hombre pago con 10.000 bitcoin una pizza en ese tiempo valuados en 41 dolares.- 189 | Segwit activate fue el día en que se hizo el Segregated Witness que fue una implementación de un soft fork (un cambio leve ) a la red con el fin de cambiar el formato de las transacciones en bitcoin. 190 | Taproot fue una de las implementaciones mas significativas en la red y fue hecha con el fin de acelerar la verificación de las transacciones, durante su implementación conllevo a una especie de guerra civil entre los nodos que querían actualizar y aceptar esta implementación y los que no lo que llevo a la creación de Bitcoin Cash como otra moneda y como una nueva red.- 191 | Ordinals BRC-20 fue la implementación de una idea, ya que las transacciones permiten almacenar información dentro de su Scritp Sig que es la parte de la transacción que contiene la información de la firma y es utilizado para la creación del UTXO, es posible usar este pequeño espacio para guardar información, entre los usos que le dieron están el guardar frases,pequeños poemas e incluso la información de los metadatos de NFT's (Non Fungible Token), esto significo un incremento en el tamaño de los bloques. 192 | 193 | \end{figure} 194 | 195 | \begin{figure} [h] 196 | \includegraphics[width=0.5\textwidth]{dificultad_total_estilo_blanco.png} 197 | 198 | \caption{Evolución de la dificultad desde el inicio de la red.-Hasta el bloque 811298, el máximo histórico fue de 57,32 T hasta el 3 de octubre de 2023, además que la caída del tamaño de los bloques pudo ser causado por la prohibición de China del minado de criptomonedas esta acción fue llevada a cabo en agosto del 2021. } 199 | 200 | \end{figure} 201 | 202 | 203 | \begin{figure*} 204 | \centering 205 | \includegraphics[width=1\textwidth]{dificultad_halv_estilo_blanco.png} 206 | 207 | \caption{Evolución de la dificultad por Halving.- GPU Art Fozt (Graphics Processing Unit) se refiere al uso de tarjetas gráficas domésticas, FPGA (Field Programmable Gate Arrays) se refiere a circuitos internos de matrices programables, y el término ASIC (Application-Specific Integrated Circuit) hace referencia a circuitos dedicados exclusivamente a la minería de criptomonedas. Desde su implementación, la mejora de estos dispositivos ha estado centrada en hacerlos más pequeños y mejorar su rendimiento para que consuman menos energía. } 208 | 209 | \end{figure*} 210 | 211 | 212 | \begin{figure*} 213 | \includegraphics[width=1\textwidth]{hashrate_estilo_blanco.png} 214 | 215 | \caption{Tamaño en Bits por bloque desde el inicio de la red hasta el presente.-Hashrate es la cantidad de intentos que se llevaron a cabo para crear un hash que cumpla con los requisitos de complejidad exigidos por la red a través de la dificultad, estos valores son estimados a partir de los datos de la dificultad(la dificultad se mide en términos de hashes o intentos de hash por bloque) y el chainwork (cantidad de esfuerzo computacional empleado en la creación de los bloques dentro la red). } 216 | \end{figure*} 217 | 218 | 219 | 220 | \begin{figure*} 221 | \includegraphics[width=1\textwidth]{Numero_de_transacciones_estilo_dark.png} 222 | 223 | \caption{Evolución del número de transacciones de cada bloque desde el inicio de la red-Se debe notar que el máximo histórico sucedió en el segundo halving la cantidad máxima de transacciones que puede tener un bloque esta condicionado a 1 MB de información- Acumulado del número de transacciones } 224 | \end{figure*} 225 | 226 | 227 | 228 | \begin{figure*} 229 | \includegraphics[width=1\textwidth]{Numero_de_transacciones_halv_estilo_dark.png} 230 | 231 | \caption{Número de Bloques contra número de transacciones en intervalos .-Al inicio de la red los bloques se subían con una transacción perteneciente al coinbase. El máximo histórico se sale de la gráfica, pero pertenece al segundo Halving } 232 | \end{figure*} 233 | 234 | 235 | \begin{figure*} 236 | \includegraphics[width=1\textwidth]{timestamp_estilo_blanco.png} 237 | 238 | \caption{Tiempo de llegada de cada bloque Azul,Diferencia de tiempo entre el bloque y el siguiente.-Se debería esperar que los bloques lleguen con tiempos de llegada consecutivos pero existen bloques que se subieron con tiempos de llegada anteriores al de la red Naranja.-Estas anomalías temporales aparecen desde el primer Halving, pero suceden con menos frecuencia al día de hoy y su diferencia de tiempo es demasiado pequeña, su existencia puede estar ligada a momentos en que la red sufrió forks o incluso se reporto que algunos bloques podían ser subidos a la red de manera intencional con esta anomalía temporal } 239 | \end{figure*} 240 | 241 | \begin{figure*} 242 | \includegraphics[width=1\textwidth]{pricHash_estilo_dark.png} 243 | 244 | \caption{Análisis histórico del precio de Bitcoin en dólares: la red de Bitcoin no proporciona datos sobre el precio de Bitcoin, ya que éste es externo y se define a través de la relación entre la oferta y la demanda. El hashrate es un factor crucial para determinar el precio, ya que un consumo excesivo de energía puede hacer que la minería de Bitcoin deje de ser rentable. } 245 | \end{figure*} 246 | \section{Conclusiones} 247 | 248 | \begin{thebibliography} 249 | 250 | \bibitem[{Charles (1993)}] {charles} 251 | Charles M.(1993), Revista de la Sociedad Española de Historia de las Ciencias y de las T{\'e}cnicas {\bf 16} 241 252 | 253 | \bibitem[{Bielajew (2001)}] {biela} 254 | Bielajew A. (2001), Some random thoughts on Monte Carlo electron and photon transport (Springer) 255 | 256 | 257 | 258 | \end{thebibliography} 259 | 260 | 261 | \end{document} --------------------------------------------------------------------------------