以太坊开发(二十六)根据账号/合约地址查询交易详情

1. 使用web3.js查询

在以太坊中,想要查询交易详情,只能使用交易hash,调用web3.js的方法web3.eth.getTransaction(transactionHash [, callback])来查询。

官方示例:

web3.eth.getTransaction('0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b§234')
.then(console.log);

> {
    "hash": "0x9fc76417374aa880d4449a1f7f31ec597f00b1f6f3dd2d66f4c9c6c445836d8b",
    "nonce": 2,
    "blockHash": "0xef95f2f1ed3ca60b048b4bf67cde2195961e0bba6f70bcbea9a2c4e133e34b46",
    "blockNumber": 3,
    "transactionIndex": 0,
    "from": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b",
    "to": "0x6295ee1b4f6dd65047762f924ecd367c17eabf8f",
    "value": '123450000000000000',
    "gas": 314159,
    "gasPrice": '2000000000000',
    "input": "0x57cb2fc4"
}

2. 使用etherscan.io的API

而很多时候,我们希望根据某个账户地址或者合约地址,来查询相关交易详情。

最简单的,我们可以使用https://etherscan.io的相关API完成操作:
https://etherscan.io/apis#accounts

http://api.etherscan.io/api?module=account&action=txlist&address=0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae&startblock=0&endblock=99999999&sort=asc&apikey=YourApiKeyToken

但是这个接口有个限制:

The Etherscan Ethereum Developer APIs are provided as a community service and without warranty, so please just use what you need and no more. They support both GET/POST requests and a rate limit of 5 requests/sec (exceed and you will be blocked). 

每秒最多仅支持5次请求。所以如果你的应用请求量很大,那这个方法就不适合你。

3. 自己同步区块数据写入数据库,通过数据库查询

由于我对Node.js和MongoDB都不熟,所以代码质量可能较差,主要是思路的分享

这篇文章主要就是介绍第三种方法,分为以下几个内容:

  1. 安装MongoDB

  2. 编写同步区块的方法

  3. 编写查询交易的方法

3.1 安装MongoDB

下载+安装+配置环境,可以查看这篇文章在Mac上安装MongoDB

下载安装可视化数据库工具NoSQLBooster for MongoDB

3.2 编写同步区块的方法

同步区块内的交易,根据web3.js的API,需要采取如下顺序:

  1. 获取当前的最新区块编号web3.eth.getBlockNumber()

  2. 根据当前数据库区块编号和当前最新的区块编号,确定要获取的区块编号。例如数据库同步到了Block500,当前最新为Block600,现在需要从Block501开始同步,且Block不能大于Block600

  3. 根据区块编号获取该区块内共有多少笔交易web3.eth.getBlockTransactionCount(blockNum)

  4. 根据区块编号和交易的index获取交易详情getTransactionFromBlock(hashStringOrNumber, indexNumber [, callback])

详细流程:

  1. 初始化数据库,插入一条数据,记录本地数据库的区块号和交易index,默认设为0

  2. 检查上一次本地最新区块下的交易是否同步完成,若未完成,先同步完该区块交易。

2.1 如果该区块之前已经同步完成,跳过。

2.2 如果该区块交易数为0,跳过。

  1. 检查主网最新区块号是否大于本地数据库最新区块号,如果大于,同步。

  2. 由于主网最新区块的交易可能会持续增加,只同步主网最新区块的前一个区块。

说明

  1. 不同步创世区块,同步时从第一个区块开始。

  2. 由于前面约120万区块没有交易,如果检测到某个区块没有交易,只更新数据库区块号。

  3. 请求方法设置全局变量,避免重复请求导致的重复同步。

  4. 每插入一条交易数据,同步更新本地数据库区块号和交易index。

关键代码

// 引入所需模块和配置
var koa = require('koa'),
    app = new koa(),
    Web3 = require('web3'),
    MongoClient = require('mongodb').MongoClient,
    moment = require('moment');

// 连接WEB3
web3 = new Web3(new Web3(config.url))

// 数据库url
databaseUrl: 'mongodb://localhost:27017/',
// 数据库名称
databaseName: 'blockTransationsDB',
// 数据表表名称
tableName: 'transations',

/ 获取当前最新区块的前一个区块数
async function getPrevBlockNum() {
    // 获取当前最新的区块号
    var currentBlockNum = await web3.eth.getBlockNumber();
    // 获取前一个区块号
    var prevBlockNum = currentBlockNum - 1;
    console.log('===================================');
    console.log('currentBlockNum: ' + currentBlockNum);
    console.log('prevBlockNum: ' + prevBlockNum);
    return prevBlockNum;
}

// 获取指定区块下所有交易的数量
async function getBlockTransactionCounts(blockNum) {
    // 获取当前区块的前一个区块下的所有交易的数量
    var transationCountByBlockNum = await
    web3.eth.getBlockTransactionCount(blockNum);
    // 如果返回的是null,提示错误中止执行后面的方法
    if (transationCountByBlockNum == null) throw 'Error: The transationCountByBlockNum is null, please try again later.';
    console.log('blockNum: ' + blockNum + ' transationCountByBlockNum: ' + transationCountByBlockNum);
    return transationCountByBlockNum;
}

// 向数据库插入交易记录
function insertTransationData(response, blockNum, index) {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);

            // // 插入单条数据
            dbo.collection(config.tableName).insertOne(response, function(err, res) {
                if (err) reject(err);
                console.log("Insert success! BlockNum: " + blockNum + ' index: ' + index);
                db.close();
                resolve();
            });
        });
    });
}

// 初始化数据库,插入第一条数据,将dataBaselocalBlockNumber和dataBaselocalTransationIndex设为0

function initDatabaseData() {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);
            var initData = { dataBaselocalBlockNumber: 0, dataBaselocalTransationIndex: 0 };
            dbo.collection(config.tableName).insertOne(initData, function(err, res) {
                if (err) reject(err);
                console.log("Init database data success.");
                db.close();
                resolve();
            });
        });
    });
}

// 更新数据库的dataBaselocalBlockNumber和dataBaselocalTransationIndex
function updateDataBaselocalBlockNumber(localBlockNumber, transationIndex) {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);
            var whereStr = {
                dataBaselocalBlockNumber: {
                    $exists: true
                }
            }
            var updateStr = {
                $set: {
                    dataBaselocalBlockNumber: localBlockNumber,
                    dataBaselocalTransationIndex: transationIndex
                }
            };
            dbo.collection(config.tableName).updateOne(whereStr, updateStr, function(err, res) {
                if (err) reject(err);
                console.log("Update dataBaselocalBlockNumber: " + localBlockNumber +
                    " dataBaselocalTransationIndex: " + transationIndex);
                db.close();
                resolve();
            });
        });
    })
}

// 获取数据库记录的已同步的区块号
function getLatestLocalBlockNum() {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);
            dbo.collection(config.tableName).find({
                dataBaselocalBlockNumber: {
                    $exists: true
                }
            }).toArray(async function(err, result) {
                if (err) reject(err);
                var localBlockNumber;
                var localTransationIndex;
                // 先判断数据库的blockNum字段和transationIndex字段是否有值,没有的话都设为0
                if (result.length === 0) {
                    // 不同步创世区块(block[0])
                    localBlockNumber = 0;
                    localTransationIndex = 0;
                } else {
                    // 如果有,取值
                    localBlockNumber = result[0].dataBaselocalBlockNumber;
                    localTransationIndex = result[0].dataBaselocalTransationIndex;
                }
                console.log('Latest localBlockNumber: ' + localBlockNumber +
                    ' & localTransationIndex ' + localTransationIndex);
                db.close();
                resolve(localBlockNumber);
            });
        });
    })
}

// 获取数据库记录的已同步的区块号的交易index
function getLatestLocalTransationIndex() {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);
            dbo.collection(config.tableName).find({
                dataBaselocalTransationIndex: {
                    $exists: true
                }
            }).toArray(async function(err, result) {
                if (err) reject(err);
                var localBlockNumber;
                var localTransationIndex;
                // 先判断数据库的blockNum字段和transationIndex字段是否有值,没有的话都设为0
                if (result.length === 0) {
                    // 不同步创世区块(block[0])
                    localBlockNumber = 0;
                    localTransationIndex = 0;
                } else {
                    // 如果有,取值
                    localBlockNumber = result[0].dataBaselocalBlockNumber;
                    localTransationIndex = result[0].dataBaselocalTransationIndex;
                }
                console.log('localTransationIndex ' + localTransationIndex);
                db.close();
                resolve(localTransationIndex);
            });
        });
    })
}

router.get('/transation/syncTransations', async(ctx, next) => {

    // 防止重复请求重复同步
    if (tools.isSync) {
        console.log('============================================================');
        console.log('The Sync is in progress... ...');
        console.log('============================================================');
        return;
    }

    // 如果数据库中不存在本地区块号,先初始化数据库区块号数据
    console.log('============================================================');
    console.log('=====> Checking database data has init?');
    var localBlockNumber = await getLatestLocalBlockNum();
    if (localBlockNumber == 0) {
        console.log('The local blockNum in the database does not exist.');
        await initDatabaseData();
    } else {
        console.log('The database data has already init before.');
    }
    console.log('============================================================');


    // 判断上一次本地最新区块下的交易是否同步完成,若未完成,先同步完该区块交易
    console.log('============================================================');
    console.log('=====> Checking last local block transations has complete sync?');
    var transactionCount = await getBlockTransactionCounts(localBlockNumber);
    console.log('localBlockNumber[' + localBlockNumber + ']' + ' transactionCount on mainNet is ' + transactionCount);
    // 先查看该区块中是否有交易
    if (transactionCount == 0) {
        console.log('Block ' + localBlockNumber + ' has no transation data.');
    } else {
        // 如果有,判断是否需要继续同步
        if ((transactionCount - 1) > await getLatestLocalTransationIndex()) {
            console.log('Not all of transations, Continue sync block transations... ...');
            tools.isSync = true;

            // 从最后一条交易记录的下一条开始获取并插入
            for (i = (await getLatestLocalTransationIndex() + 1); i < transactionCount; i++) {
                await web3.eth.getTransactionFromBlock(localBlockNumber, i).then(async response => {

                    // 插入交易数据
                    await insertTransationData(response, localBlockNumber, i);
                    // 更新数据库的dataBaselocalBlockNumber和dataBaselocalTransationIndex
                    await updateDataBaselocalBlockNumber(localBlockNumber, i);
                    console.log(moment().format('LLLL'));
                })
            }

            console.log('Complete sync block ' +
                localBlockNumber + ' of ' + transactionCount + ' transations.');
            tools.isSync = false;

        } else {
            console.log('No need sync.Local block has full of ' + transactionCount + ' transations.');
        }
    }
    console.log('============================================================');


    // 比较主网和本地的区块号并决定是否进行同步
    console.log('============================================================');
    console.log('=====> Checking local block is the latested?');
    var nextLocalBlockNumber;
    var blockTransactionCount;
    while (await getPrevBlockNum() > await getLatestLocalBlockNum()) {
        console.log('Not the latested, Prepare Sync......');

        tools.isSync = true;

        // 获取数据库最新一条交易记录的区块号的下一个区块号
        nextLocalBlockNumber = (await getLatestLocalBlockNum()) + 1;
        console.log('nextLocalBlockNumber: ' + nextLocalBlockNumber);

        // 获取该区块下的交易数量
        blockTransactionCount = await getBlockTransactionCounts(nextLocalBlockNumber);

        // 判断区块内是否包含交易,如果为0,只需要更新下数据库的区块号
        if (blockTransactionCount == 0) {

            console.log('Block ' + nextLocalBlockNumber + ' has no transation data.');

            await updateDataBaselocalBlockNumber(nextLocalBlockNumber, 0);
        } else {

            console.log('Block ' + nextLocalBlockNumber + ' isSyncing......');

            // 获取该区块下的交易数据
            for (var i = 0; i < blockTransactionCount; i++) {

                await web3.eth.getTransactionFromBlock(nextLocalBlockNumber, i).then(async response => {

                    // 插入交易数据
                    await insertTransationData(response, nextLocalBlockNumber, i);
                    // 更新数据库的dataBaselocalBlockNumber和dataBaselocalTransationIndex
                    await updateDataBaselocalBlockNumber(nextLocalBlockNumber, i);
                    console.log(moment().format('LLLL'));
                })
            }
        }
    }

    tools.isSync == false;

    console.log('The current local block is the latest.');
    console.log('Finish Sync.');
    console.log('============================================================');
})

3.3 编写查询交易的方法

// 在数据库中查询指定账号的交易
function queryAccountTransationData(account) {
    return new Promise((resolve, reject) => {
        MongoClient.connect(config.databaseUrl, function(err, db) {
            if (err) reject(err);
            var dbo = db.db(config.databaseName);
            var whereStr = {
                $or: [{
                    from: account
                }, {
                    to: account
                }]
            };
            dbo.collection(config.tableName).find(whereStr).toArray(function(err, result) {
                if (err) reject(err);
                console.log('Transations by account ' + account + ':');
                console.log(result);
                db.close();
                resolve(result);
            });
        });
    })
}

// 根据账号地址从数据库中查询交易:from or to
// http://0.0.0.0:8084/transation/getTransationsByAccount?account=0xcE85247b032f7528bA97396F7B17C76D5D034D2F
router.get('/transation/getTransationsByAccount', async(ctx, next) => {

    if (!ctx.request.query.account) {
        ctx.body = await Promise.resolve({
            code: 20002,
            data: {},
            message: 'account 必须是一个钱包地址',
        })
    }

    var transations = await queryAccountTransationData(ctx.request.query.account);

    ctx.body = await Promise.resolve({
        code: 10000,
        transations: transations,
        message: 'ok',
    })
})

原创内容,转载请注明出处,谢谢!

你可能感兴趣的:(以太坊开发(二十六)根据账号/合约地址查询交易详情)