-
Notifications
You must be signed in to change notification settings - Fork 12
Submitting Transaction Byron
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 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
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
How diffusion works when sendTx
is prodded can be followed in communication diagram in Fig. 4.
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.
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
incardano-sl-crypto
):
hash $ taTx txAux
- Message content of the submitted transaction is computed (from
Pos.Chain.Txp.TxMsg
incardano-sl-chain
)
TxMsgContents txAux
- The type of message is heeded (from
Network.Broadcast.OutboundQueue.Types
incardano-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
(fromPos.Infra.Communication.Relay.Logic
incardano-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 ()
}