├── .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 | 
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 |
--------------------------------------------------------------------------------