forked from tornado-packages/ethers.js
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ccac24a5b0 | ||
|
|
f6d155c820 | ||
|
|
180221574c | ||
|
|
6ee1a5f8bb | ||
|
|
98910ba01e |
@@ -3,7 +3,12 @@ Change Log
|
||||
|
||||
This change log is maintained by `src.ts/_admin/update-changelog.ts` but may also be manually updated.
|
||||
|
||||
ethers/v6.9.0 (2023-11-27 06:07)
|
||||
ethers/v6.9.1 (2023-12-19 04:53)
|
||||
--------------------------------
|
||||
|
||||
- Fix uncatchable issue when sending transactions over JSON-RPC and provide some retry-recovery for missing v ([#4513](https://github.com/ethers-io/ethers.js/issues/4513); [1802215](https://github.com/ethers-io/ethers.js/commit/180221574c5d2af9ad85404af4fab8752d3d5029)).
|
||||
|
||||
ethers/v6.9.0 (2023-11-27 06:15)
|
||||
--------------------------------
|
||||
|
||||
- Use provider-specified suggested priority fee when available, otherwise fallback onto existing logic of 1 gwei ([#4463](https://github.com/ethers-io/ethers.js/issues/4463); [f8f11c7](https://github.com/ethers-io/ethers.js/commit/f8f11c754aa2c9b541db73d3bde66a8ffa5146f0)).
|
||||
|
||||
80
dist/ethers.js
vendored
80
dist/ethers.js
vendored
@@ -3,7 +3,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.1";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
@@ -18048,16 +18048,19 @@ class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
@@ -19444,12 +19447,45 @@ class JsonRpcSigner extends AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" )) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -19527,7 +19563,7 @@ class JsonRpcApiProvider extends AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -19690,9 +19726,15 @@ class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
2
dist/ethers.js.map
vendored
2
dist/ethers.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/ethers.min.js
vendored
2
dist/ethers.min.js
vendored
File diff suppressed because one or more lines are too long
80
dist/ethers.umd.js
vendored
80
dist/ethers.umd.js
vendored
@@ -9,7 +9,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.1";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
@@ -18054,16 +18054,19 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
@@ -19450,12 +19453,45 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" )) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -19533,7 +19569,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -19696,9 +19732,15 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
2
dist/ethers.umd.js.map
vendored
2
dist/ethers.umd.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/ethers.umd.min.js
vendored
2
dist/ethers.umd.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/wordlists-extra.js
vendored
2
dist/wordlists-extra.js
vendored
@@ -100,7 +100,7 @@ const rotlBL = (h, l, s) => (h << (s - 32)) | (l >>> (64 - s));
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.1";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
|
||||
2
dist/wordlists-extra.js.map
vendored
2
dist/wordlists-extra.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/wordlists-extra.min.js
vendored
2
dist/wordlists-extra.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -129,8 +129,8 @@ _code: @lang<script>
|
||||
|
||||
// If MetaMask is not installed, we use the default provider,
|
||||
// which is backed by a variety of third-party services (such
|
||||
// as INFURA). They do not have private keys installed so are
|
||||
// only have read-only access
|
||||
// as INFURA). They do not have private keys installed,
|
||||
// so they only have read-only access
|
||||
console.log("MetaMask not installed; using read-only defaults")
|
||||
provider = ethers.getDefaultProvider()
|
||||
|
||||
@@ -156,7 +156,7 @@ you can use the [[JsonRpcProvider]] directly, which communicates
|
||||
using the [[link-jsonrpc]] protocol.
|
||||
|
||||
When using your own Ethereum node or a developer-base blockchain,
|
||||
such as Hardhat or Ganache, you can get access the accounts with
|
||||
such as Hardhat or Ganache, you can get access to the accounts with
|
||||
[[JsonRpcProvider-getSigner]].
|
||||
|
||||
_code: connecting to a JSON-RPC URL @lang<script>
|
||||
@@ -177,7 +177,7 @@ results when performing mathematic operations.
|
||||
|
||||
As a result, the internal units used (e.g. wei) which are suited for
|
||||
machine-readable purposes and maths are often very large and not
|
||||
terribly human-readable.
|
||||
easily human-readable.
|
||||
|
||||
For example, imagine dealing with dollars and cents; you would show
|
||||
values like ``"$2.56"``. In the blockchain world, we would keep all
|
||||
|
||||
2
lib.commonjs/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
2
lib.commonjs/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=test-provider-jsonrpc.d.ts.map
|
||||
1
lib.commonjs/_tests/test-provider-jsonrpc.d.ts.map
Normal file
1
lib.commonjs/_tests/test-provider-jsonrpc.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"test-provider-jsonrpc.d.ts","sourceRoot":"","sources":["../../src.ts/_tests/test-provider-jsonrpc.ts"],"names":[],"mappings":""}
|
||||
160
lib.commonjs/_tests/test-provider-jsonrpc.js
Normal file
160
lib.commonjs/_tests/test-provider-jsonrpc.js
Normal file
@@ -0,0 +1,160 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
const index_js_1 = require("../index.js");
|
||||
const StatusMessages = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
const wallet = new index_js_1.Wallet((0, index_js_1.id)("test"));
|
||||
function createProvider(testFunc) {
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++; }, 100);
|
||||
if (ticker.unref) {
|
||||
ticker.unref();
|
||||
}
|
||||
const processReq = (req) => {
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [wallet.address];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id: index_js_1.id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
const req = new index_js_1.FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? (0, index_js_1.toUtf8String)(_req.body) : "");
|
||||
let statusCode = 200;
|
||||
const headers = {};
|
||||
let resp;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
}
|
||||
else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
const body = (0, index_js_1.toUtf8Bytes)(JSON.stringify(resp));
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
return new index_js_1.JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
describe("Ensure Catchable Errors", function () {
|
||||
it("Can catch bad broadcast replies", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = index_js_1.Transaction.from(txSign);
|
||||
let count = 0;
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw (0, index_js_1.makeError)("Faux Error", "SERVER_ERROR", {
|
||||
request: ({})
|
||||
});
|
||||
}
|
||||
// Second time; return null
|
||||
if (count === 2) {
|
||||
return null;
|
||||
}
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address });
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = (0, index_js_1.id)("test");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
(0, assert_1.default)(tx);
|
||||
});
|
||||
it("Missing v is recovered", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = index_js_1.Transaction.from(txSign);
|
||||
let count = 0;
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address, sig: null });
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) {
|
||||
delete result.v;
|
||||
}
|
||||
// Debug
|
||||
result._count = count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// Track any "missing v" error
|
||||
let missingV = null;
|
||||
provider.on("error", (e) => {
|
||||
if ((0, index_js_1.isError)(e, "UNKNOWN_ERROR") && (0, index_js_1.isError)(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert_1.default.ok(!!tx, "we got a transaction");
|
||||
assert_1.default.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=test-provider-jsonrpc.js.map
|
||||
1
lib.commonjs/_tests/test-provider-jsonrpc.js.map
Normal file
1
lib.commonjs/_tests/test-provider-jsonrpc.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -5,5 +5,5 @@ exports.version = void 0;
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
exports.version = "6.9.0";
|
||||
exports.version = "6.9.1";
|
||||
//# sourceMappingURL=_version.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -544,16 +544,19 @@ class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -136,12 +136,45 @@ class JsonRpcSigner extends abstract_signer_js_1.AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if ((0, index_js_5.isError)(error, "CANCELLED") || (0, index_js_5.isError)(error, "BAD_DATA") ||
|
||||
(0, index_js_5.isError)(error, "NETWORK_ERROR" || (0, index_js_5.isError)(error, "UNSUPPORTED_OPERATION"))) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if ((0, index_js_5.isError)(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", (0, index_js_5.makeError)("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -220,7 +253,7 @@ class JsonRpcApiProvider extends abstract_provider_js_1.AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -383,9 +416,15 @@ class JsonRpcApiProvider extends abstract_provider_js_1.AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = network_js_1.Network.from((0, index_js_5.getBigInt)(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = network_js_1.Network.from((0, index_js_5.getBigInt)(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
2
lib.esm/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
2
lib.esm/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=test-provider-jsonrpc.d.ts.map
|
||||
1
lib.esm/_tests/test-provider-jsonrpc.d.ts.map
Normal file
1
lib.esm/_tests/test-provider-jsonrpc.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"test-provider-jsonrpc.d.ts","sourceRoot":"","sources":["../../src.ts/_tests/test-provider-jsonrpc.ts"],"names":[],"mappings":""}
|
||||
157
lib.esm/_tests/test-provider-jsonrpc.js
Normal file
157
lib.esm/_tests/test-provider-jsonrpc.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import assert from "assert";
|
||||
import { id, isError, makeError, toUtf8Bytes, toUtf8String, FetchRequest, JsonRpcProvider, Transaction, Wallet } from "../index.js";
|
||||
const StatusMessages = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
const wallet = new Wallet(id("test"));
|
||||
function createProvider(testFunc) {
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++; }, 100);
|
||||
if (ticker.unref) {
|
||||
ticker.unref();
|
||||
}
|
||||
const processReq = (req) => {
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [wallet.address];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
const req = new FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? toUtf8String(_req.body) : "");
|
||||
let statusCode = 200;
|
||||
const headers = {};
|
||||
let resp;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
}
|
||||
else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
const body = toUtf8Bytes(JSON.stringify(resp));
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
return new JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
describe("Ensure Catchable Errors", function () {
|
||||
it("Can catch bad broadcast replies", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
let count = 0;
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw makeError("Faux Error", "SERVER_ERROR", {
|
||||
request: ({})
|
||||
});
|
||||
}
|
||||
// Second time; return null
|
||||
if (count === 2) {
|
||||
return null;
|
||||
}
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address });
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = id("test");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert(tx);
|
||||
});
|
||||
it("Missing v is recovered", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
let count = 0;
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address, sig: null });
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) {
|
||||
delete result.v;
|
||||
}
|
||||
// Debug
|
||||
result._count = count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// Track any "missing v" error
|
||||
let missingV = null;
|
||||
provider.on("error", (e) => {
|
||||
if (isError(e, "UNKNOWN_ERROR") && isError(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert.ok(!!tx, "we got a transaction");
|
||||
assert.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=test-provider-jsonrpc.js.map
|
||||
1
lib.esm/_tests/test-provider-jsonrpc.js.map
Normal file
1
lib.esm/_tests/test-provider-jsonrpc.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -2,5 +2,5 @@
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
export const version = "6.9.0";
|
||||
export const version = "6.9.1";
|
||||
//# sourceMappingURL=_version.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -540,16 +540,19 @@ export class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ import { AbiCoder } from "../abi/index.js";
|
||||
import { getAddress, resolveAddress } from "../address/index.js";
|
||||
import { TypedDataEncoder } from "../hash/index.js";
|
||||
import { accessListify } from "../transaction/index.js";
|
||||
import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";
|
||||
import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, isError, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";
|
||||
import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js";
|
||||
import { AbstractSigner } from "./abstract-signer.js";
|
||||
import { Network } from "./network.js";
|
||||
@@ -133,12 +133,45 @@ export class JsonRpcSigner extends AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" || isError(error, "UNSUPPORTED_OPERATION"))) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -216,7 +249,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -379,9 +412,15 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -93,7 +93,7 @@
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"gitHead": "f8f11c754aa2c9b541db73d3bde66a8ffa5146f0",
|
||||
"gitHead": "180221574c5d2af9ad85404af4fab8752d3d5029",
|
||||
"homepage": "https://ethers.org",
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -128,8 +128,8 @@
|
||||
"test-browser": "node lib.esm/_admin/test-browser",
|
||||
"test-commonjs": "mocha --reporter ./reporter.cjs ./lib.commonjs/_tests/test-*.js",
|
||||
"test-coverage": "c8 -o output -r lcov -r text mocha --no-color --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js | tee output/summary.txt",
|
||||
"test-esm": "mocha --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
||||
"test-esm": "mocha --trace-warnings --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "6.9.0"
|
||||
"version": "6.9.1"
|
||||
}
|
||||
|
||||
207
src.ts/_tests/test-provider-jsonrpc.ts
Normal file
207
src.ts/_tests/test-provider-jsonrpc.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import assert from "assert";
|
||||
|
||||
import {
|
||||
id, isError, makeError, toUtf8Bytes, toUtf8String,
|
||||
FetchRequest,
|
||||
JsonRpcProvider, Transaction, Wallet
|
||||
} from "../index.js";
|
||||
|
||||
const StatusMessages: Record<number, string> = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
|
||||
|
||||
type ProcessRequest = (method: string, params: Array<string>, blockNumber: number) => any;
|
||||
|
||||
const wallet = new Wallet(id("test"));
|
||||
|
||||
function createProvider(testFunc: ProcessRequest): JsonRpcProvider {
|
||||
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++ }, 100);
|
||||
if (ticker.unref) { ticker.unref(); }
|
||||
|
||||
const processReq = (req: { method: string, params: Array<string>, id: any }) => {
|
||||
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [ wallet.address ];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
|
||||
const req = new FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? toUtf8String(_req.body): "");
|
||||
|
||||
let statusCode = 200;
|
||||
const headers = { };
|
||||
|
||||
let resp: any;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
} else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
|
||||
} catch(error: any) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
|
||||
const body = toUtf8Bytes(JSON.stringify(resp));
|
||||
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
|
||||
return new JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
|
||||
describe("Ensure Catchable Errors", function() {
|
||||
it("Can catch bad broadcast replies", async function() {
|
||||
this.timeout(15000);
|
||||
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
|
||||
let count = 0;
|
||||
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw makeError("Faux Error", "SERVER_ERROR", {
|
||||
request: <any>({ })
|
||||
});
|
||||
}
|
||||
|
||||
// Second time; return null
|
||||
if (count === 2) { return null; }
|
||||
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({ },
|
||||
txObj.toJSON(),
|
||||
txObj.signature!.toJSON(),
|
||||
{ hash: txObj.hash, from: wallet.address });
|
||||
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = id("test");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert(tx);
|
||||
});
|
||||
|
||||
|
||||
it("Missing v is recovered", async function() {
|
||||
this.timeout(15000);
|
||||
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
|
||||
let count = 0;
|
||||
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({ },
|
||||
txObj.toJSON(),
|
||||
txObj.signature!.toJSON(),
|
||||
{ hash: txObj.hash, from: wallet.address, sig: null });
|
||||
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) { delete result.v; }
|
||||
|
||||
// Debug
|
||||
result._count = count;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Track any "missing v" error
|
||||
let missingV: Error | null = null;
|
||||
provider.on("error", (e) => {
|
||||
if (isError(e, "UNKNOWN_ERROR") && isError(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert.ok(!!tx, "we got a transaction");
|
||||
assert.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
export const version: string = "6.9.0";
|
||||
export const version: string = "6.9.1";
|
||||
|
||||
@@ -862,16 +862,18 @@ export class AbstractProvider implements Provider {
|
||||
if (this.#networkPromise == null) {
|
||||
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
} catch (error) {
|
||||
if (this.#networkPromise === detectNetwork!) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
})();
|
||||
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TypedDataEncoder } from "../hash/index.js";
|
||||
import { accessListify } from "../transaction/index.js";
|
||||
import {
|
||||
defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes,
|
||||
makeError, assert, assertArgument,
|
||||
isError, makeError, assert, assertArgument,
|
||||
FetchRequest, resolveProperties
|
||||
} from "../utils/index.js";
|
||||
|
||||
@@ -41,7 +41,6 @@ import type { Signer } from "./signer.js";
|
||||
|
||||
type Timer = ReturnType<typeof setTimeout>;
|
||||
|
||||
|
||||
const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g);
|
||||
//const Methods = "getAddress,then".split(/,/g);
|
||||
function deepCopy<T = any>(value: T): T {
|
||||
@@ -363,12 +362,49 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [ 1000, 100 ];
|
||||
let invalids = 0;
|
||||
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" || isError(error, "UNSUPPORTED_OPERATION"))) {
|
||||
|
||||
if (error.info == null) { error.info = { }; }
|
||||
error.info.sendTransactionHash = hash;
|
||||
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) { error.info = { }; }
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
|
||||
// Wait another 4 seconds
|
||||
@@ -468,7 +504,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
#scheduleDrain(): void {
|
||||
if (this.#drainTimer) { return; }
|
||||
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0: this._getOption("batchStallTime");
|
||||
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
@@ -613,6 +649,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
* and should generally call ``super._perform`` as a fallback.
|
||||
*/
|
||||
async _perform(req: PerformActionRequest): Promise<any> {
|
||||
|
||||
// Legacy networks do not like the type field being passed along (which
|
||||
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
|
||||
if (req.method === "call" || req.method === "estimateGas") {
|
||||
@@ -664,9 +701,14 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [ ])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [ ])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
@@ -1186,7 +1228,6 @@ export class JsonRpcProvider extends JsonRpcApiPollingProvider {
|
||||
const request = this._getConnection();
|
||||
request.body = JSON.stringify(payload);
|
||||
request.setHeader("content-type", "application/json");
|
||||
|
||||
const response = await request.send();
|
||||
response.assertOk();
|
||||
|
||||
|
||||
@@ -42,12 +42,21 @@ const BN_58 = BigInt(58);
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
export function encodeBase58(_value: BytesLike): string {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
const bytes = getBytes(_value);
|
||||
|
||||
let value = toBigInt(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) { break; }
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user