├── .gitignore ├── LICENSE ├── README.md ├── config └── constants.go ├── core ├── block.go ├── blockchain.go ├── blockchain_booster.go ├── data_master.go ├── difficulty.go └── transaction.go ├── main.go ├── role ├── miner.go └── user.go ├── test ├── block_test.go ├── blockchain_test.go ├── crypto_test.go ├── test_util.go └── transaction_test.go └── util ├── crypto.go ├── hash.go └── logger.go /.gitignore: -------------------------------------------------------------------------------- 1 | mini-blockchain 2 | mini-blockchain.exe -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 codingtmd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Mini-Blockchain 4 | 5 | Mini-Blockchain is a reference design for a blockchain system to demostate a full end2end flow in current blockchain technology. 6 | 7 | There are so many open source projects of blockchain implementation, like ethereum, bitcoin, eos etc.. But even running their code will take u couple of days to setup. 8 | 9 | The goal of the project is to build a "Hello-world" version of blockchain to help people understand how the foundamental technology without wasting time reading in a huge code base, to simulate the different roles, like user, miner, booster, and demostrate how they participate the blockchain network. And it also simplify the deployment, instead of configuring tons of library and debugging a version in a real distributed network, it is single machine version to easy the debug, test, and iterate. 10 | 11 | Since it is primarily designed as a learning tool for people to learn the blockchain end to end. It is meant to be as simple to understand as possible while still providing clear structure and workflow. 12 | 13 | ## What is this not? 14 | 15 | Mini-Blockchain is **not** meant for real usage as it is a simplified version and has many hard coded parameters. It's just meant for learning. 16 | 17 | It also may just simply stop working at any time since there are some depandences. 18 | 19 | ## What is included? 20 | 21 | This reference system is written in Go.(Don't ask me why use Go. I just fucking lost my mind.) 22 | - a main.go to boost the blockchian and simnulate the workflow 23 | - ./core to implement the blockchain 24 | - ./role to implement different actors in the blockchain ecosystem. 25 | - ./test to implement some unit tests to keep the code in some quality(even very little) 26 | 27 | 28 | ## Parts of the system 29 | 30 | This is how bitcoin mining works. I follow the same flow except checking the 2016th block. 31 | 32 | 33 | SOME DEFINITIONS: 34 | 35 | **User -** anyone who as an account(hash address) is a user. User holds the coins in their account and is able to send/receive coins to/from other users. 36 | 37 | **Transaction -** any coin transfer between two users is a transaction. The activities of users will generate transaction, which is the source of transaction pool. 38 | 39 | **Miner -** a special role, who doesn't generate transaction, but collect/validate transaction from the pool. 40 | 41 | **Block -** a collection of validated transactions. Every miner can propose a block, but only the one acknowledged by most of the miners will be the official block in the chian. 42 | 43 | **Difficulty -** a measure of how difficult it is to find a hash below a given target. Valid blocks must have a hash below this target. Mining pools also have a pool-specific share difficulty setting a lower limit for shares. In bitcoin, the network difficulty changes every 2016 blocks. For my implementation, the difficulty changes every block. 44 | 45 | **Nonce -** a 32-bit (4-byte) field to random the hash generation. Any change to the block data (such as the nonce) will make the block hash completely different. The resulting hash has to be a value less than the current difficulty and so will have to have a certain number of leading zero bits to be less than that. As this iterative calculation requires time and resources, the presentation of the block with the correct nonce value constitutes proof of work. 46 | 47 | **Reward -** when a block is discovered, the miner may award themselves a certain number of bitcoins, which is agreed-upon by everyone in the network. Normally the rewarding transaction is the first transaction in a block proposed by the miner. 48 | 49 | **Fee -** The miner is also awarded the fees paid by users sending transactions. The fee is an incentive for the miner to include the transaction in their block. In the future, as the number of new bitcoins miners are allowed to create in each block dwindles, the fees will make up a much more important percentage of mining income. Ethereum is a good example of fee usage. 50 | 51 | ## How does the simulated workflow work? 52 | 53 | First, it will start to initialize an empty blockchain. Note, for every blockchain project, boosting it from beginning is the tricky part. In this implementation, I created a empty chian and then add a empty block as the head. 54 | 55 | Second, it creates 1 miner to represent Consortium Blockchain(if you don't know what is consortium blockchain, read [this](https://www.blockchaindailynews.com/The-difference-between-a-Private-Public-Consortium-Blockchain_a24681.html "this")). The miner will submit several empty blocks and earn rewards for each block. 56 | 57 | Third, it creates 10 users and vest some coins for users to trade with each other. The vesting is implemented by transferring the coins from miner to each user. 58 | 59 | Forth, each user will start to randomly trade with each other and submit his transaction to the trasnactuon pool for mining. 60 | 61 | Fifty, miner keeps mining, validate transaction, confirm block. 62 | 63 | Cool, we got that? Great. Let's actually build this thing now. 64 | 65 | 66 | ## Building the code 67 | 68 | First, you need to install Golang 69 | if Mac 70 | 71 | brew install go 72 | 73 | if Ubuntu 74 | 75 | sudo apt-get update && sudo apt-get -y upgrade && sudo apt-get install -y golang-go 76 | 77 | Once you have installed and rebooted, log in, then open up the program “terminal.” Now run the command… 78 | 79 | sudo apt-get update && sudo apt-get -y upgrade && sudo apt-get -y install git 80 | 81 | Now you can clone the repository to get the full code. 82 | 83 | cd ~/ && git clone https://github.com/codingtmd/mini-blockchain.git 84 | 85 | Do not forget to download the missing library: 86 | 87 | go get -d -v . 88 | 89 | 90 | Go to the directory and build the code 91 | 92 | go build 93 | 94 | Then run it with fun 95 | 96 | go run main.go 97 | 98 | And you will see the console output as below 99 | ![workflow](https://drive.google.com/uc?export=view&id=1SDnBbREANWRk2DnqipcTDwInm-EeI1Vk) 100 | 101 | If you need an IDE, normally I use [microsoft visual studio code](https://code.visualstudio.com/download "microsoft visual studio code") and [Go Plugin](https://code.visualstudio.com/docs/languages/go "Go Plugin") 102 | 103 | 104 | ## Things to know 105 | 106 | The logging uses loggo. Plz check the configuration and usage here: https://github.com/juju/loggo 107 | 108 | ## Cool future work / Areas you can contribute / TODOs 109 | 110 | - Use msg to communicate infro between miners, users. (Currently just function call) 111 | - Same-input tramnsaction merging 112 | - Add multi-miner support 113 | - A simple script to initialize a blockchain. 114 | - Create a config for all hard code parameters. 115 | - Better logging. 116 | - Make a web UI to look into operation details, like etherscan.io 117 | - Create PoS support 118 | - Create a wallet implementation and web UI 119 | - Add some animations that make it easier to understand. 120 | - Build a dApp 121 | - Add ICO simulation like how to vest coins to user 122 | 123 | ## Reading List 124 | If you are not familiar with blockchain and its technology, below info will help u to ramp up the knowledge. 125 | ### Articles: 126 | + Vision 127 | * [https://avc.com/2018/05/is-buying-crypto-assets-investing/](https://avc.com/2018/05/is-buying-crypto-assets-investing/) 128 | + [https://news.earn.com/thoughts-on-tokens-436109aabcbe](https://news.earn.com/thoughts-on-tokens-436109aabcbe) 129 | + [https://thecontrol.co/cryptoeconomics-101-e5c883e9a8ff](https://thecontrol.co/cryptoeconomics-101-e5c883e9a8ff) 130 | + [https://medium.com/@cdixon/why-decentralization-matters-5e3f79f7638e](https://medium.com/@cdixon/why-decentralization-matters-5e3f79f7638e) 131 | + [https://continuations.com/post/148098927445/crypto-tokens-and-the-coming-age-of-protocol](https://continuations.com/post/148098927445/crypto-tokens-and-the-coming-age-of-protocol) 132 | + [https://medium.com/@cdixon/crypto-tokens-a-breakthrough-in-open-network-design-e600975be2ef](https://medium.com/@cdixon/crypto-tokens-a-breakthrough-in-open-network-design-e600975be2ef) 133 | + [https://www.theinformation.com/articles/14-ways-the-cryptocurrency-market-will-change-in-2018](https://www.theinformation.com/articles/14-ways-the-cryptocurrency-market-will-change-in-2018) 134 | + [https://medium.com/@FEhrsam/why-decentralized-exchange-protocols-matter-58fb5e08b320](https://medium.com/@FEhrsam/why-decentralized-exchange-protocols-matter-58fb5e08b320) 135 | + [https://thecontrol.co/some-blockchain-reading-1d98ec6b2f39](https://thecontrol.co/some-blockchain-reading-1d98ec6b2f39) 136 | + App Token v.s. Protocol Token 137 | + [https://blog.0xproject.com/the-difference-between-app-coins-and-protocol-tokens-7281a428348c](https://blog.0xproject.com/the-difference-between-app-coins-and-protocol-tokens-7281a428348c) 138 | + [https://medium.com/blockchannel/protocol-tokens-good-for-greedy-investors-bad-for-business-9002b40cf4cc](https://medium.com/blockchannel/protocol-tokens-good-for-greedy-investors-bad-for-business-9002b40cf4cc) 139 | + [https://blog.citowise.com/the-basics-coin-vs-token-what-is-the-difference-5cd270591538](https://blog.citowise.com/the-basics-coin-vs-token-what-is-the-difference-5cd270591538) 140 | + BaaS/Platform 141 | + [https://medium.com/@ACINQ/strike-our-stripe-like-api-for-lightning-is-live-cd1dce76ce2e](https://medium.com/@ACINQ/strike-our-stripe-like-api-for-lightning-is-live-cd1dce76ce2e) 142 | + [https://medium.com/@jbackus/blockchain-platform-plays-2827247a9014](https://medium.com/@jbackus/blockchain-platform-plays-2827247a9014) 143 | + [https://techcrunch.com/2018/05/22/po-et-launches-lab-for-developers-to-build-apps-on-publishing-blockchain/](https://techcrunch.com/2018/05/22/po-et-launches-lab-for-developers-to-build-apps-on-publishing-blockchain/) 144 | + Business Model 145 | + [https://blog.coinbase.com/app-coins-and-the-dawn-of-the-decentralized-business-model-8b8c951e734](https://blog.coinbase.com/app-coins-and-the-dawn-of-the-decentralized-business-model-8b8c951e734) 146 | + Consensus mechanism 147 | + [https://www.coindesk.com/blockchains-feared-51-attack-now-becoming-regular/](https://www.coindesk.com/blockchains-feared-51-attack-now-becoming-regular/) 148 | + dApp 149 | + [https://medium.com/@FEhrsam/the-dapp-developer-stack-the-blockchain-industry-barometer-8d55ec1c7d4](https://medium.com/@FEhrsam/the-dapp-developer-stack-the-blockchain-industry-barometer-8d55ec1c7d4) 150 | + Decentralized Exchange 151 | + [https://medium.com/@FEhrsam/why-decentralized-exchange-protocols-matter-58fb5e08b320](https://medium.com/@FEhrsam/why-decentralized-exchange-protocols-matter-58fb5e08b320) 152 | + [https://www.reuters.com/article/crypto-currencies-coinbase/coinbase-acquires-cryptocurrency-trading-platform-paradex-idUSL2N1SU1KK](https://www.reuters.com/article/crypto-currencies-coinbase/coinbase-acquires-cryptocurrency-trading-platform-paradex-idUSL2N1SU1KK) 153 | + Digital 154 | + [https://medium.com/kinfoundation/kin-blockchain-taking-fate-into-our-own-hands-f5bdfa759502](https://medium.com/kinfoundation/kin-blockchain-taking-fate-into-our-own-hands-f5bdfa759502) 155 | + ENS 156 | + [https://medium.com/the-ethereum-name-service/a-beginners-guide-to-buying-an-ens-domain-3ccac2bdc770](https://medium.com/the-ethereum-name-service/a-beginners-guide-to-buying-an-ens-domain-3ccac2bdc770) 157 | + ERC-20: 158 | + [https://medium.com/@james_3093/ethereum-erc20-tokens-explained-9f7f304055df](https://medium.com/@james_3093/ethereum-erc20-tokens-explained-9f7f304055df) 159 | + [https://medium.com/0xcert/fungible-vs-non-fungible-tokens-on-the-blockchain-ab4b12e0181a](https://medium.com/0xcert/fungible-vs-non-fungible-tokens-on-the-blockchain-ab4b12e0181a) 160 | + [https://hackernoon.com/an-overview-of-non-fungible-tokens-5f140c32a70a](https://hackernoon.com/an-overview-of-non-fungible-tokens-5f140c32a70a) 161 | + Fat Protocol 162 | + [https://www.usv.com/blog/fat-protocols](https://www.usv.com/blog/fat-protocols) 163 | + Game 164 | + [https://www.usv.com/blog/cryptokitties-1](https://www.usv.com/blog/cryptokitties-1) 165 | + [https://techcrunch.com/2018/05/25/gravys-new-mobile-game-show-is-price-is-right-mixed-with-qvc/](https://techcrunch.com/2018/05/25/gravys-new-mobile-game-show-is-price-is-right-mixed-with-qvc/) 166 | + [http://www.businessinsider.com/fortnite-esports-prize-pool-money-epic-games-2018-5](http://www.businessinsider.com/fortnite-esports-prize-pool-money-epic-games-2018-5) 167 | + Login kit 168 | + [https://medium.com/cleargraphinc/introducing-cleargraph-4713bc215a77](https://medium.com/cleargraphinc/introducing-cleargraph-4713bc215a77) 169 | + [https://tokensale.civic.com/CivicTokenSaleWhitePaper.pdf](https://tokensale.civic.com/CivicTokenSaleWhitePaper.pdf) 170 | + Loyalty 171 | + [https://medium.com/@bitrewards/why-blockchain-is-a-smart-solution-for-loyalty-programs-9443af408f71](https://medium.com/@bitrewards/why-blockchain-is-a-smart-solution-for-loyalty-programs-9443af408f71) 172 | + [http://www.oliverwyman.com/our-expertise/insights/2017/mar/Blockchain-Will-Transform-Customer-Loyalty-Programs.html](http://www.oliverwyman.com/our-expertise/insights/2017/mar/Blockchain-Will-Transform-Customer-Loyalty-Programs.html) 173 | + [http://www.kaleidoinsights.com/analysis-should-blockchain-power-your-customer-loyalty-program/](http://www.kaleidoinsights.com/analysis-should-blockchain-power-your-customer-loyalty-program/) 174 | + Proof of work v.s. Proof of stake 175 | + [https://medium.com/@robertgreenfieldiv/explaining-proof-of-stake-f1eae6feb26f](https://medium.com/@robertgreenfieldiv/explaining-proof-of-stake-f1eae6feb26f) 176 | + Recorded video 177 | + [https://avc.com/2017/12/video-of-the-week-token-1-0-vs-token-2-0/](https://avc.com/2017/12/video-of-the-week-token-1-0-vs-token-2-0/) 178 | + [https://avc.com/2017/12/video-of-the-week-the-token-summit-conversation/](https://avc.com/2017/12/video-of-the-week-the-token-summit-conversation/) 179 | + Research report: 180 | + [https://coincenter.org/report](https://coincenter.org/report) 181 | + Rewarding system 182 | + [https://techcrunch.com/2018/05/22/tango-card-raises-35m-for-its-rewards-as-a-service-gift-card-aggregation-platform/](https://techcrunch.com/2018/05/22/tango-card-raises-35m-for-its-rewards-as-a-service-gift-card-aggregation-platform/) 183 | + [https://medium.com/@bitrewards/why-blockchain-is-a-smart-solution-for-loyalty-programs-9443af408f71](https://medium.com/@bitrewards/why-blockchain-is-a-smart-solution-for-loyalty-programs-9443af408f71) 184 | + [https://techcrunch.com/2018/05/11/hollywood-producer-plans-to-incentivise-content-viewers-with-tokens/](https://techcrunch.com/2018/05/11/hollywood-producer-plans-to-incentivise-content-viewers-with-tokens/) 185 | + Security Token 186 | + [https://medium.com/@mkogan4/what-the-heck-are-tokenised-securities-7cd1123cbdad](https://medium.com/@mkogan4/what-the-heck-are-tokenised-securities-7cd1123cbdad) 187 | + Smart Contract 188 | + [https://medium.com/@ninosm/squashing-bugs-and-stopping-heists-the-coming-arms-race-in-smart-contract-infrastructure-9666fb830f65](https://medium.com/@ninosm/squashing-bugs-and-stopping-heists-the-coming-arms-race-in-smart-contract-infrastructure-9666fb830f65) 189 | + Token 190 | + [https://thecontrol.co/tokens-tokens-and-more-tokens-d4b177fbb443](https://thecontrol.co/tokens-tokens-and-more-tokens-d4b177fbb443) 191 | 192 | 193 | ### Some white papers 194 | + [Bitcoin: A Peer-to-Peer Electronic Cash System](https://bitcoin.org/bitcoin.pdf) 195 | + [Ethereum: A Next Generation Smart Contract and Decentralized Application Platform](https://github.com/ethereum/wiki/wiki/White-Paper) 196 | + [ETHEREUM: A SECURE DECENTRALISED GENERALISED TRANSACTION LEDGER](https://ethereum.github.io/yellowpaper/paper.pdf) 197 | + [BeigePaper: A Ether Tech Spec](https://github.com/chronaeon/beigepaper/blob/master/beigepaper.pdf) 198 | + [Enabling Blockchain Innovations with Pegged Sidechains](https://blockstream.com/sidechains.pdf) 199 | + [Augur: A Decentralized, Open-source Platform for Prediction Markets](https://bravenewcoin.com/assets/Whitepapers/Augur-A-Decentralized-Open-Source-Platform-for-Prediction-Markets.pdf) 200 | + [The Dai Stablecoin System](https://github.com/makerdao/docs/blob/master/Dai.md) 201 | + [Sia: Simple Decentralized Storage](http://www.sia.tech/sia.pdf) 202 | + [OmniLedger: A Secure, Scale-Out, Decentralized Ledger via Sharding](https://eprint.iacr.org/2017/406.pdf) 203 | + [Bitcoin UTXO Lifespan Prediction](http://cs229.stanford.edu/proj2015/225_report.pdf) 204 | 205 | ## Authors 206 | Lei Zhang, https://www.linkedin.com/in/codingtmd 207 | 208 | ## Contact me 209 | 210 | Leave me a message if you want to participate for fun. -------------------------------------------------------------------------------- /config/constants.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import "crypto/sha256" 4 | 5 | const HashSize = sha256.Size 6 | const MinerRewardBase = 100000000000 7 | -------------------------------------------------------------------------------- /core/block.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rsa" 6 | "crypto/sha256" 7 | "encoding/binary" 8 | "fmt" 9 | 10 | "../config" 11 | "../util" 12 | ) 13 | 14 | type uint256 struct { 15 | data [4]uint64 16 | } 17 | 18 | //Block conains a group of valid transactions and the cryptographic hash of the prior block in the blockchain 19 | type Block struct { 20 | hash [config.HashSize]byte 21 | prevBlockHash [config.HashSize]byte 22 | blockIdx uint64 23 | 24 | blockValue uint64 /* Mining Value of the block */ 25 | timeStampMs uint64 /* Epoch when mined in ms */ 26 | minerAddress rsa.PublicKey 27 | nuance uint256 /* Use to mine so that hash Value must reach a specifc difficulty */ 28 | 29 | Transactions []Transaction 30 | } 31 | 32 | func createBlock(prevBlockHash [config.HashSize]byte, blockIdx uint64, timeStampMs uint64, minerAddress *rsa.PublicKey, transactions []Transaction) *Block { 33 | var block Block 34 | block.prevBlockHash = prevBlockHash 35 | block.blockIdx = blockIdx 36 | block.timeStampMs = timeStampMs 37 | block.minerAddress = *minerAddress 38 | 39 | /* Create a special transaction to reward miner (always as transaction 0) */ 40 | block.Transactions = append(block.Transactions, CreateTransaction(1, 1)) 41 | b := make([]byte, 8) 42 | binary.BigEndian.PutUint64(b, blockIdx) 43 | copy(block.Transactions[0].Inputs[0].PrevtxMap[:], b) 44 | 45 | /* TODO: 100 coins, should be adjusted based on timeStamp */ 46 | block.Transactions[0].Outputs[0].Value = config.MinerRewardBase 47 | block.Transactions[0].Outputs[0].Address = *minerAddress 48 | 49 | /* Add real transactions */ 50 | block.AddTransactions(transactions) 51 | 52 | util.GetBlockLogger().Infof("Added reward transaction: %s\n", block.Transactions[0].Print()) 53 | return &block 54 | } 55 | 56 | //CreateFirstBlock create first block of a chain. 57 | func CreateFirstBlock(timeStampMs uint64, minerAddress *rsa.PublicKey) *Block { 58 | var prevBlockHash [config.HashSize]byte /* doesn't matter for the first block*/ 59 | var trans []Transaction 60 | return createBlock(prevBlockHash, 0, timeStampMs, minerAddress, trans) 61 | } 62 | 63 | //CreateNextEmptyBlock create next empty block of a chain. 64 | func CreateNextEmptyBlock(prevBlock *Block, timeStamp uint64, minerAddress *rsa.PublicKey) *Block { 65 | var trans []Transaction 66 | return createBlock(prevBlock.hash, prevBlock.blockIdx+1, timeStamp, minerAddress, trans) 67 | } 68 | 69 | //CreateNextBlock create next block of a chain. 70 | func CreateNextBlock(prevBlock *Block, timeStamp uint64, minerAddress *rsa.PublicKey, naunce uint64, transactions []Transaction) *Block { 71 | block := createBlock(prevBlock.hash, prevBlock.blockIdx+1, timeStamp, minerAddress, transactions) 72 | 73 | /* Finalize block */ 74 | block.nuance.data[0] = naunce 75 | block.hash = sha256.Sum256(block.getRawDataToHash()) 76 | return block 77 | } 78 | 79 | //AddTransaction add a transaction to current block. 80 | func (block *Block) AddTransaction(tran *Transaction) { 81 | block.Transactions = append(block.Transactions, *tran) 82 | } 83 | 84 | //AddTransactions add a series transactions to current block. 85 | func (block *Block) AddTransactions(trans []Transaction) { 86 | block.Transactions = append(block.Transactions, trans...) 87 | } 88 | 89 | func (block *Block) getRawDataToHash() []byte { 90 | data := block.prevBlockHash[:] 91 | data = appendUint64(data, block.timeStampMs) 92 | /* 93 | * Don't need to hash blockIdx, blockValue since they 94 | * can be derived from prevBlockHash and timeStamp 95 | */ 96 | data = appendAddress(data, &block.minerAddress) 97 | data = appendUint256(data, block.nuance) 98 | 99 | for i := 0; i < len(block.Transactions); i++ { 100 | data = append(data, block.Transactions[i].GetRawDataToHash()...) 101 | } 102 | return data 103 | } 104 | 105 | //FinalizeBlockAt Finalize a block with specified timestamp 106 | func (block *Block) FinalizeBlockAt(naunce uint64, timeStampMs uint64) { 107 | block.nuance.data[0] = naunce 108 | block.timeStampMs = timeStampMs 109 | block.hash = sha256.Sum256(block.getRawDataToHash()) 110 | } 111 | 112 | //VerifyBlockHash Verify block hash 113 | func (block *Block) VerifyBlockHash() bool { 114 | hash := sha256.Sum256(block.getRawDataToHash()) 115 | return block.hash == hash 116 | } 117 | 118 | //GetBlockHash Get hash value of block 119 | func (block *Block) GetBlockHash() [config.HashSize]byte { 120 | return block.hash 121 | } 122 | 123 | //Print details of block 124 | func (block *Block) Print() string { 125 | var buffer bytes.Buffer 126 | 127 | for _, tran := range block.Transactions { 128 | buffer.WriteString(fmt.Sprintf("%s,", util.Hash(tran))) 129 | } 130 | 131 | return fmt.Sprintf("Block:%s[hash:%s,prevBlockHash:%s,blockIdx:%v,blockValue:%v,timeStampMs:%v,minerAddress:%v,nuance:%v,Transactions:[%s],", 132 | util.Hash(block), 133 | util.HashBytes(block.hash), 134 | util.HashBytes(block.prevBlockHash), 135 | block.blockIdx, 136 | block.blockValue, 137 | block.timeStampMs, 138 | util.GetShortIdentity(block.minerAddress), 139 | block.nuance, 140 | buffer.String(), 141 | ) 142 | } 143 | -------------------------------------------------------------------------------- /core/blockchain.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rsa" 6 | "crypto/sha256" 7 | "errors" 8 | "fmt" 9 | 10 | "../config" 11 | "../util" 12 | ) 13 | 14 | //UTXO = Unspent Transaction Output 15 | type UTXO struct { 16 | txMap [config.HashSize]byte 17 | outputIndex uint32 18 | } 19 | 20 | //A Blockchain contains 21 | // - a chain of blocks indexed by hash and block index 22 | // - a set of unspent transaction output 23 | // - a set of Transactions indexed by tx hash 24 | type Blockchain struct { 25 | txMap map[[config.HashSize]byte]*Transaction /* map of all Transactions in the chain */ 26 | utxoMap map[UTXO]bool /* map of all unspent transaction output (key is not used) */ 27 | blockMap map[[config.HashSize]byte]*Block /* map of all blocks */ 28 | blockList []*Block /* list of all blocks */ 29 | 30 | difficulty Difficulty 31 | 32 | /* fields to support wallet */ 33 | AddressMap map[rsa.PublicKey]map[UTXO]bool /* map of all Addresses to their utxo list */ 34 | TransactionPool map[string]*Transaction /* all transaction broadcastd by user */ 35 | } 36 | 37 | //GetDifficulty Get difficulty 38 | func (chain *Blockchain) GetDifficulty() Difficulty { 39 | return chain.difficulty 40 | } 41 | 42 | func (chain *Blockchain) verifyTransaction(tran *Transaction, inputMap map[UTXO]bool) (uint64, error) { 43 | var totalInput uint64 44 | var fromAddresses []*rsa.PublicKey 45 | 46 | for _, input := range tran.Inputs { 47 | var utxo UTXO 48 | utxo.outputIndex = input.OutputIndex 49 | utxo.txMap = input.PrevtxMap 50 | 51 | /* 52 | * Step 1: Verify if the transaction Inputs are unique 53 | */ 54 | _, in := inputMap[utxo] 55 | if in { 56 | return 0, errors.New("All Inputs must be unique") 57 | } 58 | inputMap[utxo] = false 59 | 60 | /* 61 | * Step 2: Verify if the UTXO exists in the chain 62 | */ 63 | _, inUtxoMap := chain.utxoMap[utxo] 64 | if !inUtxoMap { 65 | return 0, fmt.Errorf("Cannot find UTXO %s corresponding to an input in the chain %s, %s", util.Hash(utxo), chain.PrintUTXOMap(), tran.Print()) 66 | } 67 | 68 | /* 69 | * Step 3: Sanity check if the UTXO has a valid transaction 70 | */ 71 | tx, intxMap := chain.txMap[utxo.txMap] 72 | if !intxMap { 73 | return 0, fmt.Errorf("Blockchain is corrupted: cannot find tx %s", tx.Print()) 74 | } 75 | if utxo.outputIndex >= uint32(len(tx.Outputs)) { 76 | return 0, errors.New("Blockchain is corrupted: cannot find utxo") 77 | } 78 | 79 | totalInput += tx.Outputs[utxo.outputIndex].Value 80 | fromAddresses = append(fromAddresses, &tx.Outputs[utxo.outputIndex].Address) 81 | } 82 | 83 | /* 84 | * Step 4: Verify signatures 85 | */ 86 | err := tran.VerifyTransaction(fromAddresses) 87 | if err != nil { 88 | return 0, err 89 | } 90 | 91 | /* 92 | * Step 5: Make sure total input <= total output (the gap is the transaction fee) 93 | */ 94 | var totalOutput uint64 95 | for _, output := range tran.Outputs { 96 | totalOutput += output.Value 97 | } 98 | if totalOutput > totalInput { 99 | return 0, errors.New("The Value of total Outputs exceed that of Inputs") 100 | } 101 | 102 | return totalInput - totalOutput, nil 103 | 104 | } 105 | 106 | func (chain *Blockchain) addUTXOToAddress(utxo *UTXO, Address *rsa.PublicKey) { 107 | m, exist := chain.AddressMap[*Address] 108 | if !exist { 109 | m = make(map[UTXO]bool) 110 | chain.AddressMap[*Address] = m 111 | } 112 | 113 | m[*utxo] = false 114 | } 115 | 116 | func (chain *Blockchain) removeUTXOFromAddress(utxo *UTXO, Address *rsa.PublicKey) { 117 | m, exist := chain.AddressMap[*Address] 118 | if !exist { 119 | return 120 | } 121 | 122 | delete(m, *utxo) 123 | } 124 | 125 | /* 126 | * Perform the transaction atomically assuming the transaction is valid. 127 | */ 128 | func (chain *Blockchain) performTransaction(tran *Transaction) { 129 | txMap := sha256.Sum256(tran.GetRawDataToHash()) 130 | chain.txMap[txMap] = tran 131 | for _, input := range tran.Inputs { 132 | var utxo UTXO 133 | utxo.outputIndex = input.OutputIndex 134 | utxo.txMap = input.PrevtxMap 135 | 136 | delete(chain.utxoMap, utxo) 137 | tx := chain.txMap[input.PrevtxMap] 138 | chain.removeUTXOFromAddress(&utxo, &tx.Outputs[utxo.outputIndex].Address) 139 | } 140 | for i, output := range tran.Outputs { 141 | var utxo UTXO 142 | utxo.outputIndex = uint32(i) 143 | utxo.txMap = txMap 144 | chain.utxoMap[utxo] = false 145 | chain.addUTXOToAddress(&utxo, &output.Address) 146 | } 147 | 148 | // remove the transaction from the poposal pool 149 | util.GetBlockchainLogger().Debugf("delete %s from transaction pool\n", util.Hash(tran)) 150 | delete(chain.TransactionPool, util.Hash(tran)) 151 | } 152 | 153 | func (chain *Blockchain) performMinerTransactionAndAddBlock(block *Block) { 154 | var utxo UTXO 155 | utxo.outputIndex = 0 156 | utxo.txMap = sha256.Sum256(block.Transactions[0].GetRawDataToHash()) 157 | chain.utxoMap[utxo] = false 158 | chain.txMap[utxo.txMap] = &block.Transactions[0] 159 | chain.addUTXOToAddress(&utxo, &block.minerAddress) 160 | 161 | chain.blockList = append(chain.blockList, block) 162 | blockHash := sha256.Sum256(block.getRawDataToHash()) 163 | chain.blockMap[blockHash] = block 164 | } 165 | 166 | //AddBlock Append the block to the end of the chain. 167 | //TODO: Support appending the block to a block that is a few blocks ahead of the end 168 | func (chain *Blockchain) AddBlock(block *Block) error { 169 | if block.prevBlockHash != chain.blockList[len(chain.blockList)-1].hash { 170 | return errors.New("The prevous block must be the last block in the chain") 171 | } 172 | 173 | if block.blockIdx != uint64(len(chain.blockList)) { 174 | return errors.New("Invalid block index") 175 | } 176 | 177 | if len(block.Transactions) == 0 { 178 | return errors.New("The Transactions must contain miner's reward as the first transaction") 179 | } 180 | 181 | if len(block.Transactions[0].Outputs) != 1 { 182 | return errors.New("Only one miner is allowed in each block") 183 | } 184 | 185 | var inputMap map[UTXO]bool 186 | inputMap = make(map[UTXO]bool) 187 | var totalFee uint64 188 | for i, tx := range block.Transactions { 189 | /* Ignore the first transaction which contains miner's reward */ 190 | if i == 0 { 191 | continue 192 | } 193 | 194 | util.GetBlockchainLogger().Debugf("Start to confirm transaction: %s\n", tx.Print()) 195 | fee, error := chain.verifyTransaction(&tx, inputMap) 196 | if error != nil { 197 | return error 198 | } 199 | totalFee += fee 200 | } 201 | 202 | /* 100 coins as base award, should be adjusted based on time */ 203 | var minerReward uint64 204 | minerReward = config.MinerRewardBase + totalFee 205 | if block.Transactions[0].Outputs[0].Value > minerReward { 206 | return errors.New("Miner's reward exceeds base + fee") 207 | } 208 | 209 | if block.timeStampMs < chain.GetLatestBlock().timeStampMs { 210 | return errors.New("Timestamp must be monotonic increasing") 211 | } 212 | 213 | if !chain.ReachDifficulty(block) { 214 | return errors.New("The block doesn't meet difficulty") 215 | } 216 | 217 | /* 218 | * Perform all Transactions 219 | */ 220 | for i := range block.Transactions { 221 | if i == 0 { 222 | continue 223 | } 224 | chain.performTransaction(&block.Transactions[i]) 225 | } 226 | 227 | chain.difficulty.UpdateDifficulty(block.timeStampMs - chain.GetLatestBlock().timeStampMs) 228 | chain.performMinerTransactionAndAddBlock(block) 229 | return nil 230 | } 231 | 232 | //GetNLatestBlock Get specified amount of latest blocks. 233 | func (chain *Blockchain) GetNLatestBlock(n int) *Block { 234 | if n > len(chain.blockList) { 235 | return nil 236 | } 237 | return chain.blockList[len(chain.blockList)-n] 238 | } 239 | 240 | //GetLatestBlock Get the latest block 241 | func (chain *Blockchain) GetLatestBlock() *Block { 242 | return chain.GetNLatestBlock(1) 243 | } 244 | 245 | //ReachDifficulty Check whether the chain has reach difficulty 246 | func (chain *Blockchain) ReachDifficulty(block *Block) bool { 247 | return chain.difficulty.ReachDifficulty(block.hash) 248 | } 249 | 250 | //RegisterUser Register user 251 | func (chain *Blockchain) RegisterUser(user rsa.PublicKey, utxoMap map[UTXO]bool) { 252 | chain.AddressMap[user] = utxoMap 253 | } 254 | 255 | //AcceptBroadcastedTransaction Accept transaction which broadchated by others. 256 | func (chain *Blockchain) AcceptBroadcastedTransaction(tran *Transaction) { 257 | chain.TransactionPool[util.Hash(tran)] = tran 258 | } 259 | 260 | /*********************************** 261 | * Wallet related methods 262 | **********************************/ 263 | 264 | // BalanceOf Check the balance of an Address 265 | func (chain *Blockchain) BalanceOf(Address *rsa.PublicKey) uint64 { 266 | m, exist := chain.AddressMap[*Address] 267 | if !exist { 268 | util.GetBlockchainLogger().Errorf("Address %x disappear from chain\n", *Address) 269 | return 0 270 | } 271 | 272 | var balance uint64 273 | for utxo := range m { 274 | tx := chain.txMap[utxo.txMap] 275 | balance += tx.Outputs[utxo.outputIndex].Value 276 | } 277 | return balance 278 | } 279 | 280 | // TransferCoin Make a transaction to transfer coins from one account to target Address. 281 | // Return nil if there is insufficient fund or amount is zero 282 | // Note that the transaction is unsigned 283 | func (chain *Blockchain) TransferCoin(from *rsa.PublicKey, to *rsa.PublicKey, amount uint64, fee uint64) (*Transaction, error) { 284 | if amount == 0 { 285 | return nil, fmt.Errorf("amount needs > 0") 286 | } 287 | 288 | if chain.BalanceOf(from) < amount { 289 | return nil, fmt.Errorf("user %s has no enough balance", util.GetShortIdentity(*from)) 290 | } 291 | 292 | fromMap := chain.AddressMap[*from] 293 | var utxoList []UTXO 294 | var fromAmount uint64 295 | for fromUTXO := range fromMap { 296 | utxoList = append(utxoList, fromUTXO) 297 | fromTx := chain.txMap[fromUTXO.txMap] 298 | fromAmount += fromTx.Outputs[fromUTXO.outputIndex].Value 299 | 300 | if fromAmount >= amount+fee { 301 | break 302 | } 303 | } 304 | 305 | var outputLen int 306 | if amount+fee == fromAmount { 307 | outputLen = 1 308 | } else { 309 | outputLen = 2 310 | } 311 | 312 | tx := CreateTransaction(len(utxoList), outputLen) 313 | for i, utxo := range utxoList { 314 | tx.Inputs[i].OutputIndex = utxo.outputIndex 315 | tx.Inputs[i].PrevtxMap = utxo.txMap 316 | } 317 | 318 | tx.Outputs[0].Address = *to 319 | tx.Outputs[0].Value = amount 320 | 321 | if outputLen == 2 { 322 | tx.Outputs[1].Address = *from 323 | tx.Outputs[1].Value = fromAmount - amount - fee 324 | } 325 | 326 | tx.Sender = *from 327 | 328 | //util.GetBlockchainLogger().Debugf("Constructed transaction %v", tx) 329 | return &tx, nil 330 | } 331 | 332 | //PrintTransactionPool Print details information of transactions in a chain. 333 | func (chain *Blockchain) PrintTransactionPool() string { 334 | var buffer bytes.Buffer 335 | for _, tran := range chain.TransactionPool { 336 | buffer.WriteString(fmt.Sprintf("%s,", util.Hash(tran))) 337 | } 338 | 339 | return fmt.Sprintf("TransactionPool:[%s],", buffer.String()) 340 | } 341 | 342 | //PrintTxMap Print information of 'TxMap' in a chain. 343 | func (chain *Blockchain) PrintTxMap() string { 344 | var buffer bytes.Buffer 345 | 346 | for key, tran := range chain.txMap { 347 | buffer.WriteString(fmt.Sprintf("%s:%s,", util.HashBytes(key), util.Hash(tran))) 348 | } 349 | 350 | return fmt.Sprintf("txMap:[%s],", buffer.String()) 351 | } 352 | 353 | func (chain *Blockchain) printUTXOMap(utxoMap map[UTXO]bool) string { 354 | var buffer bytes.Buffer 355 | for utxo := range utxoMap { 356 | buffer.WriteString(fmt.Sprintf("%s,", util.Hash(utxo))) 357 | } 358 | 359 | return fmt.Sprintf("utxoMap:[%s],", buffer.String()) 360 | } 361 | 362 | //PrintAddressMap Print information of 'AddressMap' in a chain. 363 | func (chain *Blockchain) PrintAddressMap() string { 364 | var buffer bytes.Buffer 365 | for address, utxos := range chain.AddressMap { 366 | buffer.WriteString(fmt.Sprintf("%s:%s", util.GetShortIdentity(address), chain.printUTXOMap(utxos))) 367 | } 368 | 369 | return fmt.Sprintf("AddressMap:[%s],", buffer.String()) 370 | } 371 | 372 | //PrintUTXOMap Print information of 'utxoMap' in a chain. 373 | func (chain *Blockchain) PrintUTXOMap() string { 374 | return chain.printUTXOMap(chain.utxoMap) 375 | } 376 | 377 | //PrintBlockList Print information of 'blockList' in a chain. 378 | func (chain *Blockchain) PrintBlockList() string { 379 | var buffer bytes.Buffer 380 | for _, block := range chain.blockList { 381 | buffer.WriteString(fmt.Sprintf("%s,", util.Hash(block))) 382 | } 383 | 384 | return fmt.Sprintf("blockList:[%s],", buffer.String()) 385 | } 386 | 387 | //Print details of a chain. 388 | func (chain *Blockchain) Print() string { 389 | var buffer bytes.Buffer 390 | buffer.WriteString(chain.PrintTxMap()) 391 | buffer.WriteString(chain.PrintUTXOMap()) 392 | buffer.WriteString(chain.PrintBlockList()) 393 | buffer.WriteString(fmt.Sprintf("difficulty:[%s],", chain.difficulty.Print())) 394 | buffer.WriteString(fmt.Sprintf("TransactionPool:[%s],", chain.PrintTransactionPool())) 395 | buffer.WriteString(chain.PrintAddressMap()) 396 | buffer.WriteString(fmt.Sprintf("lastblock:[%s]", chain.GetLatestBlock().Print())) 397 | return buffer.String() 398 | } 399 | -------------------------------------------------------------------------------- /core/blockchain_booster.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/rsa" 5 | "time" 6 | 7 | "../config" 8 | ) 9 | 10 | //InitializeBlockchainWithDiff creates a blockchain from scratch 11 | func InitializeBlockchainWithDiff(gensisAddress *rsa.PublicKey, diff Difficulty) Blockchain { 12 | var chain Blockchain 13 | chain.txMap = make(map[[config.HashSize]byte]*Transaction) 14 | chain.utxoMap = make(map[UTXO]bool) 15 | chain.blockMap = make(map[[config.HashSize]byte]*Block) 16 | chain.difficulty = diff 17 | chain.AddressMap = make(map[rsa.PublicKey]map[UTXO]bool) 18 | chain.TransactionPool = make(map[string]*Transaction) 19 | 20 | gensisBlock := CreateFirstBlock(uint64(time.Now().UnixNano()/1000000), gensisAddress) 21 | chain.performMinerTransactionAndAddBlock(gensisBlock) 22 | 23 | return chain 24 | } 25 | 26 | /* 27 | * CreateICOTransaction vests amount of coins to speific users 28 | * currently I use miner to vest the coin, but need a better thought 29 | */ 30 | /* func (chain *Blockchain) PopulateICOTransaction(from_address rsa.PublicKey, from_key *rsa.PrivateKey, to rsa.PublicKey, amount uint64) { 31 | tx, err := chain.TransferCoin(&from_address, &to, config.MinerRewardBase/4, 500) 32 | if err != nil { 33 | util.GetBoosterLogger().Errorf("%v\n", err) 34 | return 35 | } 36 | tx.SignTransaction([]*rsa.PrivateKey{from_key}) 37 | 38 | util.GetBoosterLogger().Debugf("%s\n", tx.Print()) 39 | chain.AcceptBroadcastedTransaction(tx) 40 | } */ 41 | -------------------------------------------------------------------------------- /core/data_master.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "crypto/rsa" 5 | "encoding/binary" 6 | ) 7 | 8 | func appendUint32(data []byte, Value uint32) []byte { 9 | b := make([]byte, 4) 10 | binary.BigEndian.PutUint32(b, Value) 11 | return append(data, b...) 12 | } 13 | 14 | func appendUint64(data []byte, Value uint64) []byte { 15 | b := make([]byte, 8) 16 | binary.BigEndian.PutUint64(b, Value) 17 | return append(data, b...) 18 | } 19 | 20 | func appendAddress(data []byte, key *rsa.PublicKey) []byte { 21 | data = appendUint32(data, uint32(key.E)) 22 | keyBytes := key.N.Bytes() 23 | data = appendUint32(data, uint32(len(keyBytes))) 24 | return append(data, keyBytes...) 25 | } 26 | 27 | func appendUint256(data []byte, Value uint256) []byte { 28 | for i := 0; i < 4; i++ { 29 | data = appendUint64(data, Value.data[i]) 30 | } 31 | return data 32 | } 33 | -------------------------------------------------------------------------------- /core/difficulty.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "../config" 8 | ) 9 | 10 | //Difficulty used for encapsulate check/update/print functions relevant the calcuate difficulty. 11 | type Difficulty interface { 12 | ReachDifficulty(hash [config.HashSize]byte) bool 13 | UpdateDifficulty(usedTimeMs uint64) error 14 | Print() string 15 | } 16 | 17 | //SimpleDifficulty A simple wrapper of difficulty. 18 | type SimpleDifficulty struct { 19 | targetBlockIntervalMs uint64 /* interval in ms */ 20 | difficulty [config.HashSize]byte 21 | } 22 | 23 | // MADifficulty Moving average difficulty algorithm 24 | // Note that BCH's new difficulty algorithm is similar to the algorithm with 25 | // - target interval is 10mins 26 | // - maSamples is 144 27 | // That is moving average over the difficulty in a day 28 | type MADifficulty struct { 29 | targetBlockIntervalMs uint64 30 | maSamples uint32 /* number of samples to ma */ 31 | workSamples []*big.Int 32 | usedTimeMsSamples []uint64 /* used time samples */ 33 | difficulty [config.HashSize]byte 34 | } 35 | 36 | //CreateSimpleDifficulty Create a 'SimpleDifficulty' 37 | func CreateSimpleDifficulty(targetBlockIntervalMs uint64, prob float64) Difficulty { 38 | var diff SimpleDifficulty 39 | diff.targetBlockIntervalMs = targetBlockIntervalMs 40 | var buf [config.HashSize]byte 41 | for i := range buf { 42 | buf[i] = byte(prob * 256) 43 | prob = prob*256 - float64(buf[i]) 44 | } 45 | diff.difficulty = buf 46 | return &diff 47 | } 48 | 49 | func hashIsSmallerOrEqual(hash1 *[config.HashSize]byte, hash2 *[config.HashSize]byte) bool { 50 | for i := 0; i < config.HashSize; i++ { 51 | if hash1[i] < hash2[i] { 52 | break 53 | } else if hash1[i] > hash2[i] { 54 | return false 55 | } 56 | } 57 | return true 58 | } 59 | 60 | //ReachDifficulty Check whether the block has reached the difficulty 61 | func (d *SimpleDifficulty) ReachDifficulty(hash [config.HashSize]byte) bool { 62 | return hashIsSmallerOrEqual(&hash, &d.difficulty) 63 | } 64 | 65 | //UpdateDifficulty Update the difficulty 66 | func (d *SimpleDifficulty) UpdateDifficulty(usedTimeMs uint64) error { 67 | var v, target, used, mul, newDiff big.Int 68 | v.SetBytes(d.difficulty[:]) 69 | target.SetUint64(d.targetBlockIntervalMs) 70 | used.SetUint64(usedTimeMs) 71 | mul.Mul(&v, &used) 72 | newDiff.Div(&mul, &target) 73 | 74 | buf := newDiff.Bytes() 75 | for i := range d.difficulty { 76 | d.difficulty[i] = 0 77 | } 78 | for i, b := range buf { 79 | d.difficulty[config.HashSize-len(buf)+i] = b 80 | } 81 | return nil 82 | } 83 | 84 | //Print details of a SimpleDifficulty 85 | func (d *SimpleDifficulty) Print() string { 86 | return fmt.Sprintf("SimpleDifficulty:[targetBlockIntervalMs:%v,difficulty:%v] \n", 87 | d.targetBlockIntervalMs, 88 | d.difficulty, 89 | ) 90 | } 91 | 92 | //CreateMADifficulty Create a MADifficulty 93 | func CreateMADifficulty(targetBlockIntervalMs uint64, prob float64, maSamples uint32) Difficulty { 94 | var diff MADifficulty 95 | diff.targetBlockIntervalMs = targetBlockIntervalMs 96 | var buf [config.HashSize]byte 97 | for i := range buf { 98 | buf[i] = byte(prob * 256) 99 | prob = prob*256 - float64(buf[i]) 100 | } 101 | diff.difficulty = buf 102 | diff.maSamples = maSamples 103 | return &diff 104 | } 105 | 106 | //ReachDifficulty Check whether the block has reached the difficulty 107 | func (d *MADifficulty) ReachDifficulty(hash [config.HashSize]byte) bool { 108 | return hashIsSmallerOrEqual(&hash, &d.difficulty) 109 | } 110 | 111 | func diffToWork(diff [config.HashSize]byte) *big.Int { 112 | var unit [config.HashSize + 1]byte 113 | unit[0] = 1 114 | var uInt, dInt, wInt big.Int 115 | uInt.SetBytes(unit[:]) 116 | dInt.SetBytes(diff[:]) 117 | wInt.Div(&uInt, &dInt) 118 | return &wInt 119 | } 120 | 121 | func workToDiff(work *big.Int) *[config.HashSize]byte { 122 | var unit [config.HashSize + 1]byte 123 | unit[0] = 1 124 | var uInt, dInt big.Int 125 | uInt.SetBytes(unit[:]) 126 | dInt.Div(&uInt, work) 127 | 128 | var diff [config.HashSize]byte 129 | buf := dInt.Bytes() 130 | 131 | for i, b := range buf { 132 | diff[config.HashSize-len(buf)+i] = b 133 | } 134 | return &diff 135 | } 136 | 137 | //UpdateDifficulty Update the difficulty 138 | func (d *MADifficulty) UpdateDifficulty(usedTimeMs uint64) error { 139 | d.usedTimeMsSamples = append(d.usedTimeMsSamples, usedTimeMs) 140 | d.workSamples = append(d.workSamples, diffToWork(d.difficulty)) 141 | 142 | if uint32(len(d.usedTimeMsSamples)) < d.maSamples { 143 | return nil 144 | } 145 | 146 | if uint32(len(d.usedTimeMsSamples)) > d.maSamples { 147 | d.usedTimeMsSamples = d.usedTimeMsSamples[1:] 148 | d.workSamples = d.workSamples[1:] 149 | } 150 | 151 | var totalWork big.Int 152 | var totalTimeMs uint64 153 | for _, w := range d.workSamples { 154 | currentWork := totalWork 155 | totalWork.Add(¤tWork, w) 156 | } 157 | 158 | for _, usedMs := range d.usedTimeMsSamples { 159 | totalTimeMs += usedMs 160 | } 161 | 162 | var expectedWork, used, target, tmp big.Int 163 | target.SetUint64(d.targetBlockIntervalMs) 164 | used.SetUint64(totalTimeMs) 165 | tmp.Mul(&totalWork, &target) 166 | expectedWork.Div(&tmp, &used) 167 | 168 | d.difficulty = *workToDiff(&expectedWork) 169 | 170 | return nil 171 | } 172 | 173 | //Print details of a MADifficulty 174 | func (d *MADifficulty) Print() string { 175 | return fmt.Sprintf("MADifficulty:[targetBlockIntervalMs:%v,maSamples:%d,workSamples:%v,usedTimeMsSamples:%v,difficulty:%v]", 176 | d.targetBlockIntervalMs, 177 | d.maSamples, 178 | d.workSamples, 179 | d.usedTimeMsSamples, 180 | d.difficulty, 181 | ) 182 | } 183 | -------------------------------------------------------------------------------- /core/transaction.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "bytes" 5 | "crypto/md5" 6 | "crypto/rsa" 7 | "errors" 8 | "fmt" 9 | 10 | "../config" 11 | "../util" 12 | ) 13 | 14 | //TransactionInput is a reference to an output of a previous transaction 15 | type TransactionInput struct { 16 | PrevtxMap [config.HashSize]byte 17 | OutputIndex uint32 18 | Signature []byte 19 | } 20 | 21 | //TransactionOutput contains instructions for sending bitcoins. 22 | type TransactionOutput struct { 23 | Value uint64 24 | Address rsa.PublicKey 25 | } 26 | 27 | //Transaction contains a list of Inputs and Outputs. 28 | // To become a valid transation, it must contain the all signatures 29 | // from all users 30 | type Transaction struct { 31 | ID string 32 | Inputs []TransactionInput 33 | Outputs []TransactionOutput 34 | Sender rsa.PublicKey 35 | } 36 | 37 | //CreateTransaction create a transaction with specified count of inputs and outputs 38 | func CreateTransaction(ninput int, noutput int) Transaction { 39 | var tran Transaction 40 | for i := 0; i < ninput; i++ { 41 | var input TransactionInput 42 | tran.Inputs = append(tran.Inputs, input) 43 | } 44 | for i := 0; i < noutput; i++ { 45 | var output TransactionOutput 46 | tran.Outputs = append(tran.Outputs, output) 47 | } 48 | 49 | tran.ID = util.GetShortedUniqueId() 50 | return tran 51 | } 52 | 53 | /* 54 | * Get the raw data to sign. 55 | * Basically it seralizes the transaction except the signature field 56 | */ 57 | func (tran *Transaction) getRawDataToSign() []byte { 58 | var data []byte 59 | for i := 0; i < len(tran.Inputs); i++ { 60 | data = appendUint32(data, tran.Inputs[i].OutputIndex) 61 | data = append(data, tran.Inputs[i].PrevtxMap[:]...) 62 | } 63 | 64 | for i := 0; i < len(tran.Outputs); i++ { 65 | data = appendUint64(data, tran.Outputs[i].Value) 66 | data = appendAddress(data, &tran.Outputs[i].Address) 67 | } 68 | return data 69 | } 70 | 71 | // GetRawDataToHash Get the raw data to hash the whole transaction 72 | func (tran *Transaction) GetRawDataToHash() []byte { 73 | var data []byte 74 | for i := 0; i < len(tran.Inputs); i++ { 75 | data = appendUint32(data, tran.Inputs[i].OutputIndex) 76 | data = append(data, tran.Inputs[i].PrevtxMap[:]...) 77 | data = append(data, tran.Inputs[i].Signature...) 78 | } 79 | 80 | for i := 0; i < len(tran.Outputs); i++ { 81 | data = appendUint64(data, tran.Outputs[i].Value) 82 | data = appendAddress(data, &tran.Outputs[i].Address) 83 | } 84 | return data 85 | } 86 | 87 | //GetRawDataToHashForTest Get the raw data to hash the whole transaction 88 | func (tran *Transaction) GetRawDataToHashForTest() []byte { 89 | return tran.GetRawDataToHash() 90 | } 91 | 92 | //SignTransaction Sign a transaction in place (in practice, it should be called by each signer individually) 93 | func (tran *Transaction) SignTransaction(signers []*rsa.PrivateKey) error { 94 | if len(signers) != len(tran.Inputs) { 95 | return errors.New("Number of signers mismatch that of Inputs") 96 | } 97 | data := tran.getRawDataToSign() 98 | for i := 0; i < len(signers); i++ { 99 | signature, err := util.Sign(data, signers[i]) 100 | if err != nil { 101 | return err 102 | } 103 | tran.Inputs[i].Signature = signature 104 | } 105 | return nil 106 | } 107 | 108 | //VerifyTransaction Verify whether a transaction has valid signatures. 109 | //Note that it doesn't verify whether the transaction is valid in the chain. 110 | func (tran *Transaction) VerifyTransaction(inputAddresses []*rsa.PublicKey) error { 111 | if len(inputAddresses) != len(tran.Inputs) { 112 | return errors.New("Number of Addresses mismatch that of Inputs") 113 | } 114 | data := tran.getRawDataToSign() 115 | for i := 0; i < len(inputAddresses); i++ { 116 | err := util.VerifySignature(data, tran.Inputs[i].Signature, inputAddresses[i]) 117 | if err != nil { 118 | return err 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | //Print details of transaction input 125 | func (input TransactionInput) Print() string { 126 | return fmt.Sprintf("TransactionInput:%s[PrevtxMap:%s,OutputIndex:%x,Signature:%x],", 127 | util.Hash(input), 128 | util.HashBytes(input.PrevtxMap), 129 | input.OutputIndex, 130 | md5.Sum(input.Signature), 131 | ) 132 | } 133 | 134 | //Print details of transaction output 135 | func (output TransactionOutput) Print() string { 136 | return fmt.Sprintf("TransactionOutput:%s[Address:%v,Value:%v],", 137 | util.Hash(output), 138 | util.GetShortIdentity(output.Address), 139 | output.Value, 140 | ) 141 | } 142 | 143 | //Print details of transaction 144 | func (tran Transaction) Print() string { 145 | var buffer bytes.Buffer 146 | for _, in := range tran.Inputs { 147 | buffer.WriteString(in.Print()) 148 | } 149 | 150 | for _, out := range tran.Outputs { 151 | buffer.WriteString(out.Print()) 152 | } 153 | 154 | return fmt.Sprintf("Transaction:%s[%s],", tran.ID, buffer.String()) 155 | } 156 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rsa" 6 | "fmt" 7 | "math/rand" 8 | "time" 9 | 10 | "./config" 11 | "./core" 12 | "./role" 13 | "./util" 14 | ) 15 | 16 | var chain core.Blockchain 17 | var users []*role.User 18 | var miner *role.Miner 19 | 20 | const userCount = 4 21 | 22 | func boostNetwork() { 23 | // 1. create the initial user of blockchain 24 | firstUser := role.CreateBoostUser() 25 | 26 | // 2. boost the blockchain with initial user 27 | diff := core.CreateMADifficulty(10000, 0.2, 16) 28 | chain = core.InitializeBlockchainWithDiff(&firstUser.Address, diff) 29 | } 30 | 31 | func boostUsers() { 32 | // create 10 users 33 | for i := 0; i < userCount; i++ { 34 | user := role.CreateUser(chain) 35 | users = append(users, user) 36 | util.GetMainLogger().Infof("User %v created\n", user.GetShortIdentity()) 37 | } 38 | } 39 | 40 | func startTrading() { 41 | s1 := rand.NewSource(time.Now().UnixNano()) 42 | r1 := rand.New(s1) 43 | 44 | block := miner.GetBlockChain().GetLatestBlock() 45 | var usage [userCount + 1]int // miner is the last one 46 | for i := 0; i <= userCount; i++ { 47 | usage[userCount] = 0 48 | } 49 | 50 | var from, to int 51 | for i := 0; true; i++ { 52 | from = r1.Intn(userCount) 53 | if from < userCount/2 { 54 | to = userCount/2 + r1.Intn(userCount/2) 55 | } else { 56 | to = r1.Intn(userCount / 2) 57 | } 58 | 59 | //if block.GetBlockHash() != chain.GetLatestBlock().GetBlockHash() { 60 | if util.Hash(*block) != util.Hash(miner.GetBlockChain().GetLatestBlock()) { 61 | util.GetMainLogger().Infof("Chain confirmed a new block. Clean the usage\n") 62 | 63 | block = miner.GetBlockChain().GetLatestBlock() 64 | } 65 | 66 | if block != nil { 67 | time.Sleep(500 * time.Millisecond) 68 | } 69 | 70 | amount := r1.Intn(config.MinerRewardBase / 1000) 71 | fee := r1.Intn(10) 72 | if couldUserPostTransaction(miner.Address) && int(miner.GetBlockChain().BalanceOf(&miner.Address)) > amount { 73 | miner.SendTo(users[to], uint64(amount), uint64(fee)) 74 | time.Sleep(1 * time.Second) 75 | } 76 | 77 | amount = r1.Intn(config.MinerRewardBase / 1000) 78 | fee = r1.Intn(userCount) 79 | if couldUserPostTransaction(users[from].Address) && int(miner.GetBlockChain().BalanceOf(&users[from].Address)) > amount { 80 | users[from].SendTo(users[to], uint64(amount), uint64(fee)) 81 | time.Sleep(1 * time.Second) 82 | } 83 | } 84 | } 85 | 86 | func initializeOneMinerAndStartMining() { 87 | miner = role.CreateMiner(chain) 88 | miner.StartMining() 89 | } 90 | 91 | func printStatus() { 92 | for { 93 | var buffer bytes.Buffer 94 | buffer.WriteString(fmt.Sprintf("Miner[%s:%d]] ", miner.GetShortIdentity(), miner.GetBlockChain().BalanceOf(&miner.Address))) 95 | 96 | for i := 0; i < userCount; i++ { 97 | buffer.WriteString(fmt.Sprintf("User[%s:%d]] ", users[i].GetShortIdentity(), miner.GetBlockChain().BalanceOf(&users[i].Address))) 98 | } 99 | 100 | util.GetMainLogger().Debugf("Account Status: %s\n", buffer.String()) 101 | //util.GetMainLogger().Debugf("Chain Status: %s\n", miner.GetBlockChain().Print()) 102 | 103 | time.Sleep(1 * time.Second) 104 | } 105 | } 106 | 107 | // TODO this implementation doesn't support multiple transactions from same user. 108 | func couldUserPostTransaction(sender rsa.PublicKey) bool { 109 | for _, tran := range miner.GetBlockChain().TransactionPool { 110 | if tran.Sender == sender { 111 | return false 112 | } 113 | } 114 | return true 115 | } 116 | 117 | /* 118 | * This function is to simulate the blochchain workflow 119 | */ 120 | func runSimulator() { 121 | // 1. boost the blochchain 122 | util.GetMainLogger().Infof("Start to boost blockchain \n") 123 | boostNetwork() 124 | util.GetMainLogger().Infof("Finished boosting blockchain \n") 125 | 126 | // 3. initialize a miner to mine the trasaction and generate block 127 | util.GetMainLogger().Infof("Start to boost miner \n") 128 | go initializeOneMinerAndStartMining() 129 | 130 | //time.Sleep(10 * time.Second) 131 | 132 | // 2. boost users 133 | util.GetMainLogger().Infof("Start to boost users \n") 134 | boostUsers() 135 | util.GetMainLogger().Infof("Finished boosting users \n") 136 | 137 | // 3. print status 138 | go printStatus() 139 | 140 | // 4. use miner to vest coins to user. Like user buy coins from exchange 141 | 142 | //time.Sleep(10 * time.Second) 143 | // 5. initialize a few users for generating random transactions 144 | util.GetMainLogger().Infof("Start to boost trading \n") 145 | go startTrading() 146 | 147 | for { 148 | time.Sleep(10 * time.Second) 149 | } 150 | } 151 | 152 | func main() { 153 | runSimulator() 154 | } 155 | -------------------------------------------------------------------------------- /role/miner.go: -------------------------------------------------------------------------------- 1 | package role 2 | 3 | import ( 4 | "crypto/rsa" 5 | "time" 6 | 7 | "github.com/juju/loggo" 8 | 9 | "../core" 10 | "../util" 11 | ) 12 | 13 | type Miner struct { 14 | chain core.Blockchain 15 | key *rsa.PrivateKey 16 | 17 | Address rsa.PublicKey 18 | TransactionPool []*core.Transaction 19 | } 20 | 21 | func CreateMiner(chain core.Blockchain) *Miner { 22 | user := CreateUser(chain) 23 | 24 | var miner Miner 25 | miner.Address = user.Address 26 | miner.key = user.key 27 | miner.chain = chain 28 | 29 | return &miner 30 | } 31 | 32 | /* 33 | * StartMining starts to propose and confirm block in the chain 34 | * Here assume we only have one miner, otherwise this function need to handle multi-threading 35 | */ 36 | func (miner *Miner) StartMining() { 37 | miner.getLogger().Infof("Miner %v starts mining\n", miner.GetShortIdentity()) 38 | for i := 0; true; i++ { 39 | block := core.CreateNextEmptyBlock(miner.chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000), &miner.Address) 40 | for _, tran := range miner.chain.TransactionPool { 41 | block.AddTransaction(tran) 42 | miner.getLogger().Debugf("Added transaction %s\n", tran.Print()) 43 | } 44 | 45 | var nuance uint64 46 | startTime := time.Now() 47 | 48 | for true { 49 | //miner.getLogger().Infof("current transaction pool %v\n", miner.chain.PrintTransactionPool()) 50 | 51 | block.FinalizeBlockAt(nuance, uint64(time.Now().UnixNano()/1000000)) 52 | if miner.chain.ReachDifficulty(block) { 53 | miner.getLogger().Debugf("Current chain:%s\n", miner.chain.Print()) 54 | miner.getLogger().Debugf("Start to confirm block: %s\n", block.Print()) 55 | err := miner.chain.AddBlock(block) 56 | if err != nil { 57 | miner.getLogger().Errorf("Failed to add a valid block: %s\n", err) 58 | continue 59 | } 60 | miner.getLogger().Infof("Confimed Block %s\n", util.Hash(block)) 61 | 62 | break 63 | } else { 64 | //miner.getLogger().Debugf("Not meet difficulty and sleep 1s\n") 65 | time.Sleep(1000 * time.Millisecond) 66 | } 67 | 68 | nuance++ 69 | } 70 | 71 | miner.getLogger().Infof("Mined %d th block at %s (used time (ms) %d, nuance %d)\n", 72 | i+1, time.Now(), (time.Now().UnixNano()-startTime.UnixNano())/1000000, nuance) 73 | miner.getLogger().Infof("New difficulty: %s \n", miner.chain.GetDifficulty().Print()) 74 | } 75 | } 76 | 77 | func (miner *Miner) GetShortIdentity() string { 78 | return util.GetShortIdentity(miner.Address) 79 | } 80 | 81 | func (miner *Miner) GetPrivateKey() *rsa.PrivateKey { 82 | return miner.key 83 | } 84 | 85 | func (miner *Miner) GetBlockChain() *core.Blockchain { 86 | return &miner.chain 87 | } 88 | 89 | func (miner *Miner) getLogger() loggo.Logger { 90 | return util.GetMinerLogger(miner.GetShortIdentity()) 91 | } 92 | 93 | func (miner *Miner) SendTo(receipt *User, amount uint64, fee uint64) { 94 | tran, err := miner.chain.TransferCoin(&miner.Address, &receipt.Address, amount, fee) 95 | if err != nil { 96 | miner.getLogger().Errorf("Failed to create transaction: %v\n", err) 97 | return 98 | } 99 | 100 | tran.SignTransaction([]*rsa.PrivateKey{miner.GetPrivateKey()}) 101 | 102 | miner.getLogger().Debugf("%s\n", tran.Print()) 103 | miner.chain.AcceptBroadcastedTransaction(tran) 104 | miner.getLogger().Infof("User %v sends %d coins to user %v\n", miner.GetShortIdentity(), amount, receipt.GetShortIdentity()) 105 | } 106 | -------------------------------------------------------------------------------- /role/user.go: -------------------------------------------------------------------------------- 1 | package role 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | 7 | "github.com/juju/loggo" 8 | 9 | "../core" 10 | "../util" 11 | ) 12 | 13 | type User struct { 14 | chain core.Blockchain 15 | 16 | key *rsa.PrivateKey 17 | Address rsa.PublicKey 18 | } 19 | 20 | /* 21 | * CreateBoostUser to create the first user before boosting the chain 22 | */ 23 | func CreateBoostUser() *User { 24 | return createUser() 25 | } 26 | 27 | /* 28 | * RegisterBoostUser used to register the boost user after initialize the blockchain 29 | */ 30 | func (user *User) RegisterBoostUser(chain core.Blockchain) { 31 | user.chain = chain 32 | 33 | utxoMap := make(map[core.UTXO]bool) 34 | chain.RegisterUser(user.Address, utxoMap) 35 | user.getLogger().Debugf("Register boost user %v\n", user.GetShortIdentity()) 36 | 37 | } 38 | 39 | func CreateUser(chain core.Blockchain) *User { 40 | user := createUser() 41 | user.chain = chain 42 | 43 | utxoMap := make(map[core.UTXO]bool) 44 | chain.RegisterUser(user.Address, utxoMap) 45 | 46 | return user 47 | } 48 | 49 | func createUser() *User { 50 | var user User 51 | 52 | account, err := rsa.GenerateKey(rand.Reader, 1024) 53 | if err != nil { 54 | user.getLogger().Errorf("Cannot create account: %s\n", err) 55 | return nil 56 | } 57 | 58 | user.Address = account.PublicKey 59 | user.key = account 60 | 61 | user.getLogger().Debugf("Created a user at %v\n", user.GetShortIdentity()) 62 | return &user 63 | } 64 | 65 | func (user *User) Balance() uint64 { 66 | return user.chain.BalanceOf(&user.Address) 67 | } 68 | 69 | func (user *User) SendTo(receipt *User, amount uint64, fee uint64) { 70 | tran, err := user.chain.TransferCoin(&user.Address, &receipt.Address, amount, fee) 71 | if err != nil { 72 | user.getLogger().Errorf("Failed to create transaction: %v\n", err) 73 | return 74 | } 75 | 76 | tran.SignTransaction([]*rsa.PrivateKey{user.GetPrivateKey()}) 77 | 78 | user.getLogger().Debugf("%s\n", tran.Print()) 79 | user.chain.AcceptBroadcastedTransaction(tran) 80 | user.getLogger().Infof("User %v sends %d coins to user %v\n", user.GetShortIdentity(), amount, receipt.GetShortIdentity()) 81 | } 82 | 83 | /* 84 | * BroadcastTransaction broadcasts the transaction to all miners 85 | * TODO: The broadcast should be based on msg in real world 86 | */ 87 | func (user *User) BroadcastTransaction(tran *core.Transaction) { 88 | user.chain.AcceptBroadcastedTransaction(tran) 89 | } 90 | 91 | func (user *User) GetShortIdentity() string { 92 | return util.GetShortIdentity(user.Address) 93 | } 94 | 95 | func (user *User) GetPrivateKey() *rsa.PrivateKey { 96 | return user.key 97 | } 98 | 99 | func (user *User) getLogger() loggo.Logger { 100 | return util.GetUserLogger(user.GetShortIdentity()) 101 | } 102 | -------------------------------------------------------------------------------- /test/block_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "testing" 5 | 6 | "../core" 7 | ) 8 | 9 | func TestBlock(t *testing.T) { 10 | users, tran, err := createTestTransaction() 11 | if err != nil { 12 | t.Error("Fail to create test transaction") 13 | } 14 | 15 | block := core.CreateFirstBlock(0, &users[0].PublicKey) 16 | block.AddTransaction(tran) 17 | block.FinalizeBlockAt(0, 0) 18 | 19 | /* Forge block */ 20 | Value := block.Transactions[0].Outputs[0].Value 21 | block.Transactions[0].Outputs[0].Value = Value * 100 22 | if block.VerifyBlockHash() { 23 | t.Error("Failed to verify block hash") 24 | } 25 | block.Transactions[0].Outputs[0].Value = Value 26 | 27 | outputIndex := block.Transactions[1].Inputs[1].OutputIndex 28 | block.Transactions[1].Inputs[1].OutputIndex = 3 29 | if block.VerifyBlockHash() { 30 | t.Error("Failed to verify block hash") 31 | } 32 | block.Transactions[1].Inputs[1].OutputIndex = outputIndex 33 | } 34 | -------------------------------------------------------------------------------- /test/blockchain_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/sha256" 6 | "testing" 7 | "time" 8 | 9 | "../config" 10 | "../core" 11 | ) 12 | 13 | func TestGensisBlock(t *testing.T) { 14 | user := createTestUser(t) 15 | chain := createTestBlockchain(&user.PublicKey) 16 | 17 | if chain.BalanceOf(&user.PublicKey) != config.MinerRewardBase { 18 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase, chain.BalanceOf(&user.PublicKey)) 19 | } 20 | 21 | } 22 | 23 | func TestBlockchainSimple(t *testing.T) { 24 | user := createTestUser(t) 25 | chain := createTestBlockchain(&user.PublicKey) 26 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+1), &user.PublicKey) 27 | err := chain.AddBlock(nextBlock) 28 | if err != nil { 29 | t.Errorf("Failed to add a valid block: %s", err) 30 | } 31 | 32 | if chain.BalanceOf(&user.PublicKey) != config.MinerRewardBase*2 { 33 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase*2, chain.BalanceOf(&user.PublicKey)) 34 | } 35 | } 36 | 37 | func TestBlockchainOneTransaction(t *testing.T) { 38 | user0 := createTestUser(t) 39 | user1 := createTestUser(t) 40 | chain := createTestBlockchain(&user0.PublicKey) 41 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase { 42 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase, chain.BalanceOf(&user0.PublicKey)) 43 | } 44 | 45 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+1), &user1.PublicKey) 46 | 47 | /* Create transaction to transfer all coins from user0 to user1 */ 48 | tx := core.CreateTransaction(1, 1) 49 | tx.Inputs[0].OutputIndex = 0 50 | tx.Inputs[0].PrevtxMap = sha256.Sum256(chain.GetLatestBlock().Transactions[0].GetRawDataToHashForTest()) 51 | tx.Outputs[0].Address = user1.PublicKey 52 | tx.Outputs[0].Value = chain.GetLatestBlock().Transactions[0].Outputs[0].Value 53 | tx.SignTransaction([]*rsa.PrivateKey{user0}) 54 | 55 | nextBlock.AddTransaction(&tx) 56 | err := chain.AddBlock(nextBlock) 57 | if err != nil { 58 | t.Errorf("Failed to add a valid block: %s", err) 59 | } 60 | 61 | if chain.BalanceOf(&user1.PublicKey) != config.MinerRewardBase*2 { 62 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase*2, chain.BalanceOf(&user1.PublicKey)) 63 | } 64 | 65 | if chain.BalanceOf(&user0.PublicKey) != 0 { 66 | t.Errorf("User balance is incorrect: expected %d, actual %d", 0, chain.BalanceOf(&user0.PublicKey)) 67 | } 68 | } 69 | 70 | func TestBlockchainWithTransactionUnsigned(t *testing.T) { 71 | user0 := createTestUser(t) 72 | user1 := createTestUser(t) 73 | chain := createTestBlockchain(&user0.PublicKey) 74 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000), &user1.PublicKey) 75 | 76 | /* Create transaction to transfer all coins from user0 to user1 */ 77 | tx := core.CreateTransaction(1, 1) 78 | tx.Inputs[0].OutputIndex = 0 79 | //tx.Inputs[0].PrevtxMap = sha256.Sum256(chain.GetLatestBlock().Transactions[0].getRawDataToHash()) 80 | tx.Outputs[0].Address = user1.PublicKey 81 | tx.Outputs[0].Value = chain.GetLatestBlock().Transactions[0].Outputs[0].Value 82 | 83 | nextBlock.AddTransaction(&tx) 84 | err := chain.AddBlock(nextBlock) 85 | if err == nil { 86 | t.Errorf("Failed to verify a invalid block: %s", err) 87 | } 88 | } 89 | 90 | func TestBlockchainWithTransactionInvalidAmount(t *testing.T) { 91 | user0 := createTestUser(t) 92 | user1 := createTestUser(t) 93 | chain := createTestBlockchain(&user0.PublicKey) 94 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000), &user1.PublicKey) 95 | 96 | /* Create transaction to transfer all coins from user0 to user1 */ 97 | tx := core.CreateTransaction(1, 1) 98 | tx.Inputs[0].OutputIndex = 0 99 | //tx.Inputs[0].PrevtxMap = sha256.Sum256(chain.GetLatestBlock().Transactions[0].getRawDataToHash()) 100 | tx.Outputs[0].Address = user1.PublicKey 101 | tx.Outputs[0].Value = chain.GetLatestBlock().Transactions[0].Outputs[0].Value + 1 102 | tx.SignTransaction([]*rsa.PrivateKey{user0}) 103 | 104 | nextBlock.AddTransaction(&tx) 105 | err := chain.AddBlock(nextBlock) 106 | if err == nil { 107 | t.Errorf("Failed to verify a invalid block: %s", err) 108 | } 109 | } 110 | 111 | func TestBlockchainTransfer(t *testing.T) { 112 | user0 := createTestUser(t) 113 | user1 := createTestUser(t) 114 | chain := createTestBlockchain(&user0.PublicKey) 115 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase { 116 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase, chain.BalanceOf(&user0.PublicKey)) 117 | } 118 | 119 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+1), &user1.PublicKey) 120 | 121 | /* Create transaction to transfer all coins from user0 to user1 */ 122 | tx, _ := chain.TransferCoin(&user0.PublicKey, &user1.PublicKey, config.MinerRewardBase/2, 0) 123 | tx.SignTransaction([]*rsa.PrivateKey{user0}) 124 | 125 | nextBlock.AddTransaction(tx) 126 | err := chain.AddBlock(nextBlock) 127 | if err != nil { 128 | t.Errorf("Failed to add a valid block: %s", err) 129 | } 130 | 131 | if chain.BalanceOf(&user1.PublicKey) != config.MinerRewardBase*1.5 { 132 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*1.5, chain.BalanceOf(&user1.PublicKey)) 133 | } 134 | 135 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase*0.5 { 136 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*0.5, chain.BalanceOf(&user0.PublicKey)) 137 | } 138 | 139 | nextBlock = core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+2), &user1.PublicKey) 140 | tx, _ = chain.TransferCoin(&user1.PublicKey, &user0.PublicKey, config.MinerRewardBase*1.2, 0) 141 | tx.SignTransaction([]*rsa.PrivateKey{user1, user1}) 142 | nextBlock.AddTransaction(tx) 143 | err = chain.AddBlock(nextBlock) 144 | if err != nil { 145 | t.Errorf("Failed to add a valid block: %s", err) 146 | } 147 | 148 | if chain.BalanceOf(&user1.PublicKey) != config.MinerRewardBase*1.3 { 149 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*1.3, chain.BalanceOf(&user1.PublicKey)) 150 | } 151 | 152 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase*1.7 { 153 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*1.7, chain.BalanceOf(&user0.PublicKey)) 154 | } 155 | } 156 | 157 | func TestBlockchainFee(t *testing.T) { 158 | user0 := createTestUser(t) 159 | user1 := createTestUser(t) 160 | user2 := createTestUser(t) 161 | chain := createTestBlockchain(&user0.PublicKey) 162 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase { 163 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase, chain.BalanceOf(&user0.PublicKey)) 164 | } 165 | 166 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+1), &user1.PublicKey) 167 | 168 | /* Create transaction to transfer all coins from user0 to user1 */ 169 | tx, _ := chain.TransferCoin(&user0.PublicKey, &user2.PublicKey, config.MinerRewardBase/2, 1000) 170 | tx.SignTransaction([]*rsa.PrivateKey{user0}) 171 | 172 | nextBlock.AddTransaction(tx) 173 | nextBlock.Transactions[0].Outputs[0].Value += 1000 174 | err := chain.AddBlock(nextBlock) 175 | if err != nil { 176 | t.Errorf("Failed to add a valid block: %s", err) 177 | } 178 | 179 | if chain.BalanceOf(&user1.PublicKey) != config.MinerRewardBase+1000 { 180 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*0.5, chain.BalanceOf(&user1.PublicKey)) 181 | } 182 | 183 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase*0.5-1000 { 184 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*0.5-1000, chain.BalanceOf(&user0.PublicKey)) 185 | } 186 | 187 | if chain.BalanceOf(&user2.PublicKey) != config.MinerRewardBase*0.5 { 188 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*0.5, chain.BalanceOf(&user0.PublicKey)) 189 | } 190 | 191 | } 192 | 193 | func TestBlockchainMulitipleFee(t *testing.T) { 194 | user0 := createTestUser(t) 195 | user1 := createTestUser(t) 196 | user2 := createTestUser(t) 197 | user3 := createTestUser(t) 198 | chain := createTestBlockchain(&user0.PublicKey) 199 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase { 200 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase, chain.BalanceOf(&user0.PublicKey)) 201 | } 202 | 203 | nextBlock := core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+1), &user1.PublicKey) 204 | err := chain.AddBlock(nextBlock) 205 | if err != nil { 206 | t.Errorf("Failed to add a valid block: %s", err) 207 | } 208 | 209 | nextBlock = core.CreateNextEmptyBlock(chain.GetLatestBlock(), uint64(time.Now().UnixNano()/1000000+2), &user2.PublicKey) 210 | tx0, _ := chain.TransferCoin(&user0.PublicKey, &user2.PublicKey, config.MinerRewardBase/2, 1000) 211 | tx0.SignTransaction([]*rsa.PrivateKey{user0}) 212 | nextBlock.AddTransaction(tx0) 213 | nextBlock.Transactions[0].Outputs[0].Value += 1000 214 | 215 | tx1, _ := chain.TransferCoin(&user1.PublicKey, &user3.PublicKey, config.MinerRewardBase/4, 500) 216 | tx1.SignTransaction([]*rsa.PrivateKey{user1}) 217 | nextBlock.AddTransaction(tx1) 218 | nextBlock.Transactions[0].Outputs[0].Value += 500 219 | 220 | err = chain.AddBlock(nextBlock) 221 | if err != nil { 222 | t.Errorf("Failed to add a valid block: %s", err) 223 | } 224 | 225 | if chain.BalanceOf(&user0.PublicKey) != config.MinerRewardBase/2-1000 { 226 | t.Errorf("User balance is incorrect: expected %f, actual %d, %x", config.MinerRewardBase*0.5-1000, chain.BalanceOf(&user0.PublicKey), user0.PublicKey) 227 | } 228 | 229 | if chain.BalanceOf(&user1.PublicKey) != config.MinerRewardBase*3/4-500 { 230 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase/4-500, chain.BalanceOf(&user1.PublicKey)) 231 | } 232 | 233 | if chain.BalanceOf(&user2.PublicKey) != config.MinerRewardBase*1.5+1500 { 234 | t.Errorf("User balance is incorrect: expected %f, actual %d", config.MinerRewardBase*1.5+1500, chain.BalanceOf(&user2.PublicKey)) 235 | } 236 | 237 | if chain.BalanceOf(&user3.PublicKey) != config.MinerRewardBase/4 { 238 | t.Errorf("User balance is incorrect: expected %d, actual %d", config.MinerRewardBase/4, chain.BalanceOf(&user3.PublicKey)) 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /test/crypto_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "testing" 7 | 8 | "../util" 9 | ) 10 | 11 | func TestSign(t *testing.T) { 12 | message := []byte("Hello world!") 13 | 14 | priv, err := rsa.GenerateKey(rand.Reader, 1024) 15 | if err != nil { 16 | t.Errorf("Failed to generate keys %s\n", err) 17 | } 18 | 19 | signature, err := util.Sign(message, priv) 20 | 21 | if err != nil { 22 | t.Errorf("Failed to sign message %s\n", err) 23 | } 24 | 25 | if util.VerifySignature(message, signature, &priv.PublicKey) != nil { 26 | t.Errorf("Signature expected true, actual false") 27 | } 28 | 29 | priv1, err := rsa.GenerateKey(rand.Reader, 1024) 30 | signature1, err := util.Sign(message, priv1) 31 | 32 | if util.VerifySignature(message, signature1, &priv.PublicKey) == nil { 33 | t.Error("Signature expect false, actual true") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/test_util.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/rand" 5 | "crypto/rsa" 6 | "testing" 7 | 8 | "../config" 9 | "../core" 10 | ) 11 | 12 | type NoDifficulty struct { 13 | } 14 | 15 | func (d NoDifficulty) ReachDifficulty(hash [config.HashSize]byte) bool { 16 | return true 17 | } 18 | 19 | func (d NoDifficulty) UpdateDifficulty(usedTimeMs uint64) error { 20 | return nil 21 | } 22 | 23 | func (d NoDifficulty) Print() string { 24 | return "" 25 | } 26 | 27 | func createTestUser(t *testing.T) *rsa.PrivateKey { 28 | user, err := rsa.GenerateKey(rand.Reader, 1024) 29 | if err != nil { 30 | t.Errorf("Failed to create a user %s", err) 31 | return nil 32 | } 33 | return user 34 | } 35 | 36 | func createTestTransaction() ([]*rsa.PrivateKey, *core.Transaction, error) { 37 | /* Create 4 users */ 38 | var users []*rsa.PrivateKey 39 | for i := 0; i < 4; i++ { 40 | user, err := rsa.GenerateKey(rand.Reader, 1024) 41 | if err != nil { 42 | return nil, nil, err 43 | } 44 | users = append(users, user) 45 | } 46 | 47 | /* Create a transation with 2 Inputs and 3 Outputs */ 48 | tran := core.CreateTransaction(2, 3) 49 | rand.Read(tran.Inputs[0].PrevtxMap[:]) 50 | rand.Read(tran.Inputs[1].PrevtxMap[:]) 51 | 52 | tran.Outputs[0].Value = 1000 53 | tran.Outputs[0].Address = users[2].PublicKey 54 | tran.Outputs[1].Value = 2000 55 | tran.Outputs[1].Address = users[3].PublicKey 56 | tran.Outputs[2].Value = 3000 57 | tran.Outputs[2].Address = users[1].PublicKey 58 | 59 | /* Sign the transaction */ 60 | tran.SignTransaction([]*rsa.PrivateKey{users[0], users[1]}) 61 | return users, &tran, nil 62 | } 63 | 64 | /* 65 | * Create a blockchain with gensis block created by an Address 66 | */ 67 | func createTestBlockchain(gensisAddress *rsa.PublicKey) core.Blockchain { 68 | var diff NoDifficulty 69 | return core.InitializeBlockchainWithDiff(gensisAddress, diff) 70 | } 71 | -------------------------------------------------------------------------------- /test/transaction_test.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "crypto/rsa" 5 | "testing" 6 | ) 7 | 8 | func TestTransaction(t *testing.T) { 9 | /* Create 4 users */ 10 | users, tran, err := createTestTransaction() 11 | if err != nil { 12 | t.Errorf("Failed to generate keys %s", err) 13 | } 14 | 15 | /* Sign the transaction */ 16 | tran.SignTransaction([]*rsa.PrivateKey{users[0], users[1]}) 17 | 18 | /* Verify the transaction */ 19 | if tran.VerifyTransaction([]*rsa.PublicKey{&users[0].PublicKey, &users[1].PublicKey}) != nil { 20 | t.Error("Failed to verify transaction") 21 | } 22 | 23 | /* Forge the transaction and verify */ 24 | tran.Outputs[1].Value = 20000 25 | if tran.VerifyTransaction([]*rsa.PublicKey{&users[0].PublicKey, &users[1].PublicKey}) == nil { 26 | t.Error("Verified a forged transaction") 27 | } 28 | 29 | tran.Outputs[1].Value = 2000 30 | tran.Outputs[2].Address = users[0].PublicKey 31 | if tran.VerifyTransaction([]*rsa.PublicKey{&users[0].PublicKey, &users[1].PublicKey}) == nil { 32 | t.Error("Verified a forged transaction") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /util/crypto.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | "crypto/sha256" 8 | ) 9 | 10 | func Sign(message []byte, priv *rsa.PrivateKey) ([]byte, error) { 11 | rng := rand.Reader 12 | 13 | hashed := sha256.Sum256(message) 14 | 15 | signature, err := rsa.SignPKCS1v15(rng, priv, crypto.SHA256, hashed[:]) 16 | 17 | return signature, err 18 | } 19 | 20 | func VerifySignature(message []byte, signature []byte, pub *rsa.PublicKey) error { 21 | hashed := sha256.Sum256(message) 22 | 23 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], signature) 24 | } 25 | 26 | func GetShortIdentity(address rsa.PublicKey) string { 27 | full_identity := address.N.String() 28 | return full_identity[len(full_identity)-5:] 29 | } 30 | -------------------------------------------------------------------------------- /util/hash.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/md5" 5 | "encoding/hex" 6 | 7 | "../config" 8 | 9 | "github.com/cnf/structhash" 10 | "github.com/rs/xid" 11 | ) 12 | 13 | func Hash(c interface{}) string { 14 | hash, _ := structhash.Hash(c, 1) 15 | return hash[len(hash)-5:] 16 | } 17 | 18 | func hashBytes(c []byte) string { 19 | hash := md5.Sum(c) 20 | md5InString := hex.EncodeToString(hash[:]) 21 | return md5InString[len(md5InString)-5:] 22 | } 23 | 24 | func HashBytes(c [config.HashSize]byte) string { 25 | bytes := []byte(string(c[:])) 26 | return hashBytes(bytes) 27 | } 28 | 29 | func GetShortedUniqueId() string { 30 | id := xid.New().String() 31 | return hashBytes([]byte(id)) 32 | } 33 | -------------------------------------------------------------------------------- /util/logger.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/juju/loggo" 7 | "github.com/juju/loggo/loggocolor" 8 | ) 9 | 10 | func getLogger(scope string) loggo.Logger { 11 | logger := loggo.GetLogger(scope) 12 | logger.SetLogLevel(loggo.DEBUG) 13 | loggo.ReplaceDefaultWriter(loggocolor.NewWriter(os.Stderr)) 14 | 15 | return logger 16 | } 17 | 18 | func GetMainLogger() loggo.Logger { 19 | return getLogger("Main") 20 | } 21 | 22 | func GetMinerLogger(id string) loggo.Logger { 23 | return getLogger("Miner-" + id) 24 | } 25 | 26 | func GetBlockchainLogger() loggo.Logger { 27 | return getLogger("Blockchain") 28 | } 29 | 30 | func GetBlockLogger() loggo.Logger { 31 | return getLogger("Block") 32 | } 33 | 34 | func GetUserLogger(id string) loggo.Logger { 35 | return getLogger("User-" + id) 36 | } 37 | 38 | func GetBoosterLogger() loggo.Logger { 39 | return getLogger("Booster") 40 | } 41 | 42 | func GetTempLogger() loggo.Logger { 43 | return getLogger("Temp") 44 | } 45 | --------------------------------------------------------------------------------