@leofcoin/chain 1.10.20 → 1.10.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,4 +6,42 @@
6
6
 
7
7
  - reachedOneZeroZero
8
8
 
9
+ ## Cold-sync reliability
10
+
11
+ Both server and browser nodes guard provider discovery against fast negative
12
+ responses. A walk succeeds only after a connected peer confirms it provides
13
+ the requested hash; individual discovery requests retain the protocol request
14
+ timeout.
15
+
16
+ Chain content requests probe the exact requested store before requesting bytes
17
+ from a peer. A pending-only copy cannot act as a provider for the canonical
18
+ transaction store. Responses must use the expected codec and advertised hash;
19
+ the first valid response wins, and unavailable content fails explicitly.
20
+
21
+ Historical ancestry is resolved iteratively, finishing transaction dependencies
22
+ before requesting the next parent. Duplicate callers await the same resolution,
23
+ completed blocks survive retries, and missing or cyclic ancestry fails explicitly.
24
+ Only dependencies of the selected target tip are resolved; unrelated hashes in
25
+ a peer's `knownBlocks` inventory do not gate canonical catch-up.
26
+ Transaction bodies are hash-checked before entering the canonical transaction
27
+ store. Resolving data never applies it: canonical loading still uses the
28
+ serialized state writer and chain-link validation.
29
+
30
+ ## Pending transaction propagation
31
+
32
+ Transactions enter the local pending pool before canonical confirmation. Peers
33
+ relay newly accepted transaction bytes using the existing `add-transaction`
34
+ topic; duplicate and already-confirmed entries are not relayed again.
35
+
36
+ On compatible peer reconnection, pending pools are reconciled even at equal
37
+ block heights. Missing bodies are fetched from `transactionPool`, checked against
38
+ their advertised hash, and admitted through the normal signature-validation and
39
+ pending-nonce path. Unavailable or invalid entries are logged without preventing
40
+ recovery of the remaining entries.
41
+
42
+ Pool admission and pubsub publication are not validator acknowledgements.
43
+ `sendTransaction().wait` resolves only after canonical block inclusion. Deploy
44
+ the updated chain runtime to intermediate nodes and validators as well as the
45
+ browser to enable relay and reconnect recovery throughout the network.
46
+
9
47
  <a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/"><img alt="Creative Commons Licence" style="border-width:0" src="https://i.creativecommons.org/l/by-nc/4.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc/4.0/">Creative Commons Attribution-NonCommercial 4.0 International License</a>.
@@ -1,4 +1,4 @@
1
- import { s as signTransaction, c as MultiWallet, d as createTransactionHash } from './index-DCbLvMKL.js';
1
+ import { s as signTransaction, c as MultiWallet, d as createTransactionHash } from './index-dxD17Fom.js';
2
2
  import { f as fromBase58 } from './index-CvKt4UDE.js';
3
3
  import './browser-crypto-CH0ua22L.js';
4
4
  import './sha2-BKcEJzgd.js';
@@ -1,8 +1,8 @@
1
- import { g as getDefaultExportFromCjs } from './node-browser-Ha4Lvntj.js';
1
+ import { g as getDefaultExportFromCjs } from './node-browser-BMfelxXV.js';
2
2
  import './browser-crypto-CH0ua22L.js';
3
3
  import './index-CvKt4UDE.js';
4
4
  import './sha2-BKcEJzgd.js';
5
- import './connection-monitor-D-krNiRH.js';
5
+ import './connection-monitor-DMGAhcC0.js';
6
6
 
7
7
  function _mergeNamespaces$1(n, m) {
8
8
  m.forEach(function (e) {
@@ -1,6 +1,6 @@
1
1
  import { F as FormatInterface, p as proto, a as proto$1, b as proto$2, T as TransactionMessage, j as jsonStringifyBigInt, c as jsonParseBigInt, C as ContractMessage, d as Codec } from './browser-crypto-CH0ua22L.js';
2
- import { B as BlockMessage, L as LastBlockMessage, P as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO, C as ConnectionMonitor, a as BWMessage, S as StateMessage } from './connection-monitor-D-krNiRH.js';
3
- import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as distributeTransactionFee, h as supportsTransactionFees, i as supportsMonetaryPolicy, v as validateBlockResourceLimits, j as calculateMonetaryPolicy, k as distributeAmount, l as aggregateValidatorFees, m as MONETARY_POLICY_AUTHORITY, n as contractFactoryMessage, o as nativeTokenMessage, p as validatorsMessage, q as nameServiceMessage, t as parseUnits, u as formatUnits } from './index-DCbLvMKL.js';
2
+ import { P as Protocol, B as BlockMessage, L as LastBlockMessage, a as PROTOCOL_VERSION, R as REACHED_ONE_ZERO_ZERO, C as ConnectionMonitor, b as BWMessage, S as StateMessage } from './connection-monitor-DMGAhcC0.js';
3
+ import { M as MAX_TRANSACTION_BYTES, a as MAX_BLOCK_TRANSACTION_BYTES, b as MAX_BLOCK_TRANSACTIONS, c as MultiWallet, d as createTransactionHash, e as calculateFee, f as createContractMessage, s as signTransaction, r as randombytes, g as distributeTransactionFee, h as supportsTransactionFees, i as supportsMonetaryPolicy, v as validateBlockResourceLimits, j as calculateMonetaryPolicy, k as distributeAmount, l as aggregateValidatorFees, m as MONETARY_POLICY_AUTHORITY, n as contractFactoryMessage, o as nativeTokenMessage, p as validatorsMessage, q as nameServiceMessage, t as parseUnits, u as formatUnits } from './index-dxD17Fom.js';
4
4
  import { f as fromBase58 } from './index-CvKt4UDE.js';
5
5
  import { g as getDefaultExportFromCjs } from './_commonjsHelpers-B85MJLTf.js';
6
6
  import { q as quorumThreshold } from './quorum-S_qdgiAY.js';
@@ -2915,29 +2915,6 @@ function requireSemver () {
2915
2915
  var semverExports = requireSemver();
2916
2916
  var semver = /*@__PURE__*/getDefaultExportFromCjs(semverExports);
2917
2917
 
2918
- const limit = 1800;
2919
- const transactionLimit = 2500;
2920
- const requestTimeout = 3e4;
2921
- const syncTimeout = 3e4;
2922
- class Protocol {
2923
- constructor(config) {
2924
- this.resolveTimeout = 1e4;
2925
- if (config?.resolveTimeout) this.resolveTimeout = config.resolveTimeout;
2926
- }
2927
- get limit() {
2928
- return limit;
2929
- }
2930
- get transactionLimit() {
2931
- return transactionLimit;
2932
- }
2933
- get requestTimeout() {
2934
- return requestTimeout;
2935
- }
2936
- get syncTimeout() {
2937
- return syncTimeout;
2938
- }
2939
- }
2940
-
2941
2918
  const waitForCanonicalTransaction = (pubsub, transactionPool, hash) => new Promise((resolve, reject) => {
2942
2919
  const completionTopic = `transaction.completed.${hash}`;
2943
2920
  const cleanup = () => {
@@ -3164,7 +3141,7 @@ class Transaction extends Protocol {
3164
3141
  await globalThis.transactionPoolStore.put(hash, message.encoded);
3165
3142
  this.addPendingNonce(message.decoded.from, message.decoded.nonce);
3166
3143
  try {
3167
- peernet.publish("add-transaction", message.encoded);
3144
+ await peernet.publish("add-transaction", message.encoded);
3168
3145
  } catch (publishError) {
3169
3146
  console.warn("peernet publish failed: add-transaction", publishError?.message ?? publishError);
3170
3147
  }
@@ -4304,6 +4281,15 @@ const validateChainLink = (localTip, incoming) => {
4304
4281
  return "append";
4305
4282
  };
4306
4283
 
4284
+ const resolveTransactionReference = async (expectedHash, transactionData) => {
4285
+ const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
4286
+ const actualHash = await transaction.hash();
4287
+ if (actualHash !== expectedHash) {
4288
+ throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
4289
+ }
4290
+ return transaction;
4291
+ };
4292
+
4307
4293
  const debug$1 = createDebugger("leofcoin/state");
4308
4294
  class State extends Contract {
4309
4295
  constructor(config) {
@@ -4320,7 +4306,7 @@ class State extends Contract {
4320
4306
  this.#totalSize = 0;
4321
4307
  this.#canonicalStateQueue = new CanonicalStateQueue();
4322
4308
  this.#loaded = false;
4323
- this.#resolvingHashes = /* @__PURE__ */ new Set();
4309
+ this.#resolvingHashes = /* @__PURE__ */ new Map();
4324
4310
  this._wantList = [];
4325
4311
  this.#chainStateHandler = () => {
4326
4312
  return new globalThis.peernet.protos["peernet-response"]({
@@ -4576,7 +4562,8 @@ class State extends Contract {
4576
4562
  if (!exists) {
4577
4563
  const data = await peernet.get(hash, "transaction");
4578
4564
  if (!data) throw new Error(`missing transaction data for ${hash}`);
4579
- await transactionStore.put(hash, data);
4565
+ const transaction = await resolveTransactionReference(hash, data);
4566
+ await transactionStore.put(hash, transaction.encoded);
4580
4567
  }
4581
4568
  const inPool = await transactionPoolStore.has(hash);
4582
4569
  if (inPool) await transactionPoolStore.delete(hash);
@@ -4584,84 +4571,61 @@ class State extends Contract {
4584
4571
  );
4585
4572
  }
4586
4573
  async #resolveBlock(hash) {
4587
- let index = this.#blockHashMap.get(hash);
4588
- let localHash = "0x0";
4589
- try {
4590
- const rawLocalHash = await globalThis.stateStore.get("lastBlock");
4591
- if (rawLocalHash) {
4592
- const decoded = JSON.parse(new TextDecoder().decode(rawLocalHash));
4593
- localHash = typeof decoded === "string" ? decoded : decoded?.hash ?? "0x0";
4594
- }
4595
- } catch (error) {
4596
- debug$1("no local state found");
4597
- }
4598
- if (this.#blocks[index]) {
4599
- const previousHash = this.#blocks[index].previousHash;
4600
- if (previousHash === localHash) return;
4601
- if (previousHash !== "0x0") {
4602
- return this.resolveBlock(previousHash);
4603
- } else {
4604
- return;
4574
+ const localHash = (await this.canonicalTip()).hash;
4575
+ const visited = /* @__PURE__ */ new Set();
4576
+ while (hash !== "0x0" && hash !== localHash) {
4577
+ if (visited.has(hash)) throw new ResolveError(`cyclic block ancestry at ${hash}`);
4578
+ visited.add(hash);
4579
+ const cachedIndex = this.#blockHashMap.get(hash);
4580
+ const cached = this.#blocks[cachedIndex];
4581
+ if (cached) {
4582
+ hash = cached.previousHash;
4583
+ continue;
4605
4584
  }
4606
- }
4607
- try {
4608
- const block = await this.getAndPutBlock(hash);
4609
- const promises = [];
4610
- if (block.decoded.previousHash !== "0x0" && block.decoded.previousHash !== localHash) {
4611
- promises.push(this.resolveBlock(block.decoded.previousHash));
4585
+ try {
4586
+ const block = await this.getAndPutBlock(hash);
4587
+ await this.#resolveTransactions(block.decoded.transactions);
4588
+ const index = block.decoded.index;
4589
+ const size = block.encoded.byteLength;
4590
+ this.#totalSize += size;
4591
+ this.#blocks[index] = { hash, ...block.decoded };
4592
+ this.#blockHashMap.set(hash, index);
4593
+ debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
4594
+ globalThis.pubsub.publish("block-resolved", { hash, index });
4595
+ this.#lastResolved = this.#blocks[index];
4596
+ this.#lastResolvedTime = Date.now();
4597
+ hash = block.decoded.previousHash;
4598
+ } catch (error) {
4599
+ throw new ResolveError(`block: ${hash}`, { cause: error });
4612
4600
  }
4613
- promises.push(this.#resolveTransactions(block.decoded.transactions));
4614
- await Promise.all(promises);
4615
- index = block.decoded.index;
4616
- const size = block.encoded.length > 0 ? block.encoded.length : block.encoded.byteLength;
4617
- this.#totalSize += size;
4618
- this.#blocks[index] = { hash, ...block.decoded };
4619
- this.#blockHashMap.set(hash, index);
4620
- debug$1(`resolved block: ${hash} @${index} ${formatBytes(size)}`);
4621
- globalThis.pubsub.publish("block-resolved", { hash, index });
4622
- this.#lastResolved = this.#blocks[index];
4623
- this.#lastResolvedTime = Date.now();
4624
- } catch (error) {
4625
- throw new ResolveError(`block: ${hash}@${index}`, { cause: error });
4626
4601
  }
4627
- return;
4628
4602
  }
4629
4603
  async resolveBlock(hash) {
4630
4604
  if (!hash) throw new Error(`expected hash, got: ${hash}`);
4631
4605
  if (hash === "0x0") return;
4632
- if (this.#resolvingHashes.has(hash)) return;
4633
- this.#resolvingHashes.add(hash);
4634
- const isEntering = this.#resolvingHashes.size === 1;
4606
+ const existing = this.#resolvingHashes.get(hash);
4607
+ if (existing) return existing;
4635
4608
  this.#resolving = true;
4636
- try {
4637
- if (isEntering) {
4638
- if (this.jobber.busy && this.jobber.destroy) await this.jobber.destroy();
4639
- await this.jobber.add(() => this.#resolveBlock(hash));
4640
- } else {
4641
- await this.#resolveBlock(hash);
4642
- }
4643
- try {
4644
- const lastBlockHash = await globalThis.stateStore.get("lastBlock");
4645
- if (lastBlockHash === hash) {
4609
+ const resolution = Promise.resolve().then(async () => {
4610
+ for (let attempt = 0; attempt < 3; attempt++) {
4611
+ try {
4612
+ await this.#resolveBlock(hash);
4646
4613
  this.#resolveErrored = false;
4647
4614
  return;
4615
+ } catch (error) {
4616
+ console.error(`block resolution attempt ${attempt + 1} failed for ${hash}`, error);
4617
+ if (attempt === 2) {
4618
+ if (!this.wantList.includes(hash)) this.wantList.push(hash);
4619
+ throw new ResolveError(`block: ${hash}`, { cause: error });
4620
+ }
4648
4621
  }
4649
- } catch (error) {
4650
- }
4651
- } catch (error) {
4652
- console.log({ error });
4653
- this.#resolveErrorCount += 1;
4654
- if (this.#resolveErrorCount < 3) {
4655
- this.#resolvingHashes.delete(hash);
4656
- return this.resolveBlock(hash);
4657
4622
  }
4658
- this.#resolveErrorCount = 0;
4659
- this.wantList.push(hash);
4660
- throw new ResolveError(`block: ${hash}`, { cause: error });
4661
- } finally {
4623
+ }).finally(() => {
4662
4624
  this.#resolvingHashes.delete(hash);
4663
4625
  if (this.#resolvingHashes.size === 0) this.#resolving = false;
4664
- }
4626
+ });
4627
+ this.#resolvingHashes.set(hash, resolution);
4628
+ return resolution;
4665
4629
  }
4666
4630
  async resolveBlocks() {
4667
4631
  if (this.#chainSyncing || this.#resolving) {
@@ -4798,24 +4762,6 @@ class State extends Contract {
4798
4762
  return;
4799
4763
  }
4800
4764
  if (localStateHash !== remoteBlockHash) {
4801
- if (this.wantList.length > 0) {
4802
- debug$1(`Fetching ${this.wantList.length} blocks before resolving`);
4803
- const getBatch = async (batch) => {
4804
- const blocks2 = await Promise.all(
4805
- batch.map(
4806
- (hash) => this.getAndPutBlock(hash).catch((e) => {
4807
- console.warn(`failed to fetch block ${hash}`, e);
4808
- })
4809
- )
4810
- );
4811
- const transactions = blocks2.filter((block) => Boolean(block)).flatMap((block) => block.decoded.transactions);
4812
- return this.#resolveTransactions(transactions);
4813
- };
4814
- for (let i = 0; i < this.wantList.length; i += 50) {
4815
- const batch = this.wantList.slice(i, i + 50);
4816
- await getBatch(batch);
4817
- }
4818
- }
4819
4765
  debug$1(`Resolving remote block: ${remoteBlockHash} @${remoteIndex} (differs from local state)`);
4820
4766
  await this.resolveBlock(remoteBlockHash);
4821
4767
  const blocksSynced = remoteIndex - localIndex;
@@ -5171,15 +5117,6 @@ const validateBlockEconomics = (block, calculatedFees, validatorFees = /* @__PUR
5171
5117
  }
5172
5118
  };
5173
5119
 
5174
- const resolveTransactionReference = async (expectedHash, transactionData) => {
5175
- const transaction = transactionData instanceof TransactionMessage ? transactionData : new TransactionMessage(transactionData);
5176
- const actualHash = await transaction.hash();
5177
- if (actualHash !== expectedHash) {
5178
- throw new Error(`Transaction hash mismatch: expected ${expectedHash}, got ${actualHash}`);
5179
- }
5180
- return transaction;
5181
- };
5182
-
5183
5120
  const consensusSignableData = (validatorsAddress, type, message) => ({
5184
5121
  from: String(message.from),
5185
5122
  to: validatorsAddress,
@@ -6077,39 +6014,29 @@ class Chain extends VersionControl {
6077
6014
  }
6078
6015
  async getPeerTransactionPool(peer) {
6079
6016
  let transactionsInPool = await this.#makeRequest(peer, "transactionPool");
6080
- console.log("raw response for request transactionPool:", transactionsInPool);
6081
6017
  if (transactionsInPool instanceof Uint8Array) {
6082
- try {
6083
- transactionsInPool = JSON.parse(new TextDecoder().decode(transactionsInPool));
6084
- } catch {
6085
- debug("transactionPool response must be decoded array payload");
6086
- return [];
6087
- }
6018
+ transactionsInPool = new TextDecoder().decode(transactionsInPool);
6088
6019
  }
6089
6020
  if (typeof transactionsInPool === "string") {
6090
- try {
6091
- transactionsInPool = JSON.parse(transactionsInPool);
6092
- } catch {
6093
- return [];
6094
- }
6021
+ transactionsInPool = JSON.parse(transactionsInPool);
6095
6022
  }
6096
- if (!Array.isArray(transactionsInPool)) return [];
6097
- const localTransactions = new Set(await globalThis.transactionPoolStore.keys());
6098
- const transactionsToGet = [];
6099
- for (const key of transactionsInPool) {
6100
- if (!localTransactions.has(key)) {
6101
- let txData;
6102
- try {
6103
- txData = await globalThis.peernet.get(key, "transaction");
6104
- } catch (error) {
6105
- debug(`Failed to get transaction ${key}:`, error?.message ?? error);
6106
- }
6107
- if (txData !== void 0) {
6108
- transactionsToGet.push(transactionPoolStore.put(key, txData));
6109
- }
6023
+ if (!Array.isArray(transactionsInPool) || transactionsInPool.some((hash) => typeof hash !== "string")) {
6024
+ throw new Error("transactionPool response must be an array of transaction hashes");
6025
+ }
6026
+ const recovered = [];
6027
+ for (const hash of new Set(transactionsInPool)) {
6028
+ if (await globalThis.transactionPoolStore.has(hash)) continue;
6029
+ if (await globalThis.transactionStore.has(hash)) continue;
6030
+ try {
6031
+ const txData = await globalThis.peernet.get(hash, "transactionPool");
6032
+ if (txData === void 0) throw new Error("pending transaction body unavailable");
6033
+ const transaction = await resolveTransactionReference(hash, txData);
6034
+ recovered.push(await this.#sendTransaction(transaction));
6035
+ } catch (error) {
6036
+ debug(`Failed to recover transaction ${hash}:`, error?.message ?? error);
6110
6037
  }
6111
6038
  }
6112
- return Promise.all(transactionsToGet);
6039
+ return recovered;
6113
6040
  }
6114
6041
  async #peerConnected(peerId) {
6115
6042
  if (typeof peerId !== "string" || !peerId.trim() || peerId === "undefined") {
@@ -6177,6 +6104,14 @@ class Chain extends VersionControl {
6177
6104
  await this.#recordPeerFailure(peerId, `unreasonable lastBlock index: ${lastBlock.index}`);
6178
6105
  return;
6179
6106
  }
6107
+ setTimeout(async () => {
6108
+ try {
6109
+ await this.getPeerTransactionPool(peer);
6110
+ } catch (error) {
6111
+ const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
6112
+ debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
6113
+ }
6114
+ }, 3e3);
6180
6115
  if (!lastBlock || !lastBlock.hash || lastBlock.hash === "0x0") {
6181
6116
  debug(`peer has no lastBlock: ${peerId}`);
6182
6117
  return;
@@ -6209,22 +6144,6 @@ class Chain extends VersionControl {
6209
6144
  }
6210
6145
  }
6211
6146
  }
6212
- if (this.wantList.length > 0) {
6213
- const promises = await Promise.allSettled(this.wantList.map((hash) => peernet.get(hash, "block")));
6214
- for (let i = promises.length - 1; i >= 0; i -= 1) {
6215
- if (promises[i].status === "fulfilled") this.wantList.splice(i, 1);
6216
- }
6217
- if (this.wantList.length === 0) await this.triggerSync();
6218
- }
6219
- setTimeout(async () => {
6220
- try {
6221
- const peerTransactionPool = higherThenCurrentLocal && await this.getPeerTransactionPool(peer) || [];
6222
- if (this.#participating && peerTransactionPool.length > 0) return this.#runEpoch();
6223
- } catch (error) {
6224
- const peerName = peer?.peerId || peer?.id || peer?.address || peerId || "unknown";
6225
- debug(`transactionPool request failed: ${peerName}:`, error?.message ?? error);
6226
- }
6227
- }, 3e3);
6228
6147
  try {
6229
6148
  let stateInfo = await this.#makeRequest(peer, "stateInfo");
6230
6149
  if (stateInfo instanceof Uint8Array) {
@@ -6601,18 +6520,32 @@ class Chain extends VersionControl {
6601
6520
  await this.validateTransactionSignature(transaction);
6602
6521
  let hash;
6603
6522
  try {
6523
+ hash = await transaction.hash();
6524
+ if (await globalThis.transactionStore.has(hash)) return hash;
6525
+ const alreadyPending = await globalThis.transactionPoolStore.has(hash);
6604
6526
  hash = await enqueueTransaction(transaction, {
6605
6527
  putToPool: (txHash, data) => globalThis.transactionPoolStore.put(txHash, data),
6606
6528
  hasInPool: (txHash) => globalThis.transactionPoolStore.has(txHash),
6607
6529
  hasInStore: (txHash) => globalThis.transactionStore.has(txHash),
6608
6530
  addPendingNonce: (address, nonce) => this.addPendingNonce(address, nonce)
6609
6531
  });
6610
- if (this.#participating && !this.#runningEpoch) this.#runEpoch();
6532
+ if (!alreadyPending) {
6533
+ try {
6534
+ await globalThis.peernet.publish("add-transaction", transaction.encoded);
6535
+ } catch (publishError) {
6536
+ debug("peernet publish failed: add-transaction relay", publishError?.message ?? publishError);
6537
+ }
6538
+ }
6539
+ if (this.#participating && !this.#runningEpoch) {
6540
+ void this.#runEpoch().catch((error) => {
6541
+ debug("transaction epoch scheduling failed:", error?.message ?? error);
6542
+ });
6543
+ }
6611
6544
  return hash;
6612
6545
  } catch (e) {
6613
6546
  if (hash) {
6614
6547
  try {
6615
- globalThis.peernet.publish("invalid-transaction", hash);
6548
+ await globalThis.peernet.publish("invalid-transaction", hash);
6616
6549
  } catch (publishError) {
6617
6550
  debug("peernet publish failed: invalid-transaction", publishError?.message ?? publishError);
6618
6551
  }
@@ -6630,7 +6563,7 @@ class Chain extends VersionControl {
6630
6563
  const event = await super.sendTransaction(transactionMessage);
6631
6564
  await this.#sendTransaction(transactionMessage.encoded);
6632
6565
  try {
6633
- globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
6566
+ await globalThis.peernet.publish("send-transaction", transactionMessage.encoded);
6634
6567
  } catch (publishError) {
6635
6568
  debug("peernet publish failed: send-transaction", publishError?.message ?? publishError);
6636
6569
  }