playweb-node 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- playweb/__init__.py +18 -0
- playweb/api/__init__.py +4 -0
- playweb/api/erc20.py +66 -0
- playweb/api/erc721.py +88 -0
- playweb/api/metadata.py +86 -0
- playweb/api/node_api.py +224 -0
- playweb/api/public_api.py +496 -0
- playweb/api/rpc.py +247 -0
- playweb/client.py +341 -0
- playweb/config.py +168 -0
- playweb/consensus/__init__.py +5 -0
- playweb/consensus/leader.py +50 -0
- playweb/consensus/nvf_bft.py +540 -0
- playweb/consensus/vote.py +108 -0
- playweb/core/__init__.py +6 -0
- playweb/core/block.py +156 -0
- playweb/core/blockchain.py +358 -0
- playweb/core/fee_engine.py +312 -0
- playweb/core/mempool.py +112 -0
- playweb/core/royalty_engine.py +207 -0
- playweb/core/transaction.py +270 -0
- playweb/network/__init__.py +6 -0
- playweb/network/bootstrap.py +217 -0
- playweb/network/gossip.py +222 -0
- playweb/network/peer_manager.py +164 -0
- playweb/network/sync.py +244 -0
- playweb/node.py +303 -0
- playweb/plugin/__init__.py +4 -0
- playweb/plugin/base_plugin.py +225 -0
- playweb/plugin/plugin_manager.py +131 -0
- playweb/registry/__init__.py +4 -0
- playweb/registry/content_registry.py +259 -0
- playweb/registry/edition_registry.py +220 -0
- playweb/storage/__init__.py +11 -0
- playweb/storage/base.py +154 -0
- playweb/storage/ram_storage.py +123 -0
- playweb/storage/sqlite_storage.py +369 -0
- playweb/storage/supabase_storage.py +389 -0
- playweb_node-1.0.0.dist-info/METADATA +416 -0
- playweb_node-1.0.0.dist-info/RECORD +43 -0
- playweb_node-1.0.0.dist-info/WHEEL +5 -0
- playweb_node-1.0.0.dist-info/licenses/LICENSE +201 -0
- playweb_node-1.0.0.dist-info/top_level.txt +1 -0
playweb/config.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlayWebit Network — L1 Configuration
|
|
3
|
+
All constants hardcoded here. Never in .env.
|
|
4
|
+
Node operators cannot override these — consensus enforces them.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
# ─────────────────────────────────────────────
|
|
10
|
+
# NETWORK IDENTITY
|
|
11
|
+
# ─────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
NETWORK_NAME = "PlayWebit Network"
|
|
14
|
+
CHAIN_ID = 4968
|
|
15
|
+
CURRENCY = "PLWB"
|
|
16
|
+
DECIMALS = 18
|
|
17
|
+
|
|
18
|
+
# ─────────────────────────────────────────────
|
|
19
|
+
# AUTHORITY WALLET
|
|
20
|
+
# Hardcoded — public and intentional.
|
|
21
|
+
# Every node enforces fees go here.
|
|
22
|
+
# Changing this = your blocks get rejected by network.
|
|
23
|
+
# ─────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
AUTHORITY_WALLET = "0x119411e1CB00FE009b907eEA54467bFa5C8604dd"
|
|
26
|
+
|
|
27
|
+
# ─────────────────────────────────────────────
|
|
28
|
+
# L1 FEES — enforced by consensus
|
|
29
|
+
# These are the ONLY fees L1 knows about.
|
|
30
|
+
# Platform fees (list, buy, delist etc) are L2 territory.
|
|
31
|
+
# ─────────────────────────────────────────────
|
|
32
|
+
|
|
33
|
+
TRANSACTION_FEE = 1 # PLWB — every tx on the network
|
|
34
|
+
CV_LINK_FEE = 5 # PLWB — linking a CID to the chain
|
|
35
|
+
PLWB_REDEMPTION_FEE = 0.05 # 5% — fee on PLWB redemption (100% authority)
|
|
36
|
+
PLWB_RATE_USD = 0.10 # $0.10 per 1 PLWB
|
|
37
|
+
PLWB_MIN_PURCHASE = 10 # minimum PLWB per purchase
|
|
38
|
+
PLWB_MIN_REDEMPTION = 50 # minimum PLWB to redeem
|
|
39
|
+
|
|
40
|
+
# ─────────────────────────────────────────────
|
|
41
|
+
# FEE SPLIT — enforced by consensus
|
|
42
|
+
# Every node validates this on every block.
|
|
43
|
+
# Wrong split = block rejected.
|
|
44
|
+
# ─────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
FEE_SPLIT_AUTHORITY = 0.50 # 50% → AUTHORITY_WALLET
|
|
47
|
+
FEE_SPLIT_NODE = 0.50 # 50% → node operator wallet
|
|
48
|
+
|
|
49
|
+
# Which fee tx_types get the 50/50 split
|
|
50
|
+
SPLITTABLE_FEE_TYPES = [
|
|
51
|
+
"fee", # base network tx fee (1 PLWB)
|
|
52
|
+
"cv_link", # CID link fee (5 PLWB)
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
# Which fee tx_types go 100% to authority
|
|
56
|
+
AUTHORITY_ONLY_FEE_TYPES = [
|
|
57
|
+
"plwb_redeem", # redemption fee
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
# ─────────────────────────────────────────────
|
|
61
|
+
# TRANSACTION TYPES — L1 knows only these
|
|
62
|
+
# Everything else is L2 / plugin territory.
|
|
63
|
+
# ─────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
L1_TX_TYPES = [
|
|
66
|
+
"transfer", # PLWB transfer between wallets
|
|
67
|
+
"fee", # network fee (auto-split 50/50)
|
|
68
|
+
"cv_link", # link CID to chain (content registration fee)
|
|
69
|
+
"content_register", # register CID ownership on chain
|
|
70
|
+
"ownership_transfer", # transfer CID ownership
|
|
71
|
+
"edition_transfer", # transfer specific edition
|
|
72
|
+
"spider_hash_anchor", # anchor a spider hash (L2 integrity proof)
|
|
73
|
+
"plwb_redeem", # user redeems PLWB for fiat
|
|
74
|
+
"plwb_purchase", # user purchases PLWB
|
|
75
|
+
"genesis", # chain genesis transaction
|
|
76
|
+
"reward", # block reward
|
|
77
|
+
"node_register", # node registers on chain
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
# These tx types skip signature verification (authority only)
|
|
81
|
+
AUTHORITY_TX_TYPES = [
|
|
82
|
+
"genesis",
|
|
83
|
+
"reward",
|
|
84
|
+
"plwb_purchase",
|
|
85
|
+
"plwb_redeem",
|
|
86
|
+
"spider_hash_anchor",
|
|
87
|
+
"node_register",
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
# ─────────────────────────────────────────────
|
|
91
|
+
# CONSENSUS — NVF-BFT
|
|
92
|
+
# ─────────────────────────────────────────────
|
|
93
|
+
|
|
94
|
+
CONSENSUS_QUORUM = 0.667 # 2/3 of active nodes must vote
|
|
95
|
+
BLOCK_TIME = 30 # seconds between blocks
|
|
96
|
+
CONSENSUS_TIMEOUT = 15 # seconds before round times out
|
|
97
|
+
CONSENSUS_ROUND_PHASES = [
|
|
98
|
+
"PROPOSE", # leader broadcasts block candidate
|
|
99
|
+
"PREPARE", # peers verify + sign
|
|
100
|
+
"VOTE", # peers broadcast votes
|
|
101
|
+
"COMMIT", # 2/3 quorum reached → finalise
|
|
102
|
+
"ANCHOR", # write to storage
|
|
103
|
+
"NOTIFY", # notify plugins
|
|
104
|
+
]
|
|
105
|
+
|
|
106
|
+
# ─────────────────────────────────────────────
|
|
107
|
+
# BOOTSTRAP — Cloudflare Worker
|
|
108
|
+
# ─────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
BOOTSTRAP_URL = os.getenv(
|
|
111
|
+
"PLAYWEBIT_BOOTSTRAP_URL",
|
|
112
|
+
"https://small-field-be1c.playwebit.workers.dev"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
BOOTSTRAP_ENDPOINTS = {
|
|
116
|
+
"nodes": "/nodes",
|
|
117
|
+
"register": "/nodes/register",
|
|
118
|
+
"heartbeat": "/nodes/heartbeat",
|
|
119
|
+
"deregister": "/nodes/deregister",
|
|
120
|
+
"health": "/nodes/health",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
# Fallback — your own permanent nodes
|
|
124
|
+
# Used if Cloudflare is unreachable
|
|
125
|
+
BOOTSTRAP_FALLBACK_NODES = [
|
|
126
|
+
"https://node1.playwebit.com",
|
|
127
|
+
"https://node2.playwebit.com",
|
|
128
|
+
]
|
|
129
|
+
|
|
130
|
+
BOOTSTRAP_HEARTBEAT_INTERVAL = 43200 # 12 hours in seconds
|
|
131
|
+
NODE_TTL = 86400 # 24 hours — auto-expire dead nodes
|
|
132
|
+
|
|
133
|
+
# ─────────────────────────────────────────────
|
|
134
|
+
# MINING
|
|
135
|
+
# ─────────────────────────────────────────────
|
|
136
|
+
|
|
137
|
+
MINING_MODE = "nvf_bft"
|
|
138
|
+
WRITE_DELAY = 5 # seconds between storage writes (rate limiting)
|
|
139
|
+
BATCH_TIMEOUT = 300 # 5 minutes — mine after this regardless
|
|
140
|
+
MAX_TX_PER_BLOCK = 10
|
|
141
|
+
|
|
142
|
+
# ─────────────────────────────────────────────
|
|
143
|
+
# NETWORK / P2P
|
|
144
|
+
# ─────────────────────────────────────────────
|
|
145
|
+
|
|
146
|
+
# Default port nodes listen on for peer-to-peer communication
|
|
147
|
+
# Node operators can override via NODE_PORT env var
|
|
148
|
+
NODE_PORT = int(os.getenv("NODE_PORT", 7860))
|
|
149
|
+
|
|
150
|
+
# How many peers to maintain connections with
|
|
151
|
+
MAX_PEERS = 20
|
|
152
|
+
MIN_PEERS = 3
|
|
153
|
+
|
|
154
|
+
# Gossip — how many peers to forward to
|
|
155
|
+
GOSSIP_FANOUT = 5
|
|
156
|
+
|
|
157
|
+
# Sync chunk size when downloading chain
|
|
158
|
+
SYNC_CHUNK_SIZE = 50 # blocks per request
|
|
159
|
+
|
|
160
|
+
# Peer timeout
|
|
161
|
+
PEER_TIMEOUT = 10 # seconds
|
|
162
|
+
|
|
163
|
+
# ─────────────────────────────────────────────
|
|
164
|
+
# LOGGING
|
|
165
|
+
# ─────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
|
168
|
+
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PlayWebit Network — Leader Election
|
|
3
|
+
Rotating leader by block index. Deterministic — all nodes agree.
|
|
4
|
+
No randomness, no voting on who leads. Just math.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import List, Optional
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LeaderElection:
|
|
14
|
+
|
|
15
|
+
def get_leader(
|
|
16
|
+
self,
|
|
17
|
+
block_index: int,
|
|
18
|
+
active_nodes: List[str], # list of wallet addresses
|
|
19
|
+
) -> Optional[str]:
|
|
20
|
+
"""
|
|
21
|
+
Get the leader wallet for a given block index.
|
|
22
|
+
Deterministic: block_index % len(active_nodes)
|
|
23
|
+
Every node on the network computes the same result.
|
|
24
|
+
"""
|
|
25
|
+
if not active_nodes:
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
sorted_nodes = sorted(active_nodes) # sort for determinism
|
|
29
|
+
leader_index = block_index % len(sorted_nodes)
|
|
30
|
+
return sorted_nodes[leader_index]
|
|
31
|
+
|
|
32
|
+
def is_leader(
|
|
33
|
+
self,
|
|
34
|
+
my_wallet: str,
|
|
35
|
+
block_index: int,
|
|
36
|
+
active_nodes: List[str],
|
|
37
|
+
) -> bool:
|
|
38
|
+
"""Check if this node is the leader for this block."""
|
|
39
|
+
leader = self.get_leader(block_index, active_nodes)
|
|
40
|
+
if not leader:
|
|
41
|
+
return False
|
|
42
|
+
return leader.lower() == my_wallet.lower()
|
|
43
|
+
|
|
44
|
+
def get_next_leader(
|
|
45
|
+
self,
|
|
46
|
+
block_index: int,
|
|
47
|
+
active_nodes: List[str],
|
|
48
|
+
) -> Optional[str]:
|
|
49
|
+
"""Get the leader for the NEXT block — for planning ahead."""
|
|
50
|
+
return self.get_leader(block_index + 1, active_nodes)
|