const util = require('util'); const utils = require('./utils'); const db = require("./db"); const dbhelper = require("./dbhelper"); const log = require("./log"); const dblog = require("./dblog"); const bc = require('./blockchain'); const metamgr = require('./metamgr'); const C = require('./C'); /* 1、数据库被认为是可靠如果数据库操作失败则应该挂起 2、区块链查询操作没啥成本可以多次调用,写入操作需谨慎!!! */ class BoxOrder { constructor(orderDb, isNewOrder) { this.isNewOrder = isNewOrder; this.orderDb = orderDb; } init () { if (this.isNewOrder) { setTimeout(this.start.bind(this), utils.randRange(100, 1000)); } else { setTimeout(this.start.bind(this), utils.randRange(1000, 3000)); } } async start() { if (!this.isPaid()) { //同步到链上 await this.sync(); //等待确认 await this.confirm(); } //创建nft await this.createNft(); } async sync() { if (!this.isSynced()) { //查询 const found = await this.bcSearch(); if (!found) { //上链 const ok = await this.bcSync(); if (!ok) { utils.emitEvent(C.REMOVE_PENDING_ORDER_EVENT, this.getOrderId()); return; } } } } async bcSearch() { const logClass = 'bcSearch'; let events = []; if (this.getSyncBlockNumber() <= 0) { return false; } while (true) { try { const fromBlock = Math.max(this.getSyncBlockNumber(), this.getBlockNumber()); events = await bc.mallInstance.getPastEvents( 'BEBoxPaid', { fromBlock: fromBlock, toBlock: fromBlock + 5000 - 1, filter: { boxId: this.getOrderId() } }); break; } catch (err) { log.warning(util.format('%s getPastEvents orderDb:%s err:%s', logClass, utils.jsonEncode(this.getOrderDb()), err )); } await utils.sleep(1000 * 3); } if (events.length > 0) { const blockNumber = events[0]['blockNumber']; if (!blockNumber) { await this.doHangUp(logClass, 'blocNumber is empty'); } await this.updateDbMustBeSuccess( logClass, [ ['state', C.ORDER_STATE_PAID], ['bc_synced', 1], ['bc_result', 1], ['bc_block_number', blockNumber], ]); this.orderDb['bc_synced'] = 1; this.orderDb['bc_sync_count'] += 1; this.orderDb['bc_sync_number'] = blockNumber; } return events.length > 0; } async bcSync() { const logClass = 'bcSync'; await bc.mustBeActive(); if (this.getSyncBlockNumber() <= 0) { await this.updateDbMustBeSuccess( logClass, [ ['bc_sync_block_number', bc.getCurrBlockNumber()], ] ); this.orderDb['bc_sync_block_number'] = this.getSyncBlockNumber(); } const nowTime = utils.getUtcTime(); const doSync = async () => { let result = null; try { dblog.addLog( 'order.sync', 'prepare', this.getOrderId(), utils.jsonEncode(this.getOrderDb())); result = await bc.mallInstance.methods.payForBoxWithSignature( this.orderDb['order_id'], this.orderDb['type'], this.orderDb['buyer_address'], this.orderDb['price'], this.orderDb['payment_token_address'], this.orderDb['nonce'], this.orderDb['signature']).send({gas: 1000000}); dblog.addLog( 'order.sync', 'success', this.getOrderId(), utils.jsonEncode(this.getOrderDb()), utils.jsonEncode(result)); } catch (err) { dblog.addLog( 'order.sync', 'failed', this.getOrderId(), utils.jsonEncode(this.getOrderDb()), err); await this.updateDbMustBeSuccess( logClass, [ ['suspend', 1], ['suspend_reason', '' . err], ] ); await this.doHangUp(logClass, err); } return result; }; try { const result = await doSync(); const blockNumber = result['blockNumber']; await this.updateDbMustBeSuccess( logClass, [ ['state', C.ORDER_STATE_PAID], ['bc_synced', 1], ['bc_sync_count', this.orderDb['bc_sync_count'] + 1], ['bc_sync_time', nowTime], ['bc_result', 1], ['bc_block_number', blockNumber], ['bc_fail_reason', ''], ] ); this.orderDb['state'] = C.ORDER_STATE_PAID; this.orderDb['bc_synced'] = 1; this.orderDb['bc_sync_count'] += 1; this.orderDb['bc_sync_time'] = nowTime; this.orderDb['bc_result'] = 1; this.orderDb['bc_sync_number'] = blockNumber; return true; } catch (e) { log.warning(util.format('%s end orderDb:%s err:%s', logClass, utils.jsonEncode(this.orderDb), e ) ); } return false; } async confirm() { const logClass = 'confirm'; while (true) { if (bc.isComfirmed(this.getBlockNumber())) { break; } await utils.sleep(1000 * 3); } } async createNft() { const logClass = 'createNft'; log.info(util.format('%s begin orderDb:%s', logClass, utils.jsonEncode(this.getOrderDb()), ) ); await this.bcMintHero(); await this.updateDbMustBeSuccess(logClass, [ ['done', 1] ]); log.info(util.format('%s end orderDb:%s', logClass, utils.jsonEncode(this.getOrderDb()), ) ); utils.emitEvent(C.REMOVE_PENDING_ORDER_EVENT, this.getOrderId()); } async bcMintHero() { const logClass = 'bcMintHero'; const buyerAddress = this.getBuyerAddress(); const tokenId = this.getTokenId(); const nowTime = utils.getUtcTime(); if (this.isMinted()) { const result = await bc.factoryInstance.methods.mintHeroTo( buyerAddress, tokenId).send({ gas: 1000000 }); await this.updateDbMustBeSuccess( logClass, [ ['bc_minted', 1], ['bc_mint_time', nowTime], ] ); this.orderDb['bc_minted'] = 1; this.orderDb['bc_mint_time'] = nowTime; } { const fieldList = [ ['token_id', tokenId], ['item_id', this.orderDb['item_id']], ['owner_id', ''], ['owner_address', this.orderDb['buyer_address']], ['owner_name', ''], ['createtime', nowTime], ['modifytime', nowTime], ]; const err = await dbhelper.insert( 't_nft', fieldList); if (err) { log.error(util.format('%s insert nft table orderDb:%s fieldList:%s err:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList), err ) ); } else { log.info(util.format('%s insert nft table orderDb:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList) ) ); } } } async updateDbMustBeSuccess(logClass, fieldList) { const err = await dbhelper.update( 't_box_order', [ ['order_id', this.getOrderId()] ], fieldList); if (err) { log.error(util.format('%s updateDbMustBeSuccess orderDb:%s fieldList:%s err:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList), err ) ); await this.doHangUp(logClass + ' updateDbMustBeSuccess ', err); } else { log.info(util.format('%s updateDbMustBeSuccess orderDb:%s', logClass, utils.jsonEncode(this.orderDb), utils.jsonEncode(fieldList) ) ); } return err; } async doHangUp(logClass, reason) { //挂起等待人工处理 try { log.warning(util.format('%s doHangUp orderDb:%s reason:%s', logClass, utils.jsonEncode(this.orderDb), reason ) ); } catch (err) { log.error(util.format('%s doHangUp orderDb:%s err:%s', logClass, utils.jsonEncode(this.orderDb), err ) ); } while (true) { await utils.sleep(1000 * 3600 * 24); } } getOrderDb() { return this.orderDb; } getOrderId() { return this.orderDb['order_id']; } getBuyerAddress() { return this.orderDb['buyer_address']; } getTokenId() { return this.orderDb['token_id']; } getBlockNumber() { return this.orderDb['bc_block_number']; } isPaid() { return this.orderDb['state'] == C.ORDER_STATE_PAID; } isSynced() { return this.orderDb['bc_synced'] != 0; } isMinted() { return this.orderDb['bc_minted'] != 0; } getSyncBlockNumber() { return this.orderDb['bc_sync_block_number']; } isFail() { return this.isSynced() && this.orderDb['bc_result'] != C.BC_RESULT_OK; } isNetError(err) { const errStr = '' + err; return errStr && errStr.indexOf('connection not open on send') >= 0; } }; exports.BoxOrder = BoxOrder;