luke-jr / eloipool Goto Github PK
View Code? Open in Web Editor NEWFast Python3 Bitcoin pool server
License: Other
Fast Python3 Bitcoin pool server
License: Other
Eloipool - Python Bitcoin pool server Copyright (C) 2011-2013 Luke Dashjr <[email protected]> Copyright License ----------------- This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. Dependencies ------------ Python 3.2+ http://python.org python-bitcoinrpc https://github.com/jgarzik/python-bitcoinrpc python-base58 https://gitorious.org/bitcoin/python-base58 Optional Dependencies --------------------- midstate http://gitorious.org/midstate/midstate Psycopg2 http://initd.org/psycopg/ PyMySQL http://www.pymysql.org/ Merged Mining ------------- Eloipool supports merged mining using the setworkaux/gotwork RPC interface. A simple port of namecoin's merged-mine-proxy to this interface is available here: https://gitorious.org/~Luke-Jr/bitcoin/luke-jr-bitcoin/blobs/namecoin_mmm/contrib/merged-mine-proxy Please be advised that: - Eloipool does not provide any interface for longpolling aux chains. - Empty and longpoll merkle roots are generated only as-needed, and will NOT be regenerated when CoinbaseAux changes. This means shares found by getwork miners after a longpoll will almost always be stale on your aux chain(s). This problem does not affect GBT or stratum miners.
Hi,
I've documented what it took for me to get eloipool installed & running at boot- would like to share with the project should they approve of its content:
To Install:
Add your ssh key to github according to these instructions here:
https://help.github.com/articles/generating-ssh-keys/
Install dependencies:
2a) Use your OS' package manager, install python 3 & screen with this command:
sudo aptitude -y update && sudo aptitude install -y python3-all-dev screen
2b) Clone the 'jgarzik/pythonbitcoinrpc' git repository into a dir in your home dir, using this command:
git clone [email protected]:jgarzik/python-bitcoinrpc.git
2b1) change to the newly created directory with the following command:
cd python-bitcoinrpc
2b2) Install pythonbitcoinrpc using the following command:
python setup.py install
2c) Clone the 'python-base58' git repository into a dir in your home dir, using this command:
git clone https://gitlab.com/bitcoin/python-base58.git
2c1) Install python-base58 using the following command:
python base58.py install
Clone the eloipool git repository locally somewhere in your home directory with this command:
git clone [email protected]:luke-jr/eloipool.git
Install bitcoind with these commands:
sudo aptitude install -y python-software-properties
sudo add-apt-repository ppa:bitcoin/bitcoin
sudo aptitude update && sudo aptitude install -y bitcoind
Configure bitcoind:
5a) Open or create the file ~/.bitcoin/bitcoin.conf & put the following in it, substituting the xxxx's with your own values, which don't really matter & can be forgotten:
server=1
rpcuser=xxxx
rpcpassword=xxxx
rpcport=8332
rpcallowip=127.0.0.1
rpcthreads=100
maxconnections=1000
logtimestamps=1
ShareTarget = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff
to this:
ShareTarget = 0x000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffff
6c) Comment out CoinbaserCmd so it looks like this:
#CoinbaserCmd = 'echo -e "1\\n$((%d / 100))\\n1579aXhdwvKZEMrAKoCZhzGuqMa8EonuXU"'
6d) Change the TemplateSources section to look like the following, adjusting for your own directory paths & username (without the <>'s):
TemplateSources = (
{
'name': 'primary',
'uri': 'http://<rpcusername:rpcpassword@localhost:8332',
'priority': 0,
'weight': 1,
},
# {
# 'name': 'secondary',
# 'uri': 'http://user:pass@localhost:18332',
# 'priority': 1,
# 'weight': 1,
# },
)
6e) Empty TemplateChecks section so it looks like this:
TemplateChecks = (
)
6f) Delete the whole BlockSubmissions section
6g) Change the following line to look like the one below:
DelayLogForUpstream = False
DelayLogForUpstream = True
6h) Change the following line to look like the one below:
UpstreamBitcoindNode = ('127.0.0.1', 18333) # testnet
UpstreamBitcoindNode = ('127.0.0.1', 8333)
6i) Change the following line to look like the one below:
UpstreamNetworkId = b'\xFA\xBF\xB5\xDA' # testnet
UpstreamNetworkId = b'\xF9\xBE\xB4\xD9'
6j) Change the following line to look like the one below:
POT = 2
#POT = 2
6k) Change the following code to look like the one below:
ShareLogging = (
{
'type': 'logfile',
'filename': 'share-logfile',
'format': "{time} {Q(remoteHost)} {username} {YN(not(rejectReason))} {dash(YN(upstreamResult))} {dash(rejectReason)} {solution}\n",
},
{
'type': 'sql',
'engine': 'postgres',
'dbopts': {
'host': 'localhost',
'database': 'pooldb',
'user': 'eloipool',
'password': 'somethingsecret',
},
'statement': "insert into shares (rem_host, username, our_result, upstream_result, reason, solution) values ({Q(remoteHost)}, {username}, {YN(not(rejectReason))}, {YN(upstreamResult)}, {rejectReason}, decode({solution}, 'hex'))",
},
{
'type': 'sql',
'engine': 'mysql',
'dbopts': {
'host': 'localhost',
'db': 'pooldb',
'user': 'eloipool',
'password': 'somethingsecret',
},
'statement': "insert into shares (rem_host, username, our_result, upstream_result, reason, solution) values ({Q(remoteHost)}, {username}, {YN(not(rejectReason))}, {YN(upstreamResult)}, {rejectReason}, unhex({solution}))",
},
{
'type': 'sql',
'engine': 'sqlite',
'dbopts': {
'database': 'share.db',
},
'statement': "insert into shares (remoteHost, username, rejectReason, upstreamResult, solution) values ({remoteHost}, {username}, {rejectReason}, {upstreamResult}, {solution})",
},
)
ShareLogging = (
{
'type': 'logfile',
'filename': 'share-logfile',
'format': "{time} {Q(remoteHost)} {username} {YN(not(rejectReason))} {dash(YN(upstreamResult))} {dash(rejectReason)} {solution}\n",
},
)
6l) Change the following line to look like the one below:
LogFile = {
'filename': 'filename.log',
LogFile = {
'filename': 'eloipool.log',
7a) In your home directory, create a folder just for your scripts with this command:
mkdir scripts
7b) Create a script to start bitcoind
7b1) Create the file ~/scripts/bitcoindstart.sh, & put the following in it, adjusting for your own directory paths & username (without the <>'s):
#!/bin/sh
sleep 30
screen -dm su <username> -c PYTHONPATH=/home/<username>/<path to bitcoinrpc>:/home/<username>/<path to python-base58> \
nohup ./eloipool.py 2>&1 >/dev/null &
7b2) Make the script belong to the user, & be executable with the following command, adjusting for your own directory paths & username (without the <>'s):
sudo chown : ~/scripts/bitcoindstart.sh
sudo chmod a+x ~/scripts/bitcoindstart.sh
#!/bin/sh
sleep 30
screen -dm su <username> -c PYTHONPATH=/home/admin/apps/python-bitcoinrpc:/home/admin/apps/python-base58 \
nohup ./eloipool.py 2>&1 >/dev/null &
Make the script belong to the user, & be executable with the following command, adjusting for your own directory paths & username (without the <>'s):
sudo chown <username>:<usergroup> ~~/<eloipooldirectory>/poolstart.sh
sudo chmod a+x ~~/<eloipooldirectory>/poolstart.sh
9a) To make bitcoind start at boot, add the following to /etc/rc.local, adjusting for your own directory paths & username (without the <>'s):
su admin -c '/home/<username>/scripts/bitcoindstart.sh'
9b) To make eloipool start at boot, edit cron with the following command:
crontab -e
9b1) Enter the following to the bottom of the file, adjusting for your own directory paths & username (without the <>'s):
@reboot /home/<username>/<path to eloipool>/poolstart.sh
Dears,
Does Eloipool supports a Var Diff setting like following? if yes, where I can setup it?
Regards,
John Ahn
Dear Luke-jr
I've got following error on my Eloipool. but I don't know exactly what should I do.
Could you give me tip or guide for setting or check point for this error?
2017-03-20 21:37:17,375 merkleMaker ERROR Traceback (most recent call last):
File "/home/linuxadm/eloipool/merklemaker.py", line 606, in _updateMerkleTree_I
r = self._updateMerkleTree_fromTS(TS)
File "/home/linuxadm/eloipool/merklemaker.py", line 578, in _updateMerkleTree_fromTS
(AcceptedScore, TotalScore) = self._CheckTemplate(newMerkleTree, TS)
File "/home/linuxadm/eloipool/merklemaker.py", line 525, in _CheckTemplate
propose = caccess.getblocktemplate(ProposeReq)
File "/home/linuxadm/eloipool/bitcoinrpc/authproxy.py", line 136, in call
'Content-type': 'application/json'})
File "/usr/lib/python3.4/http/client.py", line 1125, in request
self._send_request(method, url, body, headers)
File "/usr/lib/python3.4/http/client.py", line 1153, in _send_request
self.putrequest(method, url, **skips)
File "/usr/lib/python3.4/http/client.py", line 993, in putrequest
raise CannotSendRequest(self.__state)
http.client.CannotSendRequest: Request-sent
2017-03-20 21:37:17,379 merkleMaker CRITICAL Traceback (most recent call last):
File "/home/linuxadm/eloipool/merklemaker.py", line 793, in run
self.merkleMaker_I()
File "/home/linuxadm/eloipool/merklemaker.py", line 783, in merkleMaker_I
self.merkleMaker_II()
File "/home/linuxadm/eloipool/merklemaker.py", line 761, in merkleMaker_II
return self._updateMerkleTree()
File "/home/linuxadm/eloipool/merklemaker.py", line 649, in _updateMerkleTree
self._updateMerkleTree_I()
File "/home/linuxadm/eloipool/merklemaker.py", line 606, in _updateMerkleTree_I
r = self._updateMerkleTree_fromTS(TS)
File "/home/linuxadm/eloipool/merklemaker.py", line 569, in _updateMerkleTree_fromTS
MP = self._CallGBT(TS)
File "/home/linuxadm/eloipool/merklemaker.py", line 381, in _CallGBT
MP = access.getblocktemplate(self.GBTReq)
File "/home/linuxadm/eloipool/bitcoinrpc/authproxy.py", line 136, in call
'Content-type': 'application/json'})
File "/usr/lib/python3.4/http/client.py", line 1125, in request
self._send_request(method, url, body, headers)
File "/usr/lib/python3.4/http/client.py", line 1153, in _send_request
self.putrequest(method, url, **skips)
Regards,
John Ahn
I've been using eloipool to mine segwit blocks on testnet for the past week now. When I first started using it I noticed was I was getting submitted invalid blocks. It looks like the coinbase didn't contain a witness nonce. I added a hacky workaround to my submitblock
call here.
I'm not sure what's causing this. I'd suspect the coinbase is being mistakenly serialized as non-witness somewhere, but I haven't dug into the eloipool code much yet.
I'm testing to increase the rate of mining. I run bitcoind in regtest mode (v0.12.0). And adjusted config.py to have a target for a difficult of 0.01, and modified checkShare in eloipool.py to acept all blocks sent by miners. Cpuminer (v2.4.3), with this configuration, mines a block in an average of 400 miliseconds.
The first block is accepted by bitcoind, the rest are being rejected with "bad-cb-amount" or "bad-prevblk". After running like this for 10 minutes, no other block was accepted. After restarting eloipool, the first block mined is accepted, and again the rest are being rejected.
I want to put the pool software in Docker but the interactivemode is causing the output to be unreadable, so it is hard for me to tell if it is working or not. Can the InteractiveConsole be turned off?
I wanted to create a pool so I could mine some testnet tBTC.
Here is the output I am getting from my Docker Compose configuration:
eloipool_1 | 2020-04-22 22:21:28,173 jsonrpc_getwork WARNING Error importing 'midstate' module; work will not provide midstates
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 |
eloipool_1 | now exiting InteractiveConsole...
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
eloipool_1 | >>> Not exiting implicitly. Use exit() if you really want to.
eloipool_1 | (2 threads: 2 primary, 0 daemon)
.
.
.
Dears,
I am connection on Eloipool using Bfgminer but there are a lot of accept made by my miners. so in the result of that eloipool has been not responding after some horus.
so I want to know how I setup a proper or suitable difficult for my miners. I have a 4 of Antminer S3+, which second hash rate is 456Ghash/s per miners.
Could you guide me that?
Regards,
John Ahn
Line 514 in d488480
from Block Proposal:
capabilities | Array of Strings | MAY contain "proposal" to indicate support for block proposal |
---|
Hello I installed and configured eloipool, but anyway if I try to mine, I receive this always:
reject reason: H-not-zero
I still work on regtest chain
Thank you
Dears,
Does eloipool supports a ExtranonceSubscribe option? because I am uses a stratum-proxy as eloipool monitoring purchase there is an option for enableExtranonceSubscribe.
However when I set it as true then I've get following error messages. so I wonder if there is any option for this function on eloipool.
2016-04-27 00:29:29,867 ERROR [Pool-Eloipoolsrv-Thread]: [PoolConnection] Pool-Eloipoolsrv. JSON-RPC parsing error with line: {"id": 4, "result": null, "error": [-3, "Method 'mining.extranonce.subscribe' not found", null]}. java.lang.NullPointerException
at strat.mining.stratum.proxy.pool.Pool.processSubscribeExtranonceResponse(Pool.java:492)
at strat.mining.stratum.proxy.pool.PoolConnection.onExtranonceSubscribeResponse(PoolConnection.java:142)
at strat.mining.stratum.proxy.network.StratumConnection.onResponseReceived(StratumConnection.java:322)
at strat.mining.stratum.proxy.network.StratumConnection.onLineRead(StratumConnection.java:240)
at strat.mining.stratum.proxy.network.StratumConnection.access$200(StratumConnection.java:59)
at strat.mining.stratum.proxy.network.StratumConnection$1.run(StratumConnection.java:163)
2016-04-27 00:30:07,239 INFO [StratumProxyManagerSeverSocketListener]: [ProxyManager] New connection on /192.168.1.2:3333 from /54.94.211.146:52723.
2016-04-27 00:30:07,239 ERROR [54.94.211.146-Thread]: [WorkerConnection] Parsing error on worker connection 54.94.211.146. Failed to parse line 夏다version j ?�퀈? v?�W � ??y?9?. com.fasterxml.jackson.core.JsonParseException: Unrecognized token '夏다version j ': was expecting ('true', 'false' or 'null') at [Source: 夏다version j ?�퀈
? v?�W � ??y?9?; line: 1, column: 19]
at com.fasterxml.jackson.core.JsonParser._constructError(JsonParser.java:1486)
at com.fasterxml.jackson.core.base.ParserMinimalBase._reportError(ParserMinimalBase.java:518)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._reportInvalidToken(ReaderBasedJsonParser.java:2300)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser._handleOddValue(ReaderBasedJsonParser.java:1459)
at com.fasterxml.jackson.core.json.ReaderBasedJsonParser.nextToken(ReaderBasedJsonParser.java:683)
at com.fasterxml.jackson.databind.ObjectMapper._initForReading(ObjectMapper.java:3600)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3545)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2576)
at strat.mining.stratum.proxy.network.StratumConnection.onLineRead(StratumConnection.java:225)
at strat.mining.stratum.proxy.network.StratumConnection.access$200(StratumConnection.java:59)
at strat.mining.stratum.proxy.network.StratumConnection$1.run(StratumConnection.java:163)
Regards,
John Ahn
Dears,
Finally I've sucessfully running a Eloipool as my bitcoin solo mining purpose.
Is there any option for setup a difficulty for miner or pool side configuration?
Regards,
John Ahn
Dears,
I saw following error on logfile of eloipool. so I was looking for method to fix following error but there is not clear solution for this. even I've read a post on bitcointalk.org.
So please let me know how I fixed this issue? because of error message logfiles are too big...it almost 40GB per day log.
2016-04-30 04:21:11,338 BitcoinRPC DEBUG -1-> getblocktemplate [{"capabilities": ["coinbasevalue", "coinbase/append$
2016-04-30 04:21:11,365 BitcoinRPC DEBUG <-1- {"coinbasevalue": 2527191892, "capabilities": ["proposal"], "sizelimi$
2016-04-30 04:21:11,371 merkleMaker INFO New block: 000000000000000001893e44352e7cd4c7e33d8249068bee3d9ab5f47ae375b$
2016-04-30 04:21:11,372 JSONRPCServer INFO Waiting 15 seconds to longpoll
2016-04-30 04:21:11,381 BitcoinRPC DEBUG -2-> getblocktemplate [{"mode": "proposal", "data": "04000000b575e37af4b59$
2016-04-30 04:21:11,394 BitcoinRPC DEBUG <-2- null
2016-04-30 04:21:11,395 BitcoinRPC DEBUG -3-> getblocktemplate [{"capabilities": ["coinbasevalue", "coinbase/append$
2016-04-30 04:21:11,447 BitcoinRPC DEBUG <-3- {"coinbasevalue": 2527191892, "capabilities": ["proposal"], "sizelimi$
2016-04-30 04:21:11,463 BitcoinRPC DEBUG -4-> getblocktemplate [{"mode": "proposal", "data": "04000000b575e37af4b59$
2016-04-30 04:21:11,476 BitcoinRPC DEBUG <-4- null
2016-04-30 04:21:11,476 merkleMaker CRITICAL Traceback (most recent call last): File "/eloipoolsrv/merklemaker.py", line 704, in run self.merkleMaker_I()
File "/eloipoolsrv/merklemaker.py", line 694, in merkleMaker_I self.merkleMaker_II()
File "/eloipoolsrv/merklemaker.py", line 660, in merkleMaker_II return self._updateMerkleTree()
File "/eloipoolsrv/merklemaker.py", line 560, in _updateMerkleTree self._updateMerkleTree_I()
File "/eloipoolsrv/merklemaker.py", line 546, in _updateMerkleTree_I raise RuntimeError('Failed to create usable template') RuntimeError: Failed to create usable template
Regards,
John Ahn
Dears,
Eloipool has an issue with set Dynamic target difficult on the pool because it always set diff as 1. so there are many accpeted and then finally pool goes to no response.
So is there any method set difficult as current one using Dynamic target option?
My config file setting is
# Name of the server
ServerName = 'Private Eloipool'
### Settings relating to server scaling/load
# Share hashes must be below this to be valid shares
# If dynamic targetting is enabled, this is a minimum
ShareTarget = 0x000000000fffffffffffffffffffffffffffffffffffffffffffffffffffffff
# Automatically adjust targets per username
#0 = disabled
#1 = arbitrary targets
#2 = power of two difficulties (zero bit counts)
DynamicTargetting = 2
# How many shares per minute to try to achieve on average
DynamicTargetGoal = 8
# DynamicTargetServerAddresses
DynamicTargetServerAddresses = (::ffff:192.168.10, 3333),
# Number of seconds hashrate is measured over
DynamicTargetWindow = 120
# Should we try to adjust the target quickly when there are a lot of shares?
DynamicTargetQuick = True
Regards,
John Ahn
I get this error when I try to install it, I really don't know what I'm doing & could use some instructions on how to get started. Thanks.
$ python eloipool.py install
usage: eloipool.py [-h] [-c CONFIG]
eloipool.py: error: unrecognized arguments: install
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.