Die Reise einer Transaktion

In einigen Fällen kann eine scheinbar gültige Transaktion verworfen werden, bevor sie in einen Block aufgenommen wird. Dies tritt am häufigsten in Zeiten von Netzwerküberlastung auf, wenn ein RPC-Knoten die Transaktion nicht erneut an den [Leader] (https://docs.solana.com/terminology#leader) sendet. Für einen Endbenutzer kann es so aussehen, als würde seine Transaktion vollständig verschwinden. Während RPC-Knoten mit einem generischen Rebroadcasting-Algorithmus ausgestattet sind, sind Anwendungsentwickler auch in der Lage, ihre eigene benutzerdefinierte Rebroadcasting-Logik zu entwickeln.

Fakten

Fact Sheet

  • RPC-Knoten versuchen, Transaktionen unter Verwendung eines generischen Algorithmus erneut auszusenden
  • Anwendungsentwickler können ihre eigene benutzerdefinierte Rebroadcasting-Logik implementieren
  • Entwickler sollten den Parameter "maxRetries" in der JSON-RPC-Methode "sendTransaction" nutzen
  • Entwickler sollten Preflight-Prüfungen aktivieren, um Fehler zu melden, bevor Transaktionen übermittelt werden
  • Bevor Sie eine Transaktion neu signieren, ist es sehr wichtig sicherzustellen, dass der Blockhash der ursprünglichen Transaktion abgelaufen ist

Die Reise einer Transaktion

Wie Anwender Transaktionen übermitteln

In Solana gibt es kein Konzept eines Mempools. Alle Transaktionen, ob programmgesteuert oder von einem Endbenutzer initiiert, werden effizient an Leader weitergeleitet, damit sie in einem Block verarbeitet werden können. Es gibt zwei Hauptwege, auf denen eine Transaktion an Führungskräfte gesendet werden kann:

  1. Per Proxy über einen RPC-Server und die JSON-RPC-Methode sendTransactionopen in new window.
  2. Direkt zu Führungskräften über einen [TPU-Client] (https://docs.rs/solana-client/1.7.3/solana_client/tpu_client/index.html)

Die überwiegende Mehrheit der Endbenutzer übermittelt Transaktionen über einen RPC-Server. Wenn ein Client eine Transaktion übermittelt, versucht der empfangende RPC-Knoten wiederum, die Transaktion sowohl an den aktuellen als auch an den nächsten Leader zu senden. Bis die Transaktion von einem Leader verarbeitet wird, gibt es keine Aufzeichnung der Transaktion außerhalb dessen, was dem Client und den weiterleitenden RPC-Knoten bekannt ist. Im Fall eines TPU-Clients wird das Rebroadcasting und die Leader-Weiterleitung vollständig von der Client-Software gehandhabt.

Transaction Journey

Wie RPC-Knoten Transaktionen senden

Nachdem ein RPC-Knoten eine Transaktion über „sendTransaction“ empfangen hat, konvertiert er die Transaktion in ein UDPopen in new window-Paket, bevor er es an die relevanten Leader weiterleitet. UDP ermöglicht es Validatoren, schnell miteinander zu kommunizieren, bietet jedoch keine Garantien für die Transaktionszustellung.

Da Solanas Leader-Zeitplan vor jeder Epocheopen in new window (~2 Tage) bekannt ist, sendet ein RPC-Knoten seine Transaktion direkt an die aktuellen und nächsten Leader. Dies steht im Gegensatz zu anderen Klatschprotokollen wie Ethereum, die Transaktionen zufällig und breit über das gesamte Netzwerk verbreiten. Standardmäßig versuchen RPC-Knoten alle zwei Sekunden, Transaktionen an Leader weiterzuleiten, bis entweder die Transaktion abgeschlossen ist oder der Blockhash der Transaktion abläuft (150 Blöcke oder ~1 Minute 19 Sekunden zum Zeitpunkt des Schreibens dieses Artikels). Wenn die Größe der ausstehenden Rebroadcast-Warteschlange größer als 10.000 Transaktionenopen in new window ist, werden neu übermittelte Transaktionen fallengelassen. Es gibt Befehlszeilen-Argumenteopen in new window, die die Logik der RPC-Operatoren anpassen können, um das Standardverhalten dieser Wiederholung zu ändern.

Wenn ein RPC-Knoten eine Transaktion sendet, versucht er, die Transaktion an die Transaction Processing Unit (TPU)open in new window eines Leaders weiterzuleiten . Die TPU verarbeitet Transaktionen in fünf verschiedenen Phasen:

TPU OverviewImage Courtesy of Jito Labs

Von diesen fünf Phasen ist die Fetch-Phase für den Empfang von Transaktionen verantwortlich. Innerhalb der Fetch-Phase kategorisieren Validatoren eingehende Transaktionen nach drei Ports:

Weitere Informationen zum TPU finden Sie unter dieser hervorragenden Beschreibung von Jito Labsopen in new window.

Wie Transaktionen verworfen werden

Während der Reise einer Transaktion gibt es einige Szenarien, in denen die Transaktion unbeabsichtigt aus dem Netzwerk fallen gelassen werden kann.

Bevor eine Transaktion verarbeitet wird

Wenn das Netzwerk eine Transaktion verwirft, wird es dies höchstwahrscheinlich tun, bevor die Transaktion von einem Leader verarbeitet wird. UDP Paketverlustopen in new window ist der einfachste Grund, warum dies auftreten kann. In Zeiten intensiver Netzwerklast ist es auch möglich, dass Validierer von der schieren Anzahl der für die Verarbeitung erforderlichen Transaktionen überwältigt werden. Während Validatoren in der Lage sind, überschüssige Transaktionen über tpu_forwards weiterzuleiten, gibt es eine Grenze für die Datenmenge, die weitergeleitetopen in new window werden kann . Darüber hinaus ist jede Weiterleitung auf einen einzelnen Sprung zwischen Prüfern beschränkt. Das heißt, Transaktionen, die auf dem tpu_forwards-Port empfangen werden, werden nicht an andere Validatoren weitergeleitet.

Es gibt auch zwei weniger bekannte Gründe, warum eine Transaktion verworfen werden kann, bevor sie verarbeitet wird. Das erste Szenario umfasst Transaktionen, die über einen RPC-Pool übermittelt werden. Gelegentlich kann ein Teil des RPC-Pools dem Rest des Pools ausreichend voraus sein. Dies kann zu Problemen führen, wenn Knoten innerhalb des Pools zusammenarbeiten müssen. In diesem Beispiel wird der recentBlockhashopen in new window der Transaktion aus dem erweiterten Teil des Pools (Backend A) abgefragt. Wenn die Transaktion an den verzögerten Teil des Pools (Backend B) gesendet wird, erkennen die Knoten den erweiterten Blockhash nicht und verwerfen die Transaktion. Dies kann bei der Transaktionsübermittlung erkannt werden, wenn Entwickler Preflight-Prüfungenopen in new window für sendTransaction aktivieren.

Über RPC Pool gelöscht

Vorübergehende Netzwerk-Forks können auch zu abgebrochenen Transaktionen führen. Wenn ein Validator seine Blöcke innerhalb der Banking-Phase nur langsam wiedergibt, kann er am Ende einen Minority Fork erstellen. Wenn ein Client eine Transaktion erstellt, ist es möglich, dass die Transaktion auf einen recentBlockhash verweist, der nur auf dem Minority Fork existiert. Nachdem die Transaktion übermittelt wurde, kann der Cluster von seinem Minority Fork wegschalten, bevor die Transaktion verarbeitet wird. In diesem Szenario wird die Transaktion verworfen, weil der Blockhash nicht gefunden wird.

Aufgrund von Minority Fork fallen gelassen (vor der Verarbeitung)

Nachdem eine Transaktion verarbeitet wurde und bevor sie abgeschlossen wird

Für den Fall, dass eine Transaktion auf einen „recentBlockhash“ von einem Minority Fork verweist, ist es immer noch möglich, dass die Transaktion verarbeitet wird. In diesem Fall würde es jedoch vom Leader auf dem Minority Fork verarbeitet. Wenn dieser Anführer versucht, seine verarbeiteten Transaktionen mit dem Rest des Netzwerks zu teilen, würde er keinen Konsens mit der Mehrheit der Validatoren erzielen, die den Minority Fork nicht anerkennen. Zu diesem Zeitpunkt würde die Transaktion fallen gelassen, bevor sie abgeschlossen werden könnte.

Gefallen aufgrund von Minority Fork (nach Verarbeitung)

Umgang mit abgebrochenen Transaktionen

Während RPC-Knoten versuchen, Transaktionen erneut zu übertragen, ist der von ihnen verwendete Algorithmus generisch und oft für die Anforderungen bestimmter Anwendungen ungeeignet. Um sich auf Zeiten der Netzwerküberlastung vorzubereiten, sollten Anwendungsentwickler ihre eigene Rebroadcasting-Logik anpassen.

Ein eingehender Blick in sendTransaction

Wenn es um das Senden von Transaktionen geht, ist die RPC-Methode sendTransaction das primäre Tool, das Entwicklern zur Verfügung steht. sendTransaction ist nur für die Weiterleitung einer Transaktion von einem Client an einen RPC-Knoten verantwortlich. Wenn der Knoten die Transaktion empfängt, gibt sendTransaction die Transaktions-ID zurück, die zum Verfolgen der Transaktion verwendet werden kann. Eine erfolgreiche Antwort zeigt nicht an, ob die Transaktion vom Cluster verarbeitet oder abgeschlossen wird.

TIP

Request Parameters

  • transaction: string - vollständig signierte Transaktion als verschlüsselte Zeichenfolge
  • (optional) Konfigurationsobjekt: Objekt
    • skipPreflight: boolean - wenn wahr, die Preflight-Transaktionsprüfungen überspringen (Standard: falsch)
    • (optional) preflightCommitment: string - Commitmentopen in new window Level zur Verwendung für Preflight-Simulationen gegen den Bank-Slot (Standard: "abgeschlossen").
    • (optional) encoding: string - Codierung, die für die Transaktionsdaten verwendet werden. Entweder "base58" (langsam) oder "base64". (Standard: "base58").
    • (optional) maxRetries: usize - Maximale Anzahl von Malen für den RPC-Knoten, um erneut zu versuchen, die Transaktion an den Leader zu senden. Wenn dieser Parameter nicht angegeben wird, wiederholt der RPC-Knoten die Transaktion, bis sie abgeschlossen ist oder bis der Blockhash abläuft.

Antwort

  • transaction id: string – Erste in die Transaktion eingebettete Transaktionssignatur als base-58-codierte Zeichenfolge. Diese Transaktions-ID kann mit getSignatureStatusesopen in new window verwendet werden, um Statusaktualisierungen abzurufen.

Anpassen der Reroadcast-Logik

Um ihre eigene Rebroadcasting-Logik zu entwickeln, sollten Entwickler den Parameter maxRetries von sendTransaction nutzen. Falls angegeben, überschreibt maxRetries die standardmäßige Wiederholungslogik eines RPC-Knotens, sodass Entwickler den Wiederholungsprozess innerhalb vernünftiger Grenzenopen in new window manuell steuern können.

Ein gängiges Muster für das manuelle Wiederholen von Transaktionen besteht darin, lastValidBlockHeight vorübergehend zu speichern, das von getLatestBlockhashopen in new window stammt. Nach dem Zwischenspeichern kann eine Anwendung dann die Blockhöhe des Clusters abfragenopen in new window und die Transaktion in einem angemessenen Intervall manuell wiederholen. In Zeiten von Netzwerküberlastung ist es vorteilhaft, „maxRetries“ auf 0 zu setzen und manuell über einen benutzerdefinierten Algorithmus erneut zu senden. Während einige Anwendungen möglicherweise einen exponentiellen Backoffopen in new window-Algorithmus verwenden, entscheiden sich andere wie Mangoopen in new window dafür, Transaktionen in einem konstanten Intervall kontinuierlich erneut einzureichenopen in new window, bis eine Zeitüberschreitung aufgetreten ist.

Press </> button to view full source
import {
  Keypair,
  Connection,
  LAMPORTS_PER_SOL,
  SystemProgram,
  Transaction,
} from "@solana/web3.js";
import * as nacl from "tweetnacl";

const sleep = async (ms: number) => {
  return new Promise((r) => setTimeout(r, ms));
};

(async () => {
  const payer = Keypair.generate();
  const toAccount = Keypair.generate().publicKey;

  const connection = new Connection("http://127.0.0.1:8899", "confirmed");

  const airdropSignature = await connection.requestAirdrop(
    payer.publicKey,
    LAMPORTS_PER_SOL
  );

  await connection.confirmTransaction(airdropSignature);

  const blockhashResponse = await connection.getLatestBlockhashAndContext();
  const lastValidBlockHeight = blockhashResponse.context.slot + 150;

  const transaction = new Transaction({
    feePayer: payer.publicKey,
    blockhash: blockhashResponse.value.blockhash,
    lastValidBlockHeight: lastValidBlockHeight,
  }).add(
    SystemProgram.transfer({
      fromPubkey: payer.publicKey,
      toPubkey: toAccount,
      lamports: 1000000,
    })
  );
  const message = transaction.serializeMessage();
  const signature = nacl.sign.detached(message, payer.secretKey);
  transaction.addSignature(payer.publicKey, Buffer.from(signature));
  const rawTransaction = transaction.serialize();
  let blockheight = await connection.getBlockHeight();

  while (blockheight < lastValidBlockHeight) {
    connection.sendRawTransaction(rawTransaction, {
      skipPreflight: true,
    });
    await sleep(500);
    blockheight = await connection.getBlockHeight();
  }
})();

Bei der Abfrage über getLatestBlockhash sollten Anwendungen ihre beabsichtigte Commitmentopen in new window-Ebene angeben. Indem sie ihr Commitment auf „bestätigt“ (abgestimmt) oder „abgeschlossen“ (~30 Blöcke nach „bestätigt“) setzen, kann eine Anwendung vermeiden, einen Blockhash von einem Minority Fork abzufragen.

Wenn eine Anwendung Zugriff auf RPC-Knoten hinter einem Load Balancer hat, kann sie ihre Arbeitslast auch auf bestimmte Knoten aufteilen. RPC-Knoten, die datenintensive Anfragen wie getProgramAccounts bedienen, neigen möglicherweise dazu, ins Hintertreffen zu geraten, und können auch für die Weiterleitung von Transaktionen ungeeignet sein. Für Anwendungen, die zeitkritische Transaktionen verarbeiten, kann es ratsam sein, dedizierte Knoten zu haben, die nur "sendTransaction" verarbeiten.

Die Kosten für das Überspringen des Preflights

Standardmäßig führt "sendTransaction" drei Preflight-Prüfungen durch, bevor eine Transaktion übermittelt wird. Insbesondere wird sendTransaction:

  • Überprüfen, ob alle Unterschriften gültig sind
  • Überprüfen, ob der referenzierte Blockhash innerhalb der letzten 150 Blöcke liegt
  • Die Transaktion mit dem durch "preflightCommitment" angegebenen Bankfach simulieren

Für den Fall, dass eine dieser drei Preflight-Prüfungen fehlschlägt, wird sendTransaction einen Fehler auslösen, bevor die Transaktion übermittelt wird. Preflight-Prüfungen können oft den Unterschied zwischen dem Verlust einer Transaktion und der ordnungsgemäßen Behandlung eines Fehlers durch einen Client ausmachen. Um sicherzustellen, dass diese häufigen Fehler berücksichtigt werden, wird empfohlen, dass Entwickler skipPreflight auf false setzen.

Wann Transaktionen neu signiert werden müssen

Trotz aller Wiederholungsversuche kann es vorkommen, dass ein Kunde eine Transaktion erneut signieren muss. Bevor Sie eine Transaktion neu signieren, ist es sehr wichtig sicherzustellen, dass der Blockhash der ursprünglichen Transaktion abgelaufen ist. Wenn der anfängliche Blockhash noch gültig ist, ist es möglich, dass beide Transaktionen vom Netzwerk akzeptiert werden. Für einen Endbenutzer würde dies so aussehen, als ob er dieselbe Transaktion unbeabsichtigt zweimal gesendet hätte.

In Solana kann eine verworfene Transaktion sicher verworfen werden, sobald der Blockhash, auf den sie verweist, älter ist als die von „getLatestBlockhash“ empfangene „lastValidBlockHeight“. Entwickler sollten diese lastValidBlockHeight im Auge behalten, indem sie getEpochInfoopen in new window abfragen und mit blockHeight in der Antwort vergleichen. Sobald ein Blockhash für ungültig erklärt wurde, können Clients mit einem neu abgefragten Blockhash erneut signieren.

Danksagungen

Vielen Dank an Trent Nelson, Jacob Creechopen in new window, White Tiger, Le Yafo, Buffaluopen in new window und Jito Labsopen in new window für ihre Bewertung und ihr Feedback.

Last Updated:
Contributors: nyk