keystore_wdc
Version:
``` npm i keystore_wdc; const KeyStore = require('keystore_wdc'); const ks = new KeyStore(); ``` #### 生成keystore ``` async function create(){ const keystore = await ks.Create("your password"); } ``` * 返回keystore,密码格式不正确返回-1。
489 lines (437 loc) • 16.7 kB
text/typescript
import {AbiInput, Binary, Event, Readable, RLPElement, TransactionResult, TX_STATUS, WS_CODES} from "./types"
import {assert, bin2hex, bin2str, hex2bin, normalizeAddress, toSafeInt, uuidv4} from "./utils"
import {byteArrayToInt} from "./rlp"
import {Contract, normalizeParams} from "./contract"
import {Transaction} from "./tx"
import rlp = require('./rlp');
import BN = require("../bn");
export interface Resp {
code: WS_CODES
nonce: number
body: RLPElement | RLPElement[]
}
interface EventResp extends Resp {
addr: string
name: string
fields: Uint8Array[]
}
interface TransactionResp extends Resp {
hash: string
status: TX_STATUS
reason?: string
blockHeight?: number | string
blockHash?: string
gasUsed?: string | number
events?: [Uint8Array, Uint8Array[]][]
result?: Uint8Array[]
}
export class RPC {
host: string
port: string
timeout: number
private callbacks: Map<number, (resp: TransactionResp | EventResp) => void>
private id2key: Map<number, string>
private id2hash: Map<number, string>
private eventHandlers: Map<string, Set<number>>
private txObservers: Map<string, Set<number>>
private cid: number
private rpcCallbacks: Map<number, (resp: Resp) => void>
private nonce: number
private ws: WebSocket
private uuid: string
/**
*
* @param host 主机名
* @param port 端口号
* @param timeout 超时时间,单位是秒,默认15秒
*/
constructor(host?: string, port?: string | number, timeout?: number) {
this.host = host || 'localhost'
this.port = (port || 80).toString()
this.timeout = timeout || 15
this.callbacks = new Map() // id -> function
this.id2key = new Map()// id -> address:event
this.id2hash = new Map() // id -> txhash
this.eventHandlers = new Map() // address:event -> [id]
this.txObservers = new Map() // hash -> [id]
this.cid = 0
this.rpcCallbacks = new Map() // nonce -> cb
this.nonce = 0
}
private tryConnect(): Promise<void> {
let WS: new (url: string) => WebSocket
if (typeof WebSocket === 'function')
WS = WebSocket
else
WS = require('ws')
if (this.ws && this.ws.readyState === this.ws.OPEN) {
return Promise.resolve()
}
if (this.ws && this.ws.readyState === this.ws.CONNECTING) {
const fn = this.ws.onopen || ((e) => { })
const _rj = this.ws.onerror || ((e) => { })
const p = new Promise((rs, rj) => {
this.ws.onopen = (e) => {
fn.call(this.ws, e)
rs()
}
this.ws.onerror = (e) => {
_rj.call(this.ws, e)
rj(e)
}
})
return <Promise<void>>p
}
this.uuid = uuidv4()
this.ws = new WS(`ws://${this.host}:${this.port || 80}/websocket/${this.uuid}`)
this.ws.onerror = console.error
this.ws.onmessage = (e) => {
if (typeof WebSocket !== 'function') {
this.handleData(e.data)
return
}
const reader = new FileReader()
reader.onload = () => {
const arrayBuffer = reader.result
this.handleData(new Uint8Array(<ArrayBuffer>arrayBuffer))
}
reader.readAsArrayBuffer(e.data)
}
const p = new Promise(
(rs, rj) => {
this.ws.onopen = rs
this.ws.onerror = rj
}
)
return <Promise<void>>p
}
private parse(data: Uint8Array): Resp {
const decoded = <RLPElement[]>rlp.decode(data)
const nonce = byteArrayToInt(<Uint8Array>decoded[0])
const code = byteArrayToInt(<Uint8Array>decoded[1])
const body = decoded[2]
let r: Resp = {
code: code,
nonce: nonce,
body: body
}
switch (code) {
case WS_CODES.TRANSACTION_EMIT: {
const h = bin2hex(<Uint8Array>body[0])
const s = byteArrayToInt(<Uint8Array>body[1])
let ret: TransactionResp = <TransactionResp>r
ret.hash = h
ret.status = s
if (s === TX_STATUS.DROPPED) {
ret.reason = bin2str(<Uint8Array>body[2])
}
if (s === TX_STATUS.INCLUDED) {
const arr = body[2]
ret.blockHeight = toSafeInt(arr[0])
ret.blockHash = bin2hex(arr[1])
ret.gasUsed = toSafeInt(arr[2])
ret.result = arr[3]
ret.events = arr[4]
}
return ret
}
case WS_CODES.EVENT_EMIT: {
let ret: EventResp = <EventResp>r
ret.addr = bin2hex(<Uint8Array>body[0])
ret.name = bin2str(<Uint8Array>body[1])
ret.fields = <Uint8Array[]> (<RLPElement[]>body)[2]
return ret
}
}
return r
}
private handleData(data): void {
const r = this.parse(data)
switch (r.code) {
case WS_CODES.TRANSACTION_EMIT: {
const t = <TransactionResp>r
const funcIds = this.txObservers.get(t.hash) || []
funcIds.forEach(id => {
const func = this.callbacks.get(id)
func(t)
})
return
}
case WS_CODES.EVENT_EMIT: {
const e = <EventResp>r
const funcIds = this.eventHandlers.get(`${e.addr}:${e.name}`) || []
funcIds.forEach(id => {
const func = this.callbacks.get(id)
func(e)
})
return
}
}
if (r.nonce) {
const fn = this.rpcCallbacks.get(r.nonce)
if (fn)
fn(r)
this.rpcCallbacks.delete(r.nonce)
}
}
/**
* 监听合约事件
*/
private __listen(contract: Contract, event: string, func: (e: Record<string, Readable>) => void) {
const addr = normalizeAddress(contract.address)
const addrHex = bin2hex(addr)
this.wsRpc(WS_CODES.EVENT_SUBSCRIBE, addr)
const id = ++this.cid
const key = `${addrHex}:${event}`
this.id2key.set(id, key)
const fn: (e: EventResp) => void = (e) => {
const abiDecoded = contract.abiDecode(event, <Uint8Array[]>e.fields, 'event')
func(<Record<string, Readable>>abiDecoded)
}
if (!this.eventHandlers.has(key))
this.eventHandlers.set(key, new Set())
this.eventHandlers.get(key).add(id)
this.callbacks.set(id, fn)
return id
}
listen(contract: Contract, event: string, func?: (e: Record<string, Readable>) => void): Promise<Record<string, Readable>> {
if (func === undefined) {
return new Promise((rs, rj) => {
this.__listen(contract, event, rs)
})
}
assert(typeof func === 'function', 'callback should be function')
this.__listen(contract, event, func)
}
/**
* 移除监听器
* @param {number} id 监听器的 id
*/
removeListener(id: number): void {
const key = this.id2key.get(id)
const h = this.id2hash.get(id)
this.callbacks.delete(id)
this.id2key.delete(id)
this.id2hash.delete(id)
if (key) {
const set = this.eventHandlers.get(key)
set && set.delete(id)
if (set && set.size === 0)
this.eventHandlers.delete(key)
}
if (h) {
const set = this.txObservers.get(h)
set && set.delete(id)
if (set && set.size === 0)
this.txObservers.delete(h)
}
}
listenOnce(contract: Contract, event: string, func?: (e: Record<string, Readable>) => void): Promise<Record<string, Readable>> {
const id = this.cid + 1
if (func === undefined)
return this.listen(contract, event).then((r) => {
this.removeListener(id)
return r
})
return this.listen(contract, event, (p) => {
func(p)
this.removeListener(id)
})
}
/**
* 添加事务观察者,如果事务最终被确认或者异常终止,观察者会被移除
*/
private __observe(_hash: Binary, cb: (r: TransactionResp) => void) {
let hash = bin2hex(_hash)
const id = ++this.cid
hash = hash.toLowerCase()
if (!this.txObservers.has(hash))
this.txObservers.set(hash, new Set())
this.id2hash.set(id, hash)
this.txObservers.get(hash).add(id)
const fn: (r: TransactionResp) => void = (r) => {
cb(r)
switch (r.status) {
case TX_STATUS.DROPPED:
case TX_STATUS.CONFIRMED:
this.removeListener(id)
break
}
}
this.callbacks.set(id, fn)
return id
}
/**
* 查看合约方法
*/
viewContract(contract: Contract, method: string, parameters?: AbiInput | AbiInput[] | Record<string, AbiInput>): Promise<Readable> {
if (!(contract instanceof Contract))
throw new Error('create a instanceof Contract by new tool.Contract(addr, abi)')
let normalized = normalizeParams(parameters)
const addr = contract.address
const params = contract.abiEncode(method, normalized)
return this.wsRpc(WS_CODES.CONTRACT_QUERY, [
normalizeAddress(addr),
method,
params
]).then(r => <Readable>contract.abiDecode(method, <Uint8Array[]> rlp.decode(r.body as Uint8Array)))
}
/**
* 通过 websocket 发送事务
* @param tx 事务
*/
sendTransaction(tx: Transaction | Transaction[]): Promise<void> {
return this.wsRpc(WS_CODES.TRANSACTION_SEND, [Array.isArray(tx), tx])
.then(() => Promise.resolve())
}
observe(tx: Transaction, status: TX_STATUS, timeout?: number): Promise<TransactionResult> {
status = status === undefined ? TX_STATUS.CONFIRMED : status
return new Promise((resolve, reject) => {
let success = false
if (timeout)
setTimeout(() => {
if (success) return
reject({ reason: 'timeout' })
}, timeout)
let ret: TransactionResult = <TransactionResult>{}
let confirmed = false
let included = false
let finished = false
this.__observe(tx.getHash(), (resp: TransactionResp) => {
if(finished)
return
if(resp.status === TX_STATUS.PENDING && status === TX_STATUS.PENDING){
finished = true
resolve(<any> {transactionHash: resp.hash})
return
}
if (resp.status === TX_STATUS.DROPPED) {
finished = true
const e = { hash: resp.hash, reason: resp.reason }
reject(e)
return
}
if (resp.status === TX_STATUS.CONFIRMED) {
if (status === TX_STATUS.INCLUDED)
return
confirmed = true
if (included) {
success = true
resolve(ret)
return
}
}
if (resp.status === TX_STATUS.INCLUDED) {
included = true
ret.blockHeight = resp.blockHeight
ret.blockHash = resp.blockHash
ret.gasUsed = resp.gasUsed
if (resp.result && resp.result.length
&& tx.__abi
&& tx.isDeployOrCall()
) {
const decoded = (new Contract('', tx.__abi)).abiDecode(tx.getMethod(), resp.result)
ret.result = <Readable>decoded
}
if (
resp.events &&
resp.events.length
&& tx.__abi) {
const events: Event[] = []
for (let e of resp.events) {
const name = bin2str(e[0])
const decoded = (new Contract('', tx.__abi)).abiDecode(name, e[1], 'event')
events.push({ name: name, data: <Record<string, Readable>>decoded })
}
ret.events = events
}
ret.transactionHash = bin2hex(tx.getHash())
ret.fee = toSafeInt((new BN(tx.gasPrice).mul(new BN(ret.gasUsed))))
if (tx.isDeployOrCall()) {
ret.method = tx.getMethod()
ret.inputs = tx.__inputs
}
if (status === TX_STATUS.INCLUDED) {
success = true
resolve(ret)
return
}
if (confirmed) {
success = true
resolve(ret)
}
}
})
})
}
private wsRpc(code: WS_CODES, data: any): Promise<Resp> {
this.nonce++
const n = this.nonce
let tm = null
const ret = new Promise((rs, rj) => {
tm = setTimeout(() => {
rj('websocket rpc timeout')
this.rpcCallbacks.delete(n)
}, this.timeout * 1000)
this.rpcCallbacks.set(n, (r) => {
if(tm)
clearTimeout(tm)
rs(r)
})
})
this.tryConnect()
.then(() => {
const encoded = rlp.encode([n, code, data])
this.ws.send(encoded)
})
return <Promise<Resp>>ret
}
/**
* 发送事务的同时监听事务的状态
*/
sendAndObserve(tx: Transaction | Transaction[], status: TX_STATUS, timeout?: number): Promise<TransactionResult | TransactionResult[]> {
let ret: Promise<TransactionResult | TransactionResult[]>
let sub: Promise<Resp>
if (Array.isArray(tx)) {
const arr: Promise<TransactionResult>[] = []
sub = this.wsRpc(WS_CODES.TRANSACTION_SUBSCRIBE, tx.map(t => hex2bin(t.getHash())))
for (const t of tx) {
arr.push(this.observe(t, status, timeout))
}
ret = Promise.all(arr)
} else {
sub = this.wsRpc(WS_CODES.TRANSACTION_SUBSCRIBE, hex2bin(tx.getHash()))
ret = this.observe(tx, status, timeout)
}
return sub
.then(() => this.sendTransaction(tx))
.then(() => ret)
}
/**
* 获取 nonce
*/
getNonce(_pkOrAddress: Binary): Promise<number | string> {
let pkOrAddress = normalizeAddress(_pkOrAddress)
return this.wsRpc(WS_CODES.ACCOUNT_QUERY, pkOrAddress)
.then(resp => {
return toSafeInt(new BN(resp.body[0][2]))
})
}
/**
* 获取 账户余额
*/
getBalance(_pkOrAddress: Binary): Promise<number | string> {
let pkOrAddress = normalizeAddress(_pkOrAddress)
return this.wsRpc(WS_CODES.ACCOUNT_QUERY, pkOrAddress)
.then(resp => {
return toSafeInt(new BN(resp.body[0][3]))
})
}
close() {
if (this.ws) {
const ws = this.ws
this.ws = null
ws.close()
}
}
}