Skip to main content

10. 监控

Symbol节点可以透过WebSocket通讯监视区块链状态的更改。

10.1 监听器配置

生成 WebSocket 并配置监听器。

nsRepo = repo.createNamespaceRepository();
wsEndpoint = NODE.replace("http", "ws") + "/ws";
listener = new sym.Listener(wsEndpoint, nsRepo, WebSocket);
listener.open();

端点的格式如下。

  • wss://{node url}:3001/ws

如果没有通信,监听器将在一分钟后断开连接。

10.2 接收交易

检测帐户收到的交易。

listener.open().then(() => {
//Detection of approval transactions
listener.confirmed(alice.address).subscribe((tx) => {
//Describes the process after reception
console.log(tx);
});
//Detection of unconfirmed transactions
listener.unconfirmedAdded(alice.address).subscribe((tx) => {
//Describes the process after reception
console.log(tx);
});
});

执行上述监听器后,宣告要发送给 Alice 的交易。

市例演示
> Promise {<pending>}
> TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64,}
deadline: Deadline {adjustedValue: 12449258375}
maxFee: UInt64 {lower: 32000, higher: 0}
message: RawMessage {type: -1, payload: ''}
mosaics: []
networkType: 152
payloadSize: undefined
recipientAddress: Address {address: 'TBXUTAX6O6EUVPB6X7OBNX6UUXBMPPAFX7KE5TQ', networkType: 152}
signature: "914B625F3013635FA9C99B2F138C47CD75F6E1DF7BDDA291E449390178EB461AA389522FA126D506405163CC8BA51FA9019E0522E3FA9FED7C2F857F11FBCC09"
signer: PublicAccount {publicKey: 'D4933FC1E4C56F9DF9314E9E0533173E1AB727BDB2A04B59F048124E93BEFBD2', address: Address}
transactionInfo: TransactionInfo
hash: "3B21D8842EB70A780A662CCA19B8B030E2D5C7FB4C54BDA8B3C3760F0B35FECE"
height: UInt64 {lower: 316771, higher: 0}
id: undefined
index: undefined
merkleComponentHash: "3B21D8842EB70A780A662CCA19B8B030E2D5C7FB4C54BDA8B3C3760F0B35FECE"
type: 16724
version: 1

尚未确认的交易会显示transactionInfo.height=0。

10.3 区块监控

检测新生成的块。

listener.open().then(() => {
//Detection of block generation
listener.newBlock().subscribe((block) => console.log(block));
});
市例演示
> Promise {<pending>}
> NewBlock
beneficiaryAddress: Address {address: 'TAKATV2VSYBH3RX4JVCCILITWANT6JRANZI2AUQ', networkType: 152}
blockReceiptsHash: "ABDDB66A03A270E4815C256A8125B70FC3B7EFC4B95FF5ECAD517CB1AB5F5334"
blockTransactionsHash: "0000000000000000000000000000000000000000000000000000000000000000"
difficulty: UInt64 {lower: 1316134912, higher: 2328}
feeMultiplier: 0
generationHash: "5B4F32D3F2CDD17917D530A6A967927D93F73F2B52CC590A64E3E94408D8CE96"
hash: "E8294BDDDAE32E17242DF655805EC0FCAB3B628A331824B87A3CA7578683B09C"
height: UInt64 {lower: 316759, higher: 0}
networkType: 152
previousBlockHash: "38382D616772682321D58046511DD942F36A463155C5B7FB0A2CBEE8E29B253C"
proofGamma: "37187F1C8BD8C87CB4F000F353ACE5717D988BC220EFBCC25E2F40B1FB9B7D7A"
proofScalar: "AD91A572E5D81EA92FE313CA00915E5A497F60315C63023A52E292E55345F705"
proofVerificationHash: "EF58228B3EB3C422289626935DADEF11"
signature: "A9481E5976EDA86B74433E8BCC8495788BA2B9BE0A50F9435AD90A14D1E362D934BA26069182C373783F835E55D7F3681817716295EC1EFB5F2375B6DE302801"
signer: PublicAccount {publicKey: 'F2195B3FAFBA3DF8C31CFBD9D5BE95BB3F3A04BDB877C59EFB9D1C54ED2DC50E', address: Address}
stateHash: "4A1C828B34DE47759C2D717845830BA14287A4EC7220B75494BDC31E9539FCB5"
timestamp: UInt64 {lower: 3851456497, higher: 2}
type: 33091
version: 1

如果使用 listener.newBlock(),则通信约每30秒发生一次,这使得 WebSocket 断开连接的可能性较小。

在罕见的情况下,区块生成可能超过一分钟,此时需要重新连接监听器。 (其他因素也可能导致断开连接,因此如果要确保,请按照下面的说明补充使用 onclose。)

10.4 签名请求

检测何时发生需要签名的交易。

listener.open().then(() => {
//Detection of Aggregate Bonded Transaction occurrences requiring signatures
listener
.aggregateBondedAdded(alice.address)
.subscribe(async (tx) => console.log(tx));
});
市例演示
> AggregateTransaction
cosignatures: []
deadline: Deadline {adjustedValue: 12450154608}
> innerTransactions: Array(2)
0: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64,}
1: TransferTransaction {type: 16724, networkType: 152, version: 1, deadline: Deadline, maxFee: UInt64,}
maxFee: UInt64 {lower: 94400, higher: 0}
networkType: 152
signature: "972968C5A2FB70C1D644BE206A190C4FCFDA98976F371DBB70D66A3AAEBCFC4B26E7833BCB86C407879C07927F6882C752C7012C265C2357CAA52C29834EFD0F"
signer: PublicAccount {publicKey: '0E5C72B0D5946C1EFEE7E5317C5985F106B739BB0BC07E4F9A288417B3CD6D26', address: Address}
> transactionInfo: TransactionInfo
hash: "44B2CD891DA0B788F1DD5D5AB24866A9A172C80C1749DCB6EB62255A2497EA08"
height: UInt64 {lower: 0, higher: 0}
id: undefined
index: undefined
merkleComponentHash: "0000000000000000000000000000000000000000000000000000000000000000"
type: 16961
version: 1

检测到涉及指定地址的所有聚合交易。 是否需要共同签名由单独的筛选器决定。

10.5 使用提示

持续连接

从节点列表中随机选择并尝试连接。

连接到节点
//Node list
NODES = ["https://node.com:3001",...];
function connectNode(nodes) {
const node = nodes[Math.floor(Math.random() * nodes.length)] ;
console.log("try:" + node);
return new Promise((resolve, reject) => {
let req = new XMLHttpRequest();
req.timeout = 2000; //timeout value:2sec(=2000ms)
req.open('GET', node + "/node/health", true);
req.onload = function() {
if (req.status === 200) {
const status = JSON.parse(req.responseText).status;
if(status.apiNode == "up" && status.db == "up"){
return resolve(node);
}else{
console.log("fail node status:" + status);
return connectNode(nodes).then(node => resolve(node));
}
} else {
console.log("fail request status:" + req.status)
return connectNode(nodes).then(node => resolve(node));
}
};
req.onerror = function(e) {
console.log("onerror:" + e)
return connectNode(nodes).then(node => resolve(node));
};
req.ontimeout = function (e) {
console.log("ontimeout")
return connectNode(nodes).then(node => resolve(node));
};
req.send();
});
}

如果连接节点回应慢,设置超时值并重新选择节点。 检查端点/节点/健康状况,如果状态不正常则重新选择节点。

创建存储库
function createRepo(nodes) {
return connectNode(nodes).then(async function onFulfilled(node) {
const repo = new sym.RepositoryFactoryHttp(node);
try {
epochAdjustment = await repo.getEpochAdjustment().toPromise();
} catch (error) {
console.log("fail createRepo");
return await createRepo(nodes);
}
return await repo;
});
}

在罕见的情况下,有些节点可能还没有释放 /network/properties 端点,因此会获取并检查 getEpochAdjustment() 资讯。如果无法获取该资讯,将会进行递归读取 createRepo。

持续连接监听器
async function listenerKeepOpening(nodes) {
const repo = await createRepo(NODES);
let wsEndpoint = repo.url.replace("http", "ws") + "/ws";
const nsRepo = repo.createNamespaceRepository();
const lner = new sym.Listener(wsEndpoint, nsRepo, WebSocket);
try {
await lner.open();
lner.newBlock();
} catch (e) {
console.log("fail websocket");
return await listenerKeepOpening(nodes);
}
lner.webSocket.onclose = async function () {
console.log("listener onclose");
return await listenerKeepOpening(nodes);
};
return lner;
}

如果监听器关闭,它会重新连接。

启动监听器。
listener = await listenerKeepOpening(NODES);

U未签署交易自动签署

检测未签名的交易,然后签名并向网络公布。 需要两种检测模式:在初始屏幕显示期间的接收和在屏幕观看期间的接收。

//read rxjs.operators
op = require("/node_modules/rxjs/operators");
rxjs = require("/node_modules/rxjs");

//Aggregate Transaction detection
bondedListener = listener.aggregateBondedAdded(bob.address);
bondedHttp = txRepo
.search({ address: bob.address, group: sym.TransactionGroup.Partial })
.pipe(
op.delay(2000),
op.mergeMap((page) => page.data),
);
//Completed transaction detection listeners for selected accounts
const statusChanged = function (address, hash) {
const transactionObservable = listener.confirmed(address);
const errorObservable = listener.status(address, hash);
return rxjs.merge(transactionObservable, errorObservable).pipe(
op.first(),
op.map((errorOrTransaction) => {
if (errorOrTransaction.constructor.name === "TransactionStatusError") {
throw new Error(errorOrTransaction.code);
} else {
return errorOrTransaction;
}
}),
);
};
//Cosignature execution
function exeAggregateBondedCosignature(tx) {
txRepo
.getTransactionsById(
[tx.transactionInfo.hash],
sym.TransactionGroup.Partial,
)
.pipe(
//Only if the transaction is detected
op.filter((aggTx) => aggTx.length > 0),
)
.subscribe(async (aggTx) => {
//If my account is designated as the signatory of the inner transaction
if (
aggTx[0].innerTransactions.find((inTx) =>
inTx.signer.equals(bob.publicAccount),
) != undefined
) {
//Sign with Alice transaction
const cosignatureTx = sym.CosignatureTransaction.create(aggTx[0]);
const signedTx = bob.signCosignatureTransaction(cosignatureTx);
const cosignedAggTx = await txRepo
.announceAggregateBondedCosignature(signedTx)
.toPromise();
statusChanged(bob.address, signedTx.parentHash).subscribe((res) => {
console.log(res);
});
}
});
}
bondedSubscribe = function (observer) {
observer
.pipe(
//If not already signed
op.filter((tx) => {
return !tx.signedByAccount(
sym.PublicAccount.createFromPublicKey(bob.publicKey, networkType),
);
}),
)
.subscribe((tx) => {
console.log(tx);
exeAggregateBondedCosignature(tx);
});
};
bondedSubscribe(bondedListener);
bondedSubscribe(bondedHttp);
参考资料

为避免自动签署诈骗交易,请确保进行检查程序,例如检查发送方的帐户。