main.go. It's better to start tracing from the backend repo's main rather than the core repo's main, since the core repo is mainly intended to be used as a library. Moreover, since backend uses the core repo as a library, we will hit all of the core functionality by starting here anyway.
Run()function defined in the cmd package.
--connect-ipsmeans a peer will connect to the specified peer and nothing else.
--add-ipsmeans these peers will be added to the list of things that the peer is going to try and connect to. When we spin up new nodes, we often use
--connect-ipswith a trusted node because it's easier than bootstrapping from the sea of nodes that are running in the wider internet.
Start()function that is kicked off in main.go. Tracing the code starting from this function is a great way to understand how connections with peers are established and maintained.
ConnectPeer(). If the peer passes this version negotiation, then the peer is passed off to
server.govia a "newPeerChan." server.go is then responsible for doing higher-level interactions with the peer.
_handleHeaderBundle()and responds with different messages depending on how synced the peer is.
MsgBitCloutGetBlocksmessage is sent in
MsgBitCloutInvare processed via
messageHandler()just like everything else.
INVmessages can be for a block, as mentioned previously OR for a transaction. Below is the case for a transaction
_handlePeerMessages()function will just enqueue the message for the peer's thread to process it. This is done in order to move processing into another thread for efficiency reasons (not doing this would cause server.go to be *too* single-threaded). Here you can see the
server.godelegate the call to peer.go, and here you see peer.go dequeuing it to process it. Note that there are several messages that are delegated in this way, all defined in the
server.go, it is basically just calling
mempool.go. If the transaction is valid then it will be added to the mempool, and if not then it will be rejected. In order to validate a transaction, mempool uses the previously mentioned
ConnectTransaction()function defined in
_getBlockTemplate()contains the logic for constructing a new block. It basically does the following:
block_producer.gojust produces block templates, but it's up to miners to compute winning hashes. That happens via a remote process as follows:
remote_miner_main.goand connect to any node they want via a flag. This can be their own local node or a remote node like api.bitclout.com.
remote_miner_main.gowill continuously call
GetBlockTemplate()on the chosen node and hash it until it's found a block. Once it has found a winning hash, it calls
SubmitBlock(), which then causes the node to process it and broadcast it to the rest of the network.
get-block-template, all nodes can be used to mine blocks in this way. Miners generally don't need to do anything other than point to a valid BitClout node somewhere on the network.
GetBlockTemplate()much faster because it won't have to copy a block.
localStorageare encrypted using a call to
encryptedSeedHex, and then sends it back to bitclout.com. This same process works if you replace bitclout.com with the host of your own third-party node. The difference is that your third-party node will need to ask the user for permission in order to get encryptedSeedHex sent back to it.
encryptedSeedHex, it uses it to sign things. It does this by calling various operations on an iframe of identity.bitclout.com embedded within it.
encryptedSeedHexback to the host? Wouldn’t it be better to just keep everything in identity.bitclout.com?
localStoragewhen it’s embedded as an
iframein bitclout.com. This is due to Apple’s crusade against third-party cookies. However, Apple does allow identity.bitclout.com to access its cookies when its embedded as an iframe on bitclout.com if those cookies are set as first-party cookies.
encryptedSeedHex. When signing is needed, the
encryptedSeedHexis passed to the identity.bitclout.com iframe, which has access to the encryption key in the cookie, which it then uses to decrypt the
encryptedSeedHexand sign the transaction.
localStorage. This means that only iOS devices are subject to this drawback.
encryptedSeedHex. However, this information is useless without the encryption key stored exclusively in identity.bitclout.com.
frontend_server.gois called to construct a transaction. That transaction is then returned unsigned, signed by the identity iframe, and then submitted back to core via
/send-bitclout, which is relatively straightforward:
AddInputsAndChangeToTransaction(), which is a good function to trace through. I'm not aware of any transaction assembly that does not utilize this function for UTXO fetching.
GetSpendableUtxosForPublicKey(), which generates a universal view that includes txns from the mempool and then returns all UTXO's that are associated with the particular public key. These UTXO's can then be assembled into a transaction.
MinerBitcoinMinBurnWorkBlocksthat is utilized by the block producer.
TxnMeta. More on this later
PublicKey. In BitClout transactions are very simple and only have one public key that can be deemed to be the "executor" of the transaction. The transaction is generally always signed by this public key.
ExtraData. This is a flexible map that arbitrary data can be added to. It is currently used to support Reclouts via
IsQuoteRecloutedparams. It can be used to augment a transaction without causing a hard fork, which significantly increases the extensibility of BitClout by the community. For example, one can trivially add a "pinned posts" feature using
ExtraDatawithout consulting the core BitClout devs about it.
BitCloutTxnMetadatainterface. The full list of transaction types can be viewed here. To see descriptions of each one, simply find where that transaction type implements the interface.
BitcoinExchangeMetadata. You can see it contains a full Bitcoin transaction plus a merkle proof into the Bitcoin blockchain. This is how a node verifies that a particular Bitcoin transaction has a sufficient amount of work on it.
isDeleted=true. This is because the flush needs to propagate this change to the db, and it can only do that if it knows the entry is scheduled to be deleted by leaving it in the view.
_disconnectrestores the view to the state it was in before the transaction was connected.
_disconnectcode is rarely used, but it supports reorgs of blocks, which happen from time to time.