Skip to content
This repository has been archived by the owner on Mar 1, 2019. It is now read-only.

Submitting Transaction Byron

Pawel Jakubas edited this page Jan 4, 2019 · 3 revisions

Submission Layer (Byron era)

Executive summary The wallet submits transaction using diffusion layer directly. As both the wallet and diffusion layer components are bound to stay stitched together, the incumbent way of transaction submission is acceptable

In case of Byron era two kinds of nodes participate in transaction diffusion, ie., nodes with and without the wallet. In case of wallet-included node the transaction is submitted within wallet component, then pushed forward to the coupled diffusion layer that uses infra and networking elements together in order to disseminate the transaction between peers.

When active wallet is instantiated both wallet diffusion and passive wallet are initialized and included inside. The passive wallet layer comes with API call methods and a wallet state. Here, _walletSubmission is included. The corresponding action diagram, presented in Fig. 1, shows high level view of threads' causal relationship that is important for understanding submission layer. Other aspects of the wallet, not contributing to the comprehension of the submission mechanism, are omitted here.

Fig. 1. The relation of active wallet, submission layer and infra

Fig. 1. The relation of activate wallet, submission layer and infra

apiServer takes responsibility for updating infra diffusion when periodically triggered tick function returns. _walletSubmission is part of the passive wallet state and is shared with the dedicated submission layer thread that invokes tick. The submission layer is accountable also for managing pending transactions, eviction and choosing which ones are going to land in the current or future slots. The transactions chosen to be sent are injected into diffusion layer - here node-to-node communication takes place.

The detailed call flow when pay is invoked is presented below (redeemAda call also giving rise to transaction diffusion does not change conclusions here).

Fig. 2. The pay API call

Fig. 2. The pay API call

Upon pay call the transaction is constructed and new pending call updates _wallets which constitutes acid state DB. If DB updating proves successfull then the result is handed over to _walletSubmission. As this data is shared with SubmissionLayer thread, the periodically invoked tick function works on the updated pending set and returns (a) the set of transactions upper layers will need to drop, (b) the transactions to be sent and (c) the new WalletSubmission value. The transactions to be sent are pushed to diffusion layer. It is mediated by walletDiffusion value of the active wallet.

For information how submission layer relates to wallet spec refer to Section 10

In order to understand how diffusion layer transmits the transaction among the peers, first, the diffusion layer initialization is explained. It is controlled by the launcher during node initialization. Whether with or without wallet included, the node invokes runRealMode. Here, full diffusion layer is brought up with TCP transport and is ready to run actions within it. The possible actions, like sendTx that is of interest here, are enumerated in Diffusion data. diffusionLayerFullExposeInternals instantiates the value of Diffusion type that is used downstream (eg., on the level of active wallet). In case of sendTx the exact TxAux -> IO Bool call implementation is laid down. Important detail here is that, besides Diffusion value, outbound queue of messages is also set up. The outbound queue is initialized with static peers and worker that follows NAT and Kademlia nodes. When TxAux value is prodded through sendTx call the implemented method with the context of outbound queue is invoked. The detailed action diagram of diffusion layer initialization important for understanding sendTx propagation is below.

Fig. 3. Diffusion layer initialization

Fig. 3. Diffusion layer initialization

How diffusion works when sendTx is prodded can be followed in communication diagram in Fig. 4.

Fig. 4. Transaction diffusion

Fig. 4. Transaction diffusion

OutboundQueue from networking is workhorse for broadcasting sendTx. EnqueuedConversation is constructed first that consists of the message, node ids of known peers and protocol version :

newtype EnqueuedConversation t =
    EnqueuedConversation (Msg, NodeId -> PeerData -> Conversation PackingType t)

type Msg = MsgType NodeId
data NodeId = NodeId NT.EndPointAddress
type PeerData = VerInfo

MsgType here is MsgTransaction (Origin nid) as it is new transaction related message. After going through intermediary map with NodeId keys the final enqueue is of type:

type EnqueueMsg =
    forall t . Msg
    -> (NodeId -> PeerData -> NonEmpty (Conversation t))
    -> IO (Map NodeId (STM.TVar (OQ.PacketStatus t)))

After that the message can be physically disseminated among peers. Inv/Req/Data/Res conversation is started by invReqDataFlow and lasts until all conversations are finished. The logic behind the conversation is paved by invReqDataFlowDo - see infra/src/Pos/Infra/Communication/Relay/Logic.hs for details. sendTx returns 'True' if any peer accepted and applied this transaction.

The very essence of what is needed to diffuse transaction in Byron era

In Byron era in order to diffuse transaction to the peers the following prerequisites has to be met:

  • The hash of the submitted transaction is computed (from Pos.Crypto.Hashing in cardano-sl-crypto):
hash $ taTx txAux
  • Message content of the submitted transaction is computed (from Pos.Chain.Txp.TxMsg in cardano-sl-chain)
TxMsgContents txAux
  • The type of message is heeded (from Network.Broadcast.OutboundQueue.Types in cardano-sl-networking)
MsgTransaction OriginSender
  • Outbound enqueue has to be established and ready to participate
networkConfig :: NetworkConfig KademliaParams
mEkgNodeMetrics :: Maybe EkgNodeMetrics

oq :: OQ.OutboundQ EnqueuedConversation NodeId Bucket
oq <- initQueue networkConfig ("diffusion.outboundqueue") (enmStore <$> mEkgNodeMetrics)

enqueue :: EnqueueMsg
enqueue = makeEnqueueMsg logTrace ourVerInfo $ \msgType k -> do
    itList <- OQ.enqueue oq msgType (EnqueuedConversation (msgType, k))
    pure (M.fromList itList)
  • Having the above values setup one can disseminate using invReqDataFlowTK (from Pos.Infra.Communication.Relay.Logic in cardano-sl-infra) as is done in (Pos.Diffusion.Full.Txp):
sendTx :: Trace IO (Severity, Text) -> EnqueueMsg -> TxAux -> IO Bool
sendTx logTrace enqueue txAux = do
    anySucceeded <$> invReqDataFlowTK
        logTrace
        "tx"
        enqueue
        (MsgTransaction OriginSender)
        (hash $ taTx txAux)
        (TxMsgContents txAux)
  where
    anySucceeded outcome =
        not $ null
        [ ()
        | Right (Just peerResponse) <- toList outcome
        , resOk peerResponse
        ]

InvReqDataFlowTK calls InvReqDataFlow and uses primitives of (in Node.Conversation from cardano-sl-networking)

data ConversationActions body rcv = ConversationActions {
       -- | Send a message within the context of this conversation
       send    :: body -> IO ()

       -- | Receive a message within the context of this conversation.
       --   'Nothing' means end of input (peer ended conversation).
       --   The 'Word32' parameter is a limit on how many bytes will be read
       --   in by this use of 'recv'. If the limit is exceeded, the
       --   'LimitExceeded' exception is thrown.
     , recv    :: Word32 -> IO (Maybe rcv)

       -- | Send raw bytes.
     , sendRaw :: ByteString -> IO ()
     }