Learning-Bitcoin-from-the-C.../es/18_6_Accediendo_a_Bitcoind_con_Swift.md
2021-10-06 17:10:28 -05:00

19 KiB
Raw Permalink Blame History

18.6: Acceso a Bitcoind con Swift

NOTA: Esta sección se ha añadido recientemente al curso y es un borrador temprano que todavía puede estar pendiente de revisión. Lectura con advertencias.

Esta sección explica cómo interactuar con bitcoind usando el lenguaje de programación Swift y su propio cliente RPC.

Configurar Swift en su Mac

Hasta la fecha, ha construido todos sus entornos de desarrollo de lenguaje de programación alternativo en su nodo virtual de Debian. Sin embargo, esa no es la mejor plataforma para Swift. Aunque hay una versión de Swift disponible para las plataformas de Ubuntu, no está completamente disponible, y funciona un poco diferente de la Swift nativa de Mac. Una "variante" en la parte inferior de esta sección explica cómo configurarlo, pero tenga en cuenta que estará en un territorio desconocido.

En su lugar, sugerimos crear un entorno Swift óptimo en una Mac. Hay cuatro pasos importantes para hacerlo.

1. Instalar Xcode

Vas a necesitar Xcode, el entorno de desarrollo integrado para Swift y Objective-C. Eso se puede instalar fácilmente yendo a Mac App Store y consiguiendo Xcode.

Alternativa: Instalar a mano

Algunas personas aconsejan no instalar desde el App Store porque es algo como todo o nada; tampoco funcionará si sigue usando Mojave porque quiere evitar las incompatibilidades de Catalina. En ese caso, puede descargar directamente desde el area de Developer en Apple.

Si usted esta usando Mojave, necesitará el archivo xip para Xcode 10.3.1. De lo contrario, obtenga el más reciente.

Una vez descargado, puede hacer clic en el xip para extraerlo, y luego mover la aplicación Xcode a la carpeta Aplicaciones.

(De cualquier manera, debería tener Xcode instalado en su carpeta Aplicaciones al final de este paso.)

2. Instale el servidor Gordian

También va a necesitar un nodo Bitcoin en su Mac, para que pueda comunicarse con él. Técnicamente, se puede utilizar un nodo remoto y acceder a él con el inicio de sesión RPC y contraseña a través de la red. Sin embargo, le sugerimos que instale un nodo completo directamente en su Mac, porque es la configuración más segura y limpia, asegurando que ninguna de sus comunicaciones salga de su máquina.

Para instalar fácilmente un nodo completo en su Mac, utilice el servidor de Blockchain Commons GordianServer for MacOS. Vea las instrucciones de instalación en el README, pero generalmente todo lo que tiene que hacer es descargar el archivo actual dmg, abrirlo e instalar esa aplicación en su directorio de Aplicaciones también.

Después, ejecute la aplicación GordianServer y decirle que inicie Start Testnet.

🔗 TESTNET vs. MAINNET: O Start Mainnet.

3. Haga accesible su Gordian bitcoin-cli

Cuando quiera acceder al bitcoin-cli creado por GordianServer en su Mac local, puede encontrarlo en ~/.standup/BitcoinCore/bitcoin-VERSION/bin/bitcoin-cli, por ejemplo ~/.standup/BitcoinCore/bitcoin-0.20.1/bin/bitcoin-cli.

Es posible que desee crear un alias para eso:

alias bitcoin-cli="~/.standup/BitcoinCore/bitcoin-0.20.1/bin/bitcoin-cli -testnet"

🔗 TESTNET vs. MAINNET: Obviamente, el parámetro -testnet solo es necesario si se está ejecutando en testnet.

4. Encuentre su información de GordianServer

Finalmente necesitará su información rpcuser y rpcpassword. Eso está en ~/Library/Application Support/Bitcoin/bitcoin.conf por defecto en Gordian.

$ grep rpc ~/Library/Application\ Support/Bitcoin/bitcoin.conf
rpcuser=oIjA53JC2u
rpcpassword=ebVCeSyyM0LurvgQyi0exWTqm4oU0rZU
...

Construya su conexión a mano

En el momento de escribir esto, no hay una Biblioteca RPC de Bitcoin actualizada y fácil de usar que sea específica para Swift, algo que pueda introducir e inmediatamente empezar a usar. Por lo tanto, va a hacer algo que nunca ha hecho antes: construir una conexión RPC a mano.

Escriba el transmisor RPC

Esto solo requiere escribir una función que pase comandos RPC a bitcoind en el formato correcto:

func makeCommand(method: String, param: Any, completionHandler: @escaping (Any?) -> Void) -> Void {

Las conexiones de PC a bitcoind usan el protocolo HTML, lo que significa que necesita hacer tres cosas: crear una URL; hacer una URLRequest; e iniciar una URLSession.

1. Crear una URL

Dentro de la función, necesita crear una URL desde su IP, puerto, rpcuser, rpcpassword, y cartera:

    let testnetRpcPort = "18332"
    let nodeIp = "127.0.0.1:\(testnetRpcPort)"
    let rpcusername = "oIjA53JC2u"
    let rpcpassword = "ebVCeSyyM0LurvgQyi0exWTqm4oU0rZU"
    let walletName = ""

La conexión RPC real a Bitcoin Core se construye utilizando una URL del formato "http://rpcusername:rpcpassword@nodeIp/walletName":

    let walletUrl = "http://\(rpcusername):\(rpcpassword)@\(nodeIp)/\(walletName)"

    let url = URL(string: walletUrl)

Esto significa que las variables de muestra resultan en la siguiente URL:

http://oIjA53JC2u:ebVCeSyyM0LurvgQyi0exWTqm4oU0rZU@127.0.0.1:18332/

Que debería parecerse mucho a la URL utilizada en algunas de las secciones anteriores para conexiones RPC.

2. Crear una URLRequest

Con esa URL en su mano, ahora puede crear una URLRequest, con el método POST y el tipo de contenido text/plain. El cuerpo HTTP es entonces el objeto JSON familiar que ha estado enviando cada vez que se conectas directamente a los puertos RPC de Bitcoin Core, como se demostró por primera vez al usar Curl en §4.4.

    var request = URLRequest(url: url!)
    request.httpMethod = "POST"
    request.setValue("text/plain", forHTTPHeaderField: "Content-Type")
    request.httpBody = "{\"jsonrpc\":\"1.0\",\"id\":\"curltest\",\"method\":\"\(method)\",\"params\":[\(param)]}".data(using: .utf8)

3. Crear una URLSession

Finalmente, está listo para construir una URLSession alrededor de su URLRequest.

    let session = URLSession(configuration: .default)
    let task = session.dataTask(with: request as URLRequest) { data, response, error in

El manejador de completitud para dataTask necesita comprobar si hay errores:

        do {

            if error != nil {

                    //Handle the error
                    
            } else {

Y luego analiza los datos que está recibiendo. Aquí, está leyendo de los resultados de JSON en un NSDictionary:

 if let urlContent = data {
                        
                    do {
                            
                        let json = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableLeaves) as! NSDictionary

Después de eso, hay más manejo de errores y más manejo de errores y entonces usted puede eventualmente devolver el diccionario result usando el completionHandler que definió para la nueva función makeCommand :

                        if let errorCheck = json["error"] as? NSDictionary {
                                                                
                            if let errorMessage = errorCheck["message"] as? String {

                                print("FAILED")
                                print(errorMessage)

                            }
                                
                        } else {
                                
                            let result = json["result"]
                            completionHandler(result)
                            
                        }
                            
                    } catch {
                            
                            //Handle error here
                            
                    }

Por supuesto que eventualmente tiene que decirle a la task para empezar:

    task.resume()

Y eso es "todo" lo que hay que hacer para dicha interacción RPC a mano usando un lenguaje de programación como Swift.

🙏 THANKS: Gracias a @Fonta1n3 quien facilitó el código principal para nuestro transmisor RPC.

Hacer una llamada RPC

Después de haber escrito la función makeCommand RPC, puede enviar una llamada RPC ejecutándola. Aquí está getblockchaininfo:

let method = "getblockchaininfo"
let param = ""

makeCommand(method: method,param: param) { result in
    
    print(result!)

}

Hacer una llamada RPC con argumentos

De manera similar, podría tomar el conteo de bloques actual de esa información y usarlo para (redundantemente) obtener el hash del bloque actual, usando el parámetro param :

let method = "getblockchaininfo"
let param = ""

makeCommand(method: method,param: param) { result in
    
    let blockinfo = result as! NSDictionary
    let block = blockinfo["blocks"] as! NSNumber
    
    let method = "getblockhash"
    makeCommand(method: method,param: block) { result in
        print("Blockhash for \(block) is \(result!)")
    }
    
}

Ejecute su código

El código completo está disponible en el directorio src. Cárgalo en su entorno Xcode y luego "Editor -> Run Playground" y obtendrá resultados como:

{	
    bestblockhash = 00000000000000069725608ebc5b59e520572a8088cbc57ffa5ba87b7f300ac7;
    blocks = 1836745;
    chain = test;
    chainwork = 0000000000000000000000000000000000000000000001cc3e9f8e0bc6b71196;
    difficulty = "16508683.81195478";
    headers = 1836745;
    initialblockdownload = 0;
    mediantime = 1601416765;
    pruned = 0;
    "size_on_disk" = 28205538354;
    softforks =     {
        bip34 =         {
            active = 1;
            height = 21111;
            type = buried;
        };
        bip65 =         {
            active = 1;
            height = 581885;
            type = buried;
        };
        bip66 =         {
            active = 1;
            height = 330776;
            type = buried;
        };
        csv =         {
            active = 1;
            height = 770112;
            type = buried;
        };
        segwit =         {
            active = 1;
            height = 834624;
            type = buried;
        };
    };
    verificationprogress = "0.999999907191804";
    warnings = "Warning: unknown new rules activated (versionbit 28)";
}
Blockhash for 1836745 is 00000000000000069725608ebc5b59e520572a8088cbc57ffa5ba87b7f300ac7

Buscar fondos

Con su nuevo makeCommand para funciones RPC, también puede ejecutar un comando como getwalletinfo o getbalance:

var method = "getwalletinfo"
var param = ""

makeCommand(method: method,param: param) { result in
    
    print(result!)
    
}

method = "getbalance"
makeCommand(method: method,param: param) { result in

    let balance = result as! NSNumber
    print("Balance is \(balance)")

}

Lo que nos dice:

Balance is 0.01
{
    "avoid_reuse" = 0;
    balance = "0.01";
    hdseedid = bf493318f548df8e25c390d6a7f70758fd6b3668;
    "immature_balance" = 0;
    keypoololdest = 1599723938;
    keypoolsize = 999;
    "keypoolsize_hd_internal" = 1000;
    paytxfee = 0;
    "private_keys_enabled" = 1;
    scanning = 0;
    txcount = 1;
    "unconfirmed_balance" = 0;
    walletname = "";
    walletversion = 169900;
}

Crear una dirección

Crear una dirección es bastante simple, pero ¿qué pasa con crear una dirección heredada con una etiqueta específica? Eso requiere dos parámetros en su llamada RPC.

Dado que la función simplista makeCommand en esta sección simplemente pasa su params como las entrañas de un objeto JSON, todo lo que tiene que hacer es formatear correctamente esas entrañas. Esta es una manera de hacerlo:

method = "getnewaddress"
param = "\"learning-bitcoin\", \"legacy\""

makeCommand(method: method,param: param) { result in

    let address = result as! NSString
    print(address)
}

Ejecutar esto en el entorno de Xcode produce un resultado:

mt3ZRsmXHVMMqYQPJ8M74QjF78bmqrdHZF

Ese resultado es obviamente una dirección Legacy; su etiqueta se puede comprobar desde la línea de comandos:

$ bitcoin-cli getaddressesbylabel "learning-bitcoin"
{
  "mt3ZRsmXHVMMqYQPJ8M74QjF78bmqrdHZF": {
    "purpose": "receive"
  }
}

Éxito!

NOTA: Como decimos a menudo en estos ejemplos de codificación, un programa del mundo real sería mucho más sofisticado. En particular, querrá ser capaz de enviar un objeto JSON real como parámetro, y luego tener su programa makeCommand para analizarlo e introducirlo en la URLSession apropiadamente. Lo que tenemos aquí maximiza la legibilidad y la simplicidad sin centrarse en la facilidad de uso.

Enviar una transacción

Como de costumbre, el envío de una transacción (la forma difícil) es un proceso de varios pasos: 0. Generar o recibir una dirección de recepción

  1. Encontrar una UTXO no utilizada
  2. Crear una transacción en bruto
  3. Firmar la transacción en bruto
  4. Envío de la transacción en bruto

Usará la dirección que generó en el paso anterior como su destinatario.

1. Encuentre un UTXO no gastado

El RPC listunspent le permite encontrar su UTXO:

    method = "listunspent"
    param = ""
    
    makeCommand(method: method,param: param) { result in

        let unspent = result as! NSArray
        let utxo = unspent[0] as! NSDictionary
        
        let txid = utxo["txid"] as! NSString
        let vout = utxo["vout"] as! NSInteger
        let amount = utxo["amount"] as! NSNumber
        let new_amount = amount.floatValue - 0.0001

Como en otros ejemplos, usted va a tomar arbitrariamente el 0º UTXO, y usará las txid, vout, y amount de ella.

:information_source NOTA: Una vez más, un programa de la vida real sería mucho más sofisticado.

2. Crear una transacción sin procesar

Crear una transacción en bruto es la cosa más complicada porque necesita obtener todos sus objetos JSON, matrices y comillas correctas. He aquí cómo hacerlo en Swift, usando el formato muy básico param del transmisor:

        method = "createrawtransaction"
        param="[ { \"txid\": \"\(txid)\", \"vout\": \(vout) } ], { \"\(address)\": \(new_amount)}"
        makeCommand(method: method,param: param) { result in

            let hex = result as! NSString

3. Firme la transacción cruda

La firma de su transacción sólo requiere que ejecute la transacción signrawctionwithwallet RPC, usando su nuevo hex:

            method = "signrawtransactionwithwallet"
            param = "\"\(hex)\""
            
            makeCommand(method: method,param: param) { result in

                let signedhexinfo = result as! NSDictionary
                let signedhex = signedhexinfo["hex"] as! NSString

4. Enviar la transacción sin procesar

El envío de su transacción es igualmente simple:

                method = "sendrawtransaction"
                param = "\"\(signedhex)\""

                makeCommand(method: method,param: param) { result in

                    let new_txid = result as! NSString
                    print("TXID: \(new_txid)")
                    
                }
            }         
        }
    }
}

El código para este remitente de transacción se puede encontrar en el src directory.

Usar Swift de otras maneras

Eso cubre nuestras discusiones habituales sobre la programación de Bitcoin RPC en un lenguaje, pero Swift es un lenguaje particularmente importante ya que se puede implementar en dispositivos móviles, uno de los principales lugares para carteras. Como tal, puede considerar otras bibliotecas:

Resumen: Acceso a Bitcoind con Swift

Swift es un robusto lenguaje de programación moderno que desafortunadamente aún no tiene bibliotecas RPC fáciles de usar ... lo que nos dio la oportunidad de escribir una función de acceso RPC propia. Con eso en la mano, puede interactuar con bitcoind en un Mac o construir aplicaciones complementarias en un iPhone, que es una combinación perfecta para el trabajo Bitcoin desconectado o airgapped.

¿Qué es lo siguiente?

Aprenda acerca de Lightning en Capítulo 19: Entendiendo su configuración de Lightning.

Variante: Implementar Swift en Ubuntu

Si prefiere implementar Swift en Ubuntu, puede hacerlo, aunque la funcionalidad no es la misma. Parte del código en este capítulo probablemente generará errores que necesitará resolver, y también necesitará hacer más trabajo para enlazar en bibliotecas C.

Para empezar, instale algunas necesarias bibliotecas Debian :

$ sudo apt-get install clang
$ sudo apt-get install libcurl4 libpython2.7 libpython2.7-dev

Si está usando Debian 10 o superior (y realmente debería estarlo), también necesitará poner en una fecha anterior algunas bibliotecas para obtener versiones anteriores:

$ sudo apt-get install libtinfo5 libncurses5

Luego puede descargar e instalar Swift:

$ wget https://swift.org/builds/swift-5.1.3-release/ubuntu1804/swift-5.1.3-RELEASE/swift-5.1.3-RELEASE-ubuntu18.04.tar.gz
$ tar xzfv swift-5.1.3-RELEASE-ubuntu18.04.tar.gz 
$ sudo mv swift-5.1.3-RELEASE-ubuntu18.04 /usr/share/swift

Para poder usar su nueva configuración de Swift, y necesita actualizar su PATHen su .bashrc:

$ echo "export PATH=/usr/share/swift/usr/bin:$PATH" >> ~/.bashrc
$ source ~/.bashrc

Ahora puede probar Swift con el argumento -versión :

$ swift --version
Swift version 5.1.3 (swift-5.1.3-RELEASE)
Target: x86_64-unknown-linux-gnu

Crear un proyecto

Una vez que haya instalado Swift en su máquina Ubuntu, puede crear proyectos con el comando package init :

$ mkdir swift-project
$ cd swift-project/
/swift-project$ swift package init --type executable
Creating executable package: swift-project
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/swift-project/main.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/swift-projectTests/
Creating Tests/swift-projectTests/swift_projectTests.swift
Creating Tests/swift-projectTests/XCTestManifests.swift

Luego editará Sources/.../main.swift y cuando esté listo para compilar, puede usar el comando build :

$ swift build
[4/4] Linking swift-project

Finalmente, podrá ejecutar el programa desde el directorio .build/debug :

$ .build/debug/swift-project
Hello, world!

Buena suerte!