Flutter比特币轻客户端插件bitcoin_light_client的使用

Flutter比特币轻客户端插件bitcoin_light_client的使用

概述

bitcoin_light_client 是一个用 Dart 编写的比特币 P2P 轻客户端。它通过连接到全节点来收集数据,并发送/接收支付。该库的设计考虑到了可扩展性。

示例代码

以下是一个完整的示例,展示了如何使用 bitcoin_light_client 插件在 Flutter 中实现比特币轻客户端功能。

示例代码

// Copyright (c) 2021-2023 Kolby Moroz Liebl
// Distributed under the MIT software license, see the accompanying
// file LICENSE or http://www.opensource.org/licenses/mit-license.php.

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:bitcoin_light_client/util.dart';
import 'package:bitcoin_light_client/bitcoin_light_client.dart';
import 'package:crypto/crypto.dart';

Map<List<int>, CAnonMsg> mapAnonMsg = Map<List<int>, CAnonMsg>();

// 自定义消息类型
String anonmsg = "anonmsg";
String getanonmsg = "getanonmsg";

class CAnonMsg {
  int msgTime;
  int version = 1;
  String msgData;

  CAnonMsg() {
    msgTime = 0;
  }

  void setMessage(String msgContent) {
    msgTime = new DateTime.now().millisecondsSinceEpoch ~/ 1000;
    msgData = msgContent;
  }

  String getMessage() {
    return msgData;
  }

  int getTimestamp() {
    return msgTime;
  }

  String toString() {
    String string = ('CAnonMsg msgTime: $msgTime, msgData: $msgData');
    return string;
  }

  List<int> getHash() {
    return sha256.convert(sha256.convert(serialize()).bytes).bytes;
  }

  List<int> serialize() {
    List<int> messageData = uint64ToListIntLE(msgTime) + [utf8.encode(msgData).length] + utf8.encode(msgData);
    return messageData;
  }

  void deserialize(List<int> data) {
    msgTime = listIntToUint64LE(data.sublist(0, 8));
    int msgDataLength = listIntToUint8LE(data.sublist(8, 9));
    msgData = utf8.decode(data.sublist(9, 9 + msgDataLength.abs()), allowMalformed: true);
  }
}

class OmegaMessageNodes extends MessageNodes {
  OmegaMessageNodes(Socket inputsocket) : super(inputsocket);

  void customAddNode(String ip, [port = null]) {
    addOmegaNode(ip, port);
  }

  void extendVersion() {
    sendGetAnonMessage();
  }

  List<CInv> extendInv(List<CInv> msgInvData, List<CInv> msgGetDataInvData) {
    for (CInv k in msgInvData) {
      if (k.type == 20) {
        for (List<int> i in mapAnonMsg.keys) {
          if (IterableEquality().equals(k.hash, i)) {
            break;
          }
        }
        msgGetDataInvData.add(k);
      }
    }
    return msgGetDataInvData;
  }

  void extendCommandsSupported(MsgHeader msgHeader) {
    if (msgHeader.command == anonmsg) {
      CAnonMsg cAnonMsg = new CAnonMsg();
      cAnonMsg.deserialize(msgHeader.payload);

      // 如果我们已经拥有此消息,则返回并不要添加它。
      if (mapAnonMsg.isNotEmpty) {
        for (var k in mapAnonMsg.keys) {
          if (IterableEquality().equals(k, cAnonMsg.getHash())) {
            return;
          }
        }
      }

      // 如果消息超过一天旧,则不要添加。
      if ((cAnonMsg.msgTime + 24 * 60 * 60) < DateTime.now().millisecondsSinceEpoch ~/ 1000) {
        return;
      }

      mapAnonMsg[cAnonMsg.getHash()] = cAnonMsg;

      MsgHeader anonMsgMessage = new MsgHeader();
      anonMsgMessage.command = anonmsg;
      anonMsgMessage.payload = cAnonMsg.serialize();
      relayMessage(anonMsgMessage.serialize());
    } else if (msgHeader.command == getanonmsg) {
      mapAnonMsg.values.forEach((cAnonMsg) {
        MsgHeader anonMsgMessage = new MsgHeader();
        anonMsgMessage.command = anonmsg;
        anonMsgMessage.payload = cAnonMsg.serialize();

        // 发送消息
        pushMessage(anonMsgMessage.serialize());
      });
    } else {
      // 我们不支持此命令,可以添加代码告诉节点忽略我们或类似的操作
    }
  }
}

void sendGetAnonMessage() {
  nodes.forEach((messageNodes) {
    MsgHeader getAnonMessage = new MsgHeader();
    getAnonMessage.command = getanonmsg;

    // 发送消息
    messageNodes.pushMessage(getAnonMessage.serialize());
  });
}

void sendAnonMessage(String message) {
  bool didWeAddMessage = false;
  nodes.forEach((messageNodes) {
    MsgHeader sendAnonMessage = new MsgHeader();
    CAnonMsg cAnonMsg = CAnonMsg();
    sendAnonMessage.command = anonmsg;
    cAnonMsg.setMessage(message);
    sendAnonMessage.payload = cAnonMsg.serialize();

    if (!didWeAddMessage) {
      mapAnonMsg[cAnonMsg.getHash()] = cAnonMsg;
      didWeAddMessage = true;
    }

    // 发送消息
    messageNodes.pushMessage(sendAnonMessage.serialize());
  });
}

void updateAnonMessage() {
  nodes.forEach((messageNodes) {
    MsgHeader getanonMessage = new MsgHeader();
    getanonMessage.command = getanonmsg;

    // 发送消息
    messageNodes.pushMessage(getanonMessage.serialize());
  });
}

void removeOldAnonMessage() {
  mapAnonMsg.keys.forEach((element) {
    if (((mapAnonMsg[element].getTimestamp() + 24 * 60 * 60) < (DateTime.now().millisecondsSinceEpoch ~/ 1000))) {
      mapAnonMsg.remove(element);
    }
  });
}

void startOmegaNode({Configuration configuration = null}) {
  if (configuration == null) {
    config = Configuration();
  } else {
    config = configuration;
  }

  // 获取节点列表
  fetchNodeList().then((value) {
    nodeList = value.listOfNodes;

    // 代码开始

    for (var k in nodeList.keys) {
      addOmegaNode(k, nodeList[k]);
    }

    for (var i = 0; i < nodes.length; i++) {
      sendGetAddrMessage(nodes[i]);
    }

    Timer.periodic(Duration(seconds: 20), (timer) {
      if (nodes.length <= 6) {
        for (var i = 0; i < nodes.length; i++) {
          sendGetAddrMessage(nodes[i]);
        }
      }
      // 如果节点数量为零,尝试从节点列表中再次添加节点
      if (nodes.length == 0) {
        for (var k in nodeList.keys) {
          addOmegaNode(k, nodeList[k]);
        }
      }
    });
  });
}

Future<NodeListClass> fetchNodeList() async {
  final response = await http.get(Uri.parse('https://omegablockchain.net/omeganodelist.json'));
  if (response.statusCode == 200) {
    print(jsonDecode(response.body));
    return NodeListClass.fromJson(json.decode(response.body));
  } else {
    print('Something went wrong. \nResponse Code : ${response.statusCode}');
    throw Error();
  }
}

int periodCount = 0;
bool addOmegaNode(String ip, [port = null]) {

  if (port == null) {
    port = config.default_port;
  }

  periodCount++;
  if (periodCount >= config.connection_limit) {
    periodCount--;
    return false;
  }
  if (nodes.length > config.connection_limit) {
    periodCount--;
    return false;
  }

  // 如果节点已连接则不要添加它
  for (var i = 0; i < nodes.length; i++) {
    if (nodes[i].ip == ip) {
      periodCount--;
      return false;
    }
  }

  Socket.connect(ip, port).then((Socket socket) {
    handleOmegaConnection(socket, true);
    periodCount--;
    return true;
  }, onError: (e) {
    // 如果连接失败,返回 false
    print('Error $e');
    periodCount--;
    return false;
  });
}

void handleOmegaConnection(Socket node, bool didWeInitiateConnection){
  OmegaMessageNodes messageNodes = new OmegaMessageNodes(node);
  nodes.add(messageNodes);

  if (didWeInitiateConnection) {
    MsgHeader versionMessage = new MsgHeader();
    MsgVersion versionPayload = new MsgVersion();
    versionPayload.addr_from = CAddress.data(messageNodes.ip);
    versionMessage.command = version;
    versionMessage.payload = versionPayload.serialize();

    // 发送消息
    messageNodes.pushMessage(versionMessage.serialize());
    messageNodes.didWeSendVersion = true;
  }
}

使用说明

  1. 初始化配置
    • 首先,确保你的项目中已经包含了 bitcoin_light_clienthttp 依赖项。你可以在 pubspec.yaml 文件中添加这些依赖项:

      dependencies:
        flutter:
          sdk: flutter
        bitcoin_light_client: ^x.x.x
        http: ^0.13.3
      

更多关于Flutter比特币轻客户端插件bitcoin_light_client的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter比特币轻客户端插件bitcoin_light_client的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


bitcoin_light_client 是一个用于 Flutter 的插件,旨在帮助开发者在移动应用中实现比特币轻客户端功能。它允许应用程序与比特币网络进行交互,而无需下载完整的区块链数据。这对于移动设备尤其有用,因为它们通常存储和处理能力有限。

安装

首先,你需要在 pubspec.yaml 文件中添加 bitcoin_light_client 插件依赖:

dependencies:
  flutter:
    sdk: flutter
  bitcoin_light_client: ^0.1.0  # 请确保使用最新版本

然后运行 flutter pub get 来安装依赖。

基本用法

  1. 导入插件

    在你的 Dart 文件中导入 bitcoin_light_client 插件:

    import 'package:bitcoin_light_client/bitcoin_light_client.dart';
    
  2. 初始化客户端

    在使用插件之前,你需要初始化比特币轻客户端。通常你可以在 main() 函数或应用的初始化逻辑中完成:

    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await BitcoinLightClient.initialize();
      runApp(MyApp());
    }
    
  3. 连接到比特币网络

    你可以使用 BitcoinLightClient 类中的方法来连接到比特币网络。例如,你可以获取最新的区块高度:

    Future<void> fetchBlockHeight() async {
      int blockHeight = await BitcoinLightClient.getBlockHeight();
      print('Current Bitcoin block height: $blockHeight');
    }
    
  4. 获取地址余额

    你可以查询特定比特币地址的余额:

    Future<void> fetchAddressBalance(String address) async {
      BigInt balance = await BitcoinLightClient.getAddressBalance(address);
      print('Balance of $address: $balance satoshis');
    }
    
  5. 监听交易

    你可以监听特定地址的交易变更:

    void listenForTransactions(String address) {
      BitcoinLightClient.listenForTransactions(address, (transaction) {
        print('New transaction: $transaction');
      });
    }
    

进阶用法

  1. 发送交易

    你可以构建并发送比特币交易。首先,你需要构建一个交易对象,然后使用 bitcoin_light_client 发送它:

    Future<void> sendTransaction(String fromAddress, String toAddress, BigInt amount) async {
      String transactionId = await BitcoinLightClient.sendTransaction(fromAddress, toAddress, amount);
      print('Transaction sent with ID: $transactionId');
    }
    
  2. 使用自定义节点

    如果你不想使用默认的比特币节点,可以指定自定义的节点:

    void setupCustomNode(String nodeUrl) {
      BitcoinLightClient.setNodeUrl(nodeUrl);
    }
    

注意事项

  • 隐私与安全: 比特币轻客户端依赖于第三方节点来获取区块链数据。这意味着你的交易和地址信息可能会暴露给这些节点。请确保你信任所使用的节点,或者考虑使用你自己的节点。
  • 网络请求: 由于 bitcoin_light_client 需要与比特币网络进行交互,确保你的应用在断开连接或请求失败时能够优雅地处理错误。
  • 存储: 由于轻客户端不存储完整的区块链数据,但它可能需要存储一些关键信息(例如钱包状态),请确保你的应用有适当的存储管理。

常见问题

  1. 如何选择合适的比特币节点?

    • 你可以使用公共节点,例如 https://blockstream.info/api/,但建议使用你自己托管的节点以提高隐私性和安全性。
  2. 如何处理交易费用?

    • 交易费用通常会自动计算,但你可以手动指定。确保在发送交易时包含足够的费用以确保交易被快速确认。
  3. 如何调试问题?

    • 如果遇到问题,首先检查网络连接,然后查看插件的日志输出。你还可以查阅插件的 GitHub 仓库中的问题和讨论。

示例代码

以下是一个简单的示例,展示了如何使用 bitcoin_light_client 获取比特币地址的余额:

import 'package:flutter/material.dart';
import 'package:bitcoin_light_client/bitcoin_light_client.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await BitcoinLightClient.initialize();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Bitcoin Light Client Example'),
        ),
        body: Center(
          child: FutureBuilder<BigInt>(
            future: BitcoinLightClient.getAddressBalance('1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa'), // 中本聪的地址
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {
                return CircularProgressIndicator();
              } else if (snapshot.hasError) {
                return Text('Error: ${snapshot.error}');
              } else {
                return Text('Balance: ${snapshot.data} satoshis');
              }
            },
          ),
        ),
      ),
    );
  }
}
回到顶部