# PocketChain Ledger
> Defines the data structure of the *PocketChain* ledger.
https://excalidraw.com/#room=e1bed1f4516380fd122e,QoGoTRwTdhZzyrSIPa2pGA
# Tips mechanism
==How announcers get payed after clients spend tips ??==
Each client $i$ is subscribed to $q$ announcers. To submit a transaction $tx$ for a tip $t_i$, the clients performs the following:
- Generate $k$ proportions:
$$
p_i = \{ \alpha^k: 0 \leq k \leq q, \alpha \in [0,1]\}
$$
- Normalize $p_i$.
$$
p_i = \{ \frac{p^k_i}{\sum_{j} p^j_i} : 0 \leq k \leq q\}
$$
Calculate tips:
$$
t^k_i = \begin{cases}
T \cdot p^k_i & \text{if } t^k_i \geq \text{min_tip} \\
0 & \text{otherwise}
\end{cases}: \{0 \leq k \leq q \}
$$
----
When submitting a transaction $tx$ for a total tip $\mathcal{T}$, the client $n_c$ follows these steps:
- Generate Proportions.
The client generates $k$ proportions according to an exponential decay factor $\alpha \in [0, 1]$:
$$
p_{n_c} = \{ \alpha^k : 0 \leq k \leq q, \alpha \in [0,1] \}
$$
These proportions determine how the total tip is distributed, with earlier announcers receiving larger shares.
- Normalize Proportions:
The proportions are normalized to ensure the sum of the proportions is 1:
$$
p_{n_c} = \left\{ \frac{p_{n_c}^{(k)}}{\sum_{i \in |p_{n_c}|} p^{(i)}_{n_c}} : 0 \leq k \leq q \right\}
$$
- Calculate Tips:
The total tip $\mathcal{T}$ is distributed across $q$ announcers, with the tip for each announcer $k$ calculated as:
$$
\mathcal{T}^k = \begin{cases}
\mathcal{T} \cdot p_{n_c}^k & \text{if } \mathcal{T}^k \geq \text{ $\mathcal{T}_{base}$} \\
0 & \text{otherwise}
\end{cases}, \quad 0 \leq k \leq q
$$
This rule ensures that each announcer receives at least the minimum tip threshold $\mathcal{T}_{base}$. If an announcer’s calculated share of the tip is less than $\mathcal{T}_{base}$, they do not receive any portion of the fee. This prevents the network from being overwhelmed by low-fee transactions that offer insufficient compensation for announcers.
# To improve
- [ ] We need to mention that at least the energy consumption is retrieved from running PC on mobile devices for mobile nodes.
- [ ] Motivate the use of BRB, what's the issue that it solves and why we chose it. (intro + properties in sys model)
- [ ] Try to avoid general/fuzzy words.
- [x] Possibility to generalize the communicatiom medium use (The system assumes a very narrow transmission model (OFDMA))
- [x] Improve motivation for PC, and clarify that it doesn't exclusively uses mobile/resource-constrained devices.
- [ ] What's the motivation for the endorsment process?
- [ ] Emphasize on the complexity of the consensus algorithm (log) to discuss the network cost of PC.
- [x] "Pconflict(q) \ "(V BChs),""---Incorrect LaTeX maths mode use.
- [ ] Improve the conversion function (Tips to PC)
- [ ] Define why and how PC implements fairness.
# To ADD
- [ ] Discuss forks
- [ ] Add a section on the compression mechanisms to prevent the ledger from growing linearly.
- [ ] New devices must not download the entire chain to be able to perform the processor (or announcer) task.
- [ ] Compare PC to some other solutions that can run on mobile.
- [ ] Clearly mention the limitations of PC.
- [ ] Add security guarantess of PC.
- [ ] Additional experiments (to specify)
- [ ] Can conflicting transactions be used as a DoS attack? --> Security analysis
- [ ] Add a discussion section : Limitations, Potential attacks, Parameters tuning ...
### Questions and Considerations
> - [ ] Should staking Txs be validated without being in a block.
> - [ ] Proof of History
> - [x] How to consider that a tx is confirmed?
*In Bitcoin, the sender's balance is technically updated as soon as the transaction is included in a validated block that is added to the blockchain. *
> - [x] How are rewards distributed among the nodes (Fixed Rewards/ Tx fee sharing)
*==**Bitcoin**== has a fixed block reward. Currently, a miner who successfully creates a new block receives 6.25 BTC.
All transaction fees from a block go to the miner who successfully mines it.
==**Ethereum**==
**Block Reward**: fixed block reward that gets paid out to the validator who successfully creates a new block. This reward is currently 2 ETH.
**Transaction Fees**:
Base Fee: a portion of the transaction fee that is "burned" or permanently removed from circulation. The base fee adjusts dynamically based on network congestion.
Priority Fee : an optional additional fee that users can include to incentivize validators to include their transactions more quickly. Priority fees go directly to the validating node.*
> **Conditions to endorse:**
> - [ ] Checks if the transactions within the block are valid (e.g., correct format, no double-spending attempts).
> - [x] Checks if the transactions in the block haven't already been confirmed in the ledger. ==OnTheChain()==
> - [x] Checks if the block's Announcer is a valid announcer and doesn't have another block currently under consensus. ==HasBlockUnderConsensus()==
> - [x] Check Ongoing validations ==HasConflictingTx()==
>
> > TO ADD
>
> - For Announcers to endorse a block, the announcer must have `staking_block_expiry > 0`.
### PocketChain Tx
```python=
Class Transaction:
id: int
sender: PublicKey
recipient: PublicKey # recipient's PubKey
value: int
timestamp: GetCurrrentTime ()
signature: Str
tip: int
```
### PocketChain Block
```python=
class PCBlock:
# Header fields
index: int # depth | vector clock index
timestamp: int
hash: str # self called function
previous_hash: str
merkle_root: str
announcer: str
# Body fields
transactions: list
# Block attachments
endorsements: list
```
### PocketChain Genesis Block
```python=
genesis_block = PCBlock(
index=0
hash="000" # SHA256
previous_block_hash="000"
merkle_root="000"
timestamp=1720656000
transactions=[coinbase_transaction] initial deposit
announcer="pk"
endorsements=[pk]
)
```
# Network Initialization
**Network Configuration:**
```python=
self.config = {
'q': Number of announcers to subscribe to
'BRB_SAMPLES': {Gossip_sample, Echo_sample, Ready_sample, Delivery_sample}
'BRB_THRESHOLDS':{G,E,R,D}
'MINIMUM_FEE': 1 tip
'STAKE_AMOUNT': 1 PC coin # function of the PocketChain
'MIN_STORAGE':
'MAX_BLOCK_SIZE': 1 Mb
'STAKE_EXPIRY': 5
}
```
# ==*Generic Node Class*==
```python=
Class Node:
key_pair: str # Public and Private keys
config: list # Load network configuration
balance: float # coins exchanged in Pocketchain
tips: int
announcer_registry: list
block_store: list
```
## ==*Client Node*==
```python=
Class Client extends Node:
super(Node)
```
### Initialize client
```python=
Procedure initialize():
key_pair = KeyGen() || key_pair
config = LoadConfiguration()
balance = InitializeBalance(PubKey)
tips = InitializeTips(PubKey)
pendingTransactions = []
announcer_registry = SubscribeToAnnouncers()
End Procedure
```
### Connect to Announcers
```python=
pubSubSystem = InitializePubSubSystem()
Procedure SubscribeToAnnouncers()
pubSubSystem.subscribe("ANNOUNCER_AVAILABILITY")
while length(announcer_registry) < config.q:
message = pubSubSystem.receive()
if message.type == "announcer_advertisement":
if message.announcer not in announcer_registry:
if CheckEligibility(message.announcer):
announcer_registry.append(message.announcer)
end if
end if
end if
End Procedure
```
### Create transaction
```python=
Function CreateTransaction(receiverAddress, transferAmount)
if balance < transferAmount
return "Insufficient balance"
end if
if tip < minimumFee
return "Insufficient tip"
end if
transaction = new Transaction()
transaction.id = id
transaction.sender = address
transaction.receiver = receiverAddress
transaction.value = transferAmount
transaction.tip = CalculateTransactionFee(transaction.id, Tips) #MINIMUM_FEE + extra Tips
transaction.signature = Sign(transaction, senderPrivateKey)
transaction.timestamp = GetCurrentTimestamp()
tips = tips - transaction.tip
pendingTransactions.append(transaction)
return transaction
```
### Broadcast TX
```python=
Procedure BroadcastTransaction(transaction)
for each announcer in announcers_registry do:
announcer.send('TX_SUBMIT',transaction)
end for
End Procedure
```
### TX Validation Notification
The client listens to $p<q$ confirmation from the network, that his tx is confirmed.
```python=
# Also update the receiver's balance
Upon event <ReceiveTransactionConfirmation(transaction)|announcer>
if pendingTransactions.contains(transaction) then
pendingTransactions.remove(transaction)
# The actual balance is only updated upon confirmation
# executed by sender
balance -= transaction.amount
end if
End Procedure
```
### Update Announcers Registry
Make sure that the $q$ announcers are active at the moment of sending a Tx.
---
---
## ==*Processor Node*==
```
Class Processor extends Node:
super(Node)
local_blockchain=[]
```
### Initialize Processor
```python=
Procedure initialize():
key_pair = KeyGen() || key_pair
config = LoadConfiguration()
balance = InitializeBalance(PubKey)
tips = InitializeTips(PubKey)
#announcer_registry = DiscoverAnnouncers()
availableStorage= allocateStorage()
End Procedure
```
### Proof-of-Space
### Process blocks (Consensus)
```python=
Upon Event <ReceiveBRBMessage|block|Announcer\Processor>:
if CheckEligibility(Announcer)
and HasEnoughEndorsements(block)
initiateBRB(Config.BRB_SAMPLES,Config.BRB_THRESHOLDS)
RelayBRBmessages(block)
```
```python=
Upon event <Deliver|block>:
local_blockchain.append(block)
```
---
---
## ==*Announcer Node*==
```python=
Class Announcer extends Node:
super(Node)
blockchain : list
mempool : list
isEligible: bool #initially false
staking_block_expiry : int
```
### Initialize Announcer
```python=
Procedure initialize():
key_pair = KeyGen() || key_pair
config = LoadConfiguration()
balance = InitializeBalance(PubKey)
tips = InitializeTips(PubKey)
mempool = InitializeMempool()
announcer_registry = DiscoverAnnouncers()
isEligible= false
staking_block_expiry = 0
availableStorage= allocateStorage()
End Procedure
```
### PoS
```python=
Function CheckEligibility(Announcer):
if Announcer.availableStorage >= config.MIN_STORAGE
and Announcer.balance >= Config.STAKE_AMOUNT
and staking_block_expiry >0:
return True
else:
return False
End Function
```
```python=
Procedure Stake()
if CheckEligibility():
receipt= InitiateStakingTransaction()
ProcessStakingTransaction(receipt)
AnnounceAvailability(receipt) #Stake_receipt= stake_tx's id or hash
End Procedure
```
```python=
Function InitiateStakingTransaction(amount)
transaction = Transaction(
sender = sender.public_key,
recipient = blockchain.staking_address
value = sender.balance
timestamp = GetCurrrentTime ()
tip = 0
type = "staking"
)
sendTransaction(transaction) #consensus?
return transaction.id
End Function
```
```python=
Procedure processStakingTransaction(transaction)
if validateTransaction(transaction)
sender.balance -= transaction.value
sender.staking_block_expiry = Config.STAKE_EXPIRY
End Procedure
```
### Advertise Availability
```python=
Procedure AnnounceAvailability(announcerAddress, Stake_receipt)
while true do
message = new Advertisement(announcerAddress, Stake_receipt)
pubSubSystem.publish("ANNOUNCER_AVAILABILITY", message)
Sleep(AdvertisementInterval)
end while
End Procedure
```
### Transaction Verification
```python=
Upon Event <ReceiveTransaction(tx)|client node>
if isValidTx(tx):
mempool.addTransaction(tx) #existing tx in mempool are verified
```
```python=
Function isValidTx(tx):
if hasEnoughTips() and isValidSignature(tx) and isValidBalance(tx) #Verify sender signature, tx fees, enough balance
return True
esle:
return False
End Function
```
### Candidate Block Formation
```python=
Procedure CreateCandidateBlock()
block = new Block(
timestamp = GetCurrentTimestamp()
hash = GetHashOfBlock()
announcer = PubKey
transactions = selectTransactions(mempool)
previous_hash = GetHashOfLastBlock(Blockchain)
signature = Sign(block, announcer_private_key)
)
BroadcastCandidateBlockToAnnouncers(block)
End Procedure
```
```python=
Function selectTransactions()
transactions = []
block_size = 0
while mempool is not empty and block_size <= config.MAX_BLOCK_SIZE do
transactions.add(transaction)
block_size += size(transaction)
end while
retrun transactions
End Function
```
```python=
Upon Event <ReceiveEndorsement(block)|announcers>
if length(block.endorsements) >= EndorseThreshold():
intiateBRB(block)
staking_block_expiry -=1
```
```python=
Procedure intiateBRB()
CreateBroadcastChannel(block)
InitBroadcastSamples()
Broadcast(Block)
End Procedure
```
### Endorsements
```python=
Function EndorseThreshold()
activeAnnouncer = getActiveAnnouncer(announcer_registry)
# Simple Majority (50% + 1)
threshold = (activeAnnouncerCount // 2) + 1
# or (2/3 + 1)
# threshold = (2 * activeAnnouncerCount // 3) + 1
return theshold
End Function
Function ResolveConflict()
```
```python=
Procedure AdvertiseCandidateBlock
PubSubSystem.publish("ENDORSEMENT", candidateBlock)
End Procedure
```
```python=
Function EndorseBlock(candidateBlock)
endorsement = SignBlock(Hash(candidateBlock), GetPrivateKey())
endorsementChannel.publish(endorsement)
endorsedBlocks.append(candidateBlock)
return endorsement
End Function
```
```python=
Upon Event <ReceiveCandidateBlock(candidateBlock)|announcer>
if isValidBlock(candidateBlock)
not OnTheChain(candidateBlock) and
not HasConflictingTransactions(candidateBlock) and
not HasBlockUnderConsensus(announcer):
EndorseBlock(candidateBlock)
else
SendRejection(candidateBlock)
end if
```
```python=
Function OnTheChain(candidateBlock):
for tx in candidateBlock.transactions:
if blockchain.contains(tx.id):
return True
end if
end for
return False
```
```python=
Function HasConflictingTransactions(candidateBlock)
for block in endorsedBlocks do #endorsedBlocks[]
for tx in block.transactions do
if candidateBlock.transactions.contains(tx):
return True
end if
end for
end for
End Function
```
```python=
Function HasBlockUnderConsensus(announcer)
if routingTable.containsChannel(announcer) then
return true
end if
for each block in endorsedBlocks do
if block.announcer == announcer then
return true
end if
end for
return false
End Function
```
### After validation, update mempool
```python=
Procedure clearMempool(mempool, validatedBlock)
for each transaction in validatedBlock.transactions do
if mempool.contains(transaction) then
mempool.remove(transaction)
end if
end for
End Procedure
```
# Example
```python=
class BeaconNode:
def __init__(self, config):
self.config = config # Store configuration options
self.validator_registry = {} # Key-value store for validator data
self.block_store = [] # History of recent blocks
self.peer_manager = PeerManager(self.config) # Handles network connections
def start_syncing(self):
# Logic to connect to other nodes and download the blockchain
def process_block(self, block):
# Logic to validate and store a received block
def propose_block(self):
# Logic to create a new block if this node is selected as a proposer
def create_attestation(self, block):
# Logic to create an attestation supporting a given block
```
## PUB/SUB test code
```python=
import threading
import time
class Publisher:
def __init__(self):
self.subscribers = {} # Keep track of subscriber node IDs and their message queues
self.lock = threading.Lock()
self.advertising_event = threading.Event()
def advertise(self):
# Simulate advertising the publisher's address
print("Publisher advertising...")
self.advertising_event.set() # Set the event to notify nodes to subscribe
def register_subscriber(self, node_id, message_queue):
with self.lock:
print(f"Publisher: Node {node_id} subscribed.")
self.subscribers[node_id] = message_queue
# Acknowledge the subscription by sending a confirmation message
message_queue.put(f"Welcome, Node {node_id}!")
def run(self):
self.advertise()
def stop(self):
self.advertising_event.clear()
class Node(threading.Thread):
def __init__(self, node_id, publisher):
super().__init__()
self.id = node_id
self.publisher = publisher
self.message_queue = queue.Queue()
def subscribe(self):
# Simulate subscribing to the publisher
self.publisher.register_subscriber(self.id, self.message_queue)
def run(self):
# Wait until the publisher starts advertising
self.publisher.advertising_event.wait()
self.subscribe() # Subscribe to the publisher
# Listen for a confirmation message from the publisher
while True:
message = self.message_queue.get()
if message == 'END':
break
print(f"Node {self.id} received message: {message}")
# Set up the publisher
publisher = Publisher()
# Create and start nodes
nodes = [Node(i, publisher) for i in range(3)]
# Start the publisher
publisher_thread = threading.Thread(target=publisher.run)
publisher_thread.start()
# Start the nodes
for node in nodes:
node.start()
# Give time for nodes to subscribe and interact with the publisher
time.sleep(5)
# Stop the publisher and nodes
publisher.stop()
for node in nodes:
node.message_queue.put('END')
node.join()
publisher_thread.join()
print("Simulation ended.")
```
```python=
def initiateStakingTransaction(sender, amount):
if amount <= sender.available_balance:
transaction = Transaction(
sender=sender.public_key,
recipient=blockchain.staking_address, # A special address for staking
value=amount,
type="staking"
)
transaction.sign(sender.private_key)
sendTransaction(transaction)
else:
print("Error: Insufficient balance to stake.")
def processStakingTransaction(transaction):
if validateTransaction(transaction):
sender = getAccount(transaction.sender)
sender.available_balance -= transaction.value
sender.staked_balance += transaction.value
sender.staking_lock_expiry = getTimestamp() + config.staking_lock_period
updateAccountState(sender)
```
```python=
def processStakingTransaction(transaction):
if validateTransaction(transaction):
sender = getAccount(transaction.sender)
sender.available_balance -= transaction.value
sender.staked_balance += transaction.value
sender.staking_lock_expiry = getTimestamp() + config.staking_lock_period
updateAccountState(sender)
```
__________
```python=
// Initialization
For each endorser:
S = {} // Set of endorsed blocks
T = {} // Set of transactions in endorsed blocks
// Block Processing
On receiving block B with endorsement e(B):
if no_conflicts(B, S) and ledger_consistent(extract_transactions(B)):
if B not in S:
e(B) += 1 // Initial endorsement for new blocks
S.add(B)
T.add(extract_transactions(B))
else:
// Handle higher endorsement updates
if received e(B) > local e(B):
update local e(B)
broadcast(B, endorsement_proof(B))
else:
// Conflicts
ConflictingSet = find_all_conflicting_blocks(B, S)
C_max = find_strongest_block(ConflictingSet)
if e(B) > e(C_max) or (e(B) == e(C_max) and hash(B) < hash(C_max)):
for C in ConflictingSet:
S.remove(C)
T.remove(extract_transactions(C))
S.add(B)
T.add(extract_transactions(B))
e(B) += 1
broadcast(B, endorsement_proof(B))
// else discard B
// Finalization
if e(B) >= threshold:
B is ready for consensus
S.remove(B)
T.remove(transactions in B)
```