VOTING POWER100.00%
DOWNVOTE POWER100.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS90.37%
Net Worth
1.500USD
STEEM
0.001STEEM
SBD
2.962SBD
Effective Power
5.007SP
├── Own SP
1.347SP
└── Incoming DelegationsDeleg
+3.659SP
Detailed Balance
| STEEM | ||
| balance | 0.001STEEM | STEEM |
| market_balance | 0.000STEEM | STEEM |
| savings_balance | 0.000STEEM | STEEM |
| reward_steem_balance | 0.000STEEM | STEEM |
| STEEM POWER | ||
| Own SP | 1.347SP | SP |
| Delegated Out | 0.000SP | SP |
| Delegation In | 3.659SP | SP |
| Effective Power | 5.007SP | SP |
| Reward SP (pending) | 0.000SP | SP |
| SBD | ||
| sbd_balance | 2.962SBD | SBD |
| sbd_conversions | 0.000SBD | SBD |
| sbd_market_balance | 0.000SBD | SBD |
| savings_sbd_balance | 0.000SBD | SBD |
| reward_sbd_balance | 0.000SBD | SBD |
{
"balance": "0.001 STEEM",
"savings_balance": "0.000 STEEM",
"reward_steem_balance": "0.000 STEEM",
"vesting_shares": "2191.754397 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "5951.905409 VESTS",
"sbd_balance": "2.962 SBD",
"savings_sbd_balance": "0.000 SBD",
"reward_sbd_balance": "0.000 SBD",
"conversions": []
}Account Info
| name | dustvoice |
| id | 558356 |
| rank | 730,467 |
| reputation | 12601023545 |
| created | 2018-01-04T12:06:27 |
| recovery_account | steem |
| proxy | None |
| post_count | 20 |
| comment_count | 0 |
| lifetime_vote_count | 0 |
| witnesses_voted_for | 0 |
| last_post | 2018-01-06T14:03:00 |
| last_root_post | 2018-01-06T04:02:15 |
| last_vote_time | 2018-01-13T22:02:54 |
| proxied_vsf_votes | 0, 0, 0, 0 |
| can_vote | 1 |
| voting_power | 0 |
| delayed_votes | 0 |
| balance | 0.001 STEEM |
| savings_balance | 0.000 STEEM |
| sbd_balance | 2.962 SBD |
| savings_sbd_balance | 0.000 SBD |
| vesting_shares | 2191.754397 VESTS |
| delegated_vesting_shares | 0.000000 VESTS |
| received_vesting_shares | 5951.905409 VESTS |
| reward_vesting_balance | 0.000000 VESTS |
| vesting_balance | 0.000 STEEM |
| vesting_withdraw_rate | 0.000000 VESTS |
| next_vesting_withdrawal | 1969-12-31T23:59:59 |
| withdrawn | 0 |
| to_withdraw | 0 |
| withdraw_routes | 0 |
| savings_withdraw_requests | 0 |
| last_account_recovery | 1970-01-01T00:00:00 |
| reset_account | null |
| last_owner_update | 1970-01-01T00:00:00 |
| last_account_update | 2021-02-01T02:10:00 |
| mined | No |
| sbd_seconds | 0 |
| sbd_last_interest_payment | 2018-01-13T22:00:54 |
| savings_sbd_last_interest_payment | 1970-01-01T00:00:00 |
{
"active": {
"account_auths": [],
"key_auths": [
[
"STM8fVSCYFindrWfyACRBjd3e6LRthpKDG47KPrw6n3Tg2zK3yG21",
1
]
],
"weight_threshold": 1
},
"balance": "0.001 STEEM",
"can_vote": true,
"comment_count": 0,
"created": "2018-01-04T12:06:27",
"curation_rewards": 4,
"delegated_vesting_shares": "0.000000 VESTS",
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779061515
},
"guest_bloggers": [],
"id": 558356,
"json_metadata": "{\"profile\":{\"profile_image\":\"https://s14.postimg.org/a0f3n4az5/Dust_Voice.png\",\"cover_image\":\"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500\",\"name\":\"DustVoice\",\"website\":\"http://dustvoice.de\"}}",
"last_account_recovery": "1970-01-01T00:00:00",
"last_account_update": "2021-02-01T02:10:00",
"last_owner_update": "1970-01-01T00:00:00",
"last_post": "2018-01-06T14:03:00",
"last_root_post": "2018-01-06T04:02:15",
"last_vote_time": "2018-01-13T22:02:54",
"lifetime_vote_count": 0,
"market_history": [],
"memo_key": "STM69fmSxgcza1TYkcSgtWYPvnKvuaWoDBovN7bq7vvyHYCubJ2z4",
"mined": false,
"name": "dustvoice",
"next_vesting_withdrawal": "1969-12-31T23:59:59",
"other_history": [],
"owner": {
"account_auths": [],
"key_auths": [
[
"STM73fjp31DVRWU9tynHwLsQYYFqBfsB76iEdpJSFhSJsVTuKab49",
1
]
],
"weight_threshold": 1
},
"pending_claimed_accounts": 0,
"post_bandwidth": 0,
"post_count": 20,
"post_history": [],
"posting": {
"account_auths": [
[
"dmania.app",
1
]
],
"key_auths": [
[
"STM5XZRiDa7pe2QwkytkGamYZ1TtzBgKxDoWeDjVzP1oySuD9gLrK",
1
]
],
"weight_threshold": 1
},
"posting_json_metadata": "{\"profile\":{\"profile_image\":\"https://cdn.steemitimages.com/DQmYdjT45r1aPnK4Cigvc5LK6Wjo1SDuhGS8LUb8YfzvwSa/crop_image.jpg\",\"cover_image\":\"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500\",\"name\":\"DustVoice\",\"website\":\"https://dustvoice.de\",\"version\":2}}",
"posting_rewards": 1132,
"proxied_vsf_votes": [
0,
0,
0,
0
],
"proxy": "",
"received_vesting_shares": "5951.905409 VESTS",
"recovery_account": "steem",
"reputation": "12601023545",
"reset_account": "null",
"reward_sbd_balance": "0.000 SBD",
"reward_steem_balance": "0.000 STEEM",
"reward_vesting_balance": "0.000000 VESTS",
"reward_vesting_steem": "0.000 STEEM",
"savings_balance": "0.000 STEEM",
"savings_sbd_balance": "0.000 SBD",
"savings_sbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_sbd_seconds": "0",
"savings_sbd_seconds_last_update": "1970-01-01T00:00:00",
"savings_withdraw_requests": 0,
"sbd_balance": "2.962 SBD",
"sbd_last_interest_payment": "2018-01-13T22:00:54",
"sbd_seconds": "0",
"sbd_seconds_last_update": "2018-01-13T22:00:54",
"tags_usage": [],
"to_withdraw": 0,
"transfer_history": [],
"vesting_balance": "0.000 STEEM",
"vesting_shares": "2191.754397 VESTS",
"vesting_withdraw_rate": "0.000000 VESTS",
"vote_history": [],
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779061515
},
"voting_power": 0,
"withdraw_routes": 0,
"withdrawn": 0,
"witness_votes": [],
"witnesses_voted_for": 0,
"rank": 730467
}Withdraw Routes
| Incoming | Outgoing |
|---|---|
Empty | Empty |
{
"incoming": [],
"outgoing": []
}From Date
To Date
steemdelegated 3.659 SP to @dustvoice2026/05/17 23:45:15
steemdelegated 3.659 SP to @dustvoice
2026/05/17 23:45:15
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 5951.905409 VESTS |
| Transaction Info | Block #106142852/Trx e6f53cdcad6281a6a5342f553a33f164af944800 |
View Raw JSON Data
{
"block": 106142852,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "5951.905409 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-05-17T23:45:15",
"trx_id": "e6f53cdcad6281a6a5342f553a33f164af944800",
"trx_in_block": 0,
"virtual_op": 0
}steemdelegated 1.992 SP to @dustvoice2026/05/12 01:48:39
steemdelegated 1.992 SP to @dustvoice
2026/05/12 01:48:39
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 3239.695004 VESTS |
| Transaction Info | Block #105973277/Trx b07295ca5d8895d56be8e8802a08056bfdb91162 |
View Raw JSON Data
{
"block": 105973277,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "3239.695004 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-05-12T01:48:39",
"trx_id": "b07295ca5d8895d56be8e8802a08056bfdb91162",
"trx_in_block": 2,
"virtual_op": 0
}steemdelegated 3.667 SP to @dustvoice2026/04/25 23:06:57
steemdelegated 3.667 SP to @dustvoice
2026/04/25 23:06:57
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 5964.421165 VESTS |
| Transaction Info | Block #105510517/Trx 3fcaf3de454891f62921b90c13959ffc36f02200 |
View Raw JSON Data
{
"block": 105510517,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "5964.421165 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-04-25T23:06:57",
"trx_id": "3fcaf3de454891f62921b90c13959ffc36f02200",
"trx_in_block": 2,
"virtual_op": 0
}steemdelegated 2.017 SP to @dustvoice2026/01/23 06:26:54
steemdelegated 2.017 SP to @dustvoice
2026/01/23 06:26:54
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 3281.241823 VESTS |
| Transaction Info | Block #102850187/Trx c6c938104d6ea8fc3a179e8cda47d31c776ac8c8 |
View Raw JSON Data
{
"block": 102850187,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "3281.241823 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2026-01-23T06:26:54",
"trx_id": "c6c938104d6ea8fc3a179e8cda47d31c776ac8c8",
"trx_in_block": 0,
"virtual_op": 0
}steemdelegated 2.118 SP to @dustvoice2024/12/17 01:46:27
steemdelegated 2.118 SP to @dustvoice
2024/12/17 01:46:27
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 3445.461020 VESTS |
| Transaction Info | Block #91296607/Trx 4f9884de465d692095b67ee5d6c793d468b3688f |
View Raw JSON Data
{
"block": 91296607,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "3445.461020 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2024-12-17T01:46:27",
"trx_id": "4f9884de465d692095b67ee5d6c793d468b3688f",
"trx_in_block": 9,
"virtual_op": 0
}steemdelegated 2.222 SP to @dustvoice2023/11/13 17:29:21
steemdelegated 2.222 SP to @dustvoice
2023/11/13 17:29:21
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 3614.594552 VESTS |
| Transaction Info | Block #79850814/Trx 0117fdaf0a9dc98b98de6de42af5bb262298088c |
View Raw JSON Data
{
"block": 79850814,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "3614.594552 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2023-11-13T17:29:21",
"trx_id": "0117fdaf0a9dc98b98de6de42af5bb262298088c",
"trx_in_block": 9,
"virtual_op": 0
}steemdelegated 4.028 SP to @dustvoice2023/09/21 21:14:12
steemdelegated 4.028 SP to @dustvoice
2023/09/21 21:14:12
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 6551.873338 VESTS |
| Transaction Info | Block #78347117/Trx 3679a1e8fdccd462ca8077dab0cb68c575386254 |
View Raw JSON Data
{
"block": 78347117,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "6551.873338 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2023-09-21T21:14:12",
"trx_id": "3679a1e8fdccd462ca8077dab0cb68c575386254",
"trx_in_block": 1,
"virtual_op": 0
}steemdelegated 4.164 SP to @dustvoice2022/11/03 11:06:42
steemdelegated 4.164 SP to @dustvoice
2022/11/03 11:06:42
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 6773.554776 VESTS |
| Transaction Info | Block #69112556/Trx fadca8e11a560c68a258d2f3e7b555e3b5d91358 |
View Raw JSON Data
{
"block": 69112556,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "6773.554776 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2022-11-03T11:06:42",
"trx_id": "fadca8e11a560c68a258d2f3e7b555e3b5d91358",
"trx_in_block": 2,
"virtual_op": 0
}steemdelegated 4.300 SP to @dustvoice2022/01/17 10:25:30
steemdelegated 4.300 SP to @dustvoice
2022/01/17 10:25:30
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 6994.088007 VESTS |
| Transaction Info | Block #60808779/Trx 39df0674bfdceeca8450580a198f28d90dfa57c3 |
View Raw JSON Data
{
"block": 60808779,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "6994.088007 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2022-01-17T10:25:30",
"trx_id": "39df0674bfdceeca8450580a198f28d90dfa57c3",
"trx_in_block": 17,
"virtual_op": 0
}steemdelegated 4.413 SP to @dustvoice2021/06/14 00:22:03
steemdelegated 4.413 SP to @dustvoice
2021/06/14 00:22:03
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7177.856665 VESTS |
| Transaction Info | Block #54607192/Trx 6e0f374a21b04e0f2226ae962d7be57d83b26682 |
View Raw JSON Data
{
"block": 54607192,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7177.856665 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2021-06-14T00:22:03",
"trx_id": "6e0f374a21b04e0f2226ae962d7be57d83b26682",
"trx_in_block": 1,
"virtual_op": 0
}dustvoicecustom json: notify2021/02/01 02:12:51
dustvoicecustom json: notify
2021/02/01 02:12:51
| id | notify |
| json | ["setLastRead",{"date":"2021-02-01T02:12:47"}] |
| required auths | [] |
| required posting auths | ["dustvoice"] |
| Transaction Info | Block #50823756/Trx fa34491940b0f3a70fe324bc532092240d686b5b |
View Raw JSON Data
{
"block": 50823756,
"op": [
"custom_json",
{
"id": "notify",
"json": "[\"setLastRead\",{\"date\":\"2021-02-01T02:12:47\"}]",
"required_auths": [],
"required_posting_auths": [
"dustvoice"
]
}
],
"op_in_trx": 0,
"timestamp": "2021-02-01T02:12:51",
"trx_id": "fa34491940b0f3a70fe324bc532092240d686b5b",
"trx_in_block": 5,
"virtual_op": 0
}dustvoiceupdated their account properties2021/02/01 02:10:00
dustvoiceupdated their account properties
2021/02/01 02:10:00
| account | dustvoice |
| extensions | [] |
| json metadata | |
| posting json metadata | {"profile":{"profile_image":"https://cdn.steemitimages.com/DQmYdjT45r1aPnK4Cigvc5LK6Wjo1SDuhGS8LUb8YfzvwSa/crop_image.jpg","cover_image":"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500","name":"DustVoice","website":"https://dustvoice.de","version":2}} |
| Transaction Info | Block #50823699/Trx 81b49d6c706614abf1f83056c39381205e8a97e0 |
View Raw JSON Data
{
"block": 50823699,
"op": [
"account_update2",
{
"account": "dustvoice",
"extensions": [],
"json_metadata": "",
"posting_json_metadata": "{\"profile\":{\"profile_image\":\"https://cdn.steemitimages.com/DQmYdjT45r1aPnK4Cigvc5LK6Wjo1SDuhGS8LUb8YfzvwSa/crop_image.jpg\",\"cover_image\":\"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500\",\"name\":\"DustVoice\",\"website\":\"https://dustvoice.de\",\"version\":2}}"
}
],
"op_in_trx": 0,
"timestamp": "2021-02-01T02:10:00",
"trx_id": "81b49d6c706614abf1f83056c39381205e8a97e0",
"trx_in_block": 5,
"virtual_op": 0
}steemdelegated 4.528 SP to @dustvoice2020/12/11 10:41:54
steemdelegated 4.528 SP to @dustvoice
2020/12/11 10:41:54
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7365.278639 VESTS |
| Transaction Info | Block #49354678/Trx 19e081b2eeb081f8520c8ac67aad1b1c6ad19645 |
View Raw JSON Data
{
"block": 49354678,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7365.278639 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-11T10:41:54",
"trx_id": "19e081b2eeb081f8520c8ac67aad1b1c6ad19645",
"trx_in_block": 1,
"virtual_op": 0
}steemdelegated 1.176 SP to @dustvoice2020/12/06 04:19:12
steemdelegated 1.176 SP to @dustvoice
2020/12/06 04:19:12
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 1912.543513 VESTS |
| Transaction Info | Block #49206243/Trx ff43e619c63f899cea75c896507968a2b491e144 |
View Raw JSON Data
{
"block": 49206243,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "1912.543513 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-06T04:19:12",
"trx_id": "ff43e619c63f899cea75c896507968a2b491e144",
"trx_in_block": 5,
"virtual_op": 0
}steemdelegated 4.532 SP to @dustvoice2020/12/05 14:20:06
steemdelegated 4.532 SP to @dustvoice
2020/12/05 14:20:06
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7371.486493 VESTS |
| Transaction Info | Block #49189775/Trx dae8708cffd94888576bfd0defd90ace70301ee2 |
View Raw JSON Data
{
"block": 49189775,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7371.486493 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-12-05T14:20:06",
"trx_id": "dae8708cffd94888576bfd0defd90ace70301ee2",
"trx_in_block": 6,
"virtual_op": 0
}steemdelegated 1.180 SP to @dustvoice2020/11/02 14:39:42
steemdelegated 1.180 SP to @dustvoice
2020/11/02 14:39:42
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 1920.017158 VESTS |
| Transaction Info | Block #48256647/Trx 5491cee36dd8af5b316876d6f1fe0ed736fdd0d4 |
View Raw JSON Data
{
"block": 48256647,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "1920.017158 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-11-02T14:39:42",
"trx_id": "5491cee36dd8af5b316876d6f1fe0ed736fdd0d4",
"trx_in_block": 7,
"virtual_op": 0
}steemdelegated 4.657 SP to @dustvoice2020/05/09 05:15:51
steemdelegated 4.657 SP to @dustvoice
2020/05/09 05:15:51
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7574.291852 VESTS |
| Transaction Info | Block #43216479/Trx c02f6a696c6c9694a0fff9eb30009589cc07847f |
View Raw JSON Data
{
"block": 43216479,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7574.291852 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-05-09T05:15:51",
"trx_id": "c02f6a696c6c9694a0fff9eb30009589cc07847f",
"trx_in_block": 1,
"virtual_op": 0
}steemdelegated 1.201 SP to @dustvoice2020/05/08 08:47:36
steemdelegated 1.201 SP to @dustvoice
2020/05/08 08:47:36
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 1953.311140 VESTS |
| Transaction Info | Block #43192490/Trx 60bdaca8aef0310b247d4c2242390e662c80ac21 |
View Raw JSON Data
{
"block": 43192490,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "1953.311140 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-05-08T08:47:36",
"trx_id": "60bdaca8aef0310b247d4c2242390e662c80ac21",
"trx_in_block": 6,
"virtual_op": 0
}steemdelegated 4.665 SP to @dustvoice2020/04/15 21:19:27
steemdelegated 4.665 SP to @dustvoice
2020/04/15 21:19:27
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7587.269271 VESTS |
| Transaction Info | Block #42562220/Trx caaae21e5874fd5d01f175161f0533c5326fce08 |
View Raw JSON Data
{
"block": 42562220,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7587.269271 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2020-04-15T21:19:27",
"trx_id": "caaae21e5874fd5d01f175161f0533c5326fce08",
"trx_in_block": 22,
"virtual_op": 0
}2020/01/04 13:51:36
2020/01/04 13:51:36
| author | steemitboard |
| body | Congratulations @dustvoice! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@dustvoice/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table> <sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@dustvoice) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=dustvoice)_</sub> ###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes! |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | dustvoice |
| parent permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| permlink | steemitboard-notify-dustvoice-20200104t135136000z |
| title | |
| Transaction Info | Block #39635180/Trx 9a7b3433e3362d5bf603c7a04465fb82fdd48c39 |
View Raw JSON Data
{
"block": 39635180,
"op": [
"comment",
{
"author": "steemitboard",
"body": "Congratulations @dustvoice! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@dustvoice/birthday2.png</td><td>Happy Birthday! - You are on the Steem blockchain for 2 years!</td></tr></table>\n\n<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@dustvoice) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=dustvoice)_</sub>\n\n\n###### [Vote for @Steemitboard as a witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1) to get one more award and increased upvotes!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "dustvoice",
"parent_permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"permlink": "steemitboard-notify-dustvoice-20200104t135136000z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2020-01-04T13:51:36",
"trx_id": "9a7b3433e3362d5bf603c7a04465fb82fdd48c39",
"trx_in_block": 8,
"virtual_op": 0
}dtubesent 0.001 STEEM to @dustvoice- "Time is running out, claim your DTube account now before anyone else can! Login at https://d.tube"2019/08/22 17:59:30
dtubesent 0.001 STEEM to @dustvoice- "Time is running out, claim your DTube account now before anyone else can! Login at https://d.tube"
2019/08/22 17:59:30
| amount | 0.001 STEEM |
| from | dtube |
| memo | Time is running out, claim your DTube account now before anyone else can! Login at https://d.tube |
| to | dustvoice |
| Transaction Info | Block #35781598/Trx c6f647834c61455c82daf631a85ad5f590517480 |
View Raw JSON Data
{
"block": 35781598,
"op": [
"transfer",
{
"amount": "0.001 STEEM",
"from": "dtube",
"memo": "Time is running out, claim your DTube account now before anyone else can! Login at https://d.tube",
"to": "dustvoice"
}
],
"op_in_trx": 0,
"timestamp": "2019-08-22T17:59:30",
"trx_id": "c6f647834c61455c82daf631a85ad5f590517480",
"trx_in_block": 11,
"virtual_op": 0
}steemdelegated 4.785 SP to @dustvoice2019/05/12 14:34:18
steemdelegated 4.785 SP to @dustvoice
2019/05/12 14:34:18
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7782.892076 VESTS |
| Transaction Info | Block #32845089/Trx 874bcdc926dd30fab093d87649cf57104f1c10b7 |
View Raw JSON Data
{
"block": 32845089,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7782.892076 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2019-05-12T14:34:18",
"trx_id": "874bcdc926dd30fab093d87649cf57104f1c10b7",
"trx_in_block": 6,
"virtual_op": 0
}2019/01/04 13:53:45
2019/01/04 13:53:45
| author | steemitboard |
| body | Congratulations @dustvoice! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@dustvoice/birthday1.png</td><td>1 Year on Steemit</td></tr></table> <sub>_[Click here to view your Board](https://steemitboard.com/@dustvoice)_</sub> > Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**! |
| json metadata | {"image":["https://steemitboard.com/img/notify.png"]} |
| parent author | dustvoice |
| parent permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| permlink | steemitboard-notify-dustvoice-20190104t135344000z |
| title | |
| Transaction Info | Block #29162119/Trx a87ffaf0266e58521a28820ac7c18a60a37a56ca |
View Raw JSON Data
{
"block": 29162119,
"op": [
"comment",
{
"author": "steemitboard",
"body": "Congratulations @dustvoice! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@dustvoice/birthday1.png</td><td>1 Year on Steemit</td></tr></table>\n\n<sub>_[Click here to view your Board](https://steemitboard.com/@dustvoice)_</sub>\n\n\n> Support [SteemitBoard's project](https://steemit.com/@steemitboard)! **[Vote for its witness](https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1)** and **get one more award**!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notify.png\"]}",
"parent_author": "dustvoice",
"parent_permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"permlink": "steemitboard-notify-dustvoice-20190104t135344000z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2019-01-04T13:53:45",
"trx_id": "a87ffaf0266e58521a28820ac7c18a60a37a56ca",
"trx_in_block": 5,
"virtual_op": 0
}steemdelegated 4.907 SP to @dustvoice2018/05/16 20:15:12
steemdelegated 4.907 SP to @dustvoice
2018/05/16 20:15:12
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 7982.444511 VESTS |
| Transaction Info | Block #22489805/Trx 0519b80efe28d5b3ba83c5e8497bcbab8f6d4e82 |
View Raw JSON Data
{
"block": 22489805,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "7982.444511 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-05-16T20:15:12",
"trx_id": "0519b80efe28d5b3ba83c5e8497bcbab8f6d4e82",
"trx_in_block": 16,
"virtual_op": 0
}2018/02/17 11:06:30
2018/02/17 11:06:30
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | onurgule |
| weight | 10000 (100.00%) |
| Transaction Info | Block #19946996/Trx e74ed24ce26bfc7c977e73cd79cd637e8655e660 |
View Raw JSON Data
{
"block": 19946996,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "onurgule",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-02-17T11:06:30",
"trx_id": "e74ed24ce26bfc7c977e73cd79cd637e8655e660",
"trx_in_block": 34,
"virtual_op": 0
}steemdelegated 17.538 SP to @dustvoice2018/01/13 23:45:42
steemdelegated 17.538 SP to @dustvoice
2018/01/13 23:45:42
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 28526.330483 VESTS |
| Transaction Info | Block #18955189/Trx 810fa5d2198648ffbc8fa5ce152bb27f5e6f2d6c |
View Raw JSON Data
{
"block": 18955189,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "28526.330483 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T23:45:42",
"trx_id": "810fa5d2198648ffbc8fa5ce152bb27f5e6f2d6c",
"trx_in_block": 74,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @resteemator / resteemator-reached-500-followers2018/01/13 22:02:54
dustvoiceupvoted (100.00%) @resteemator / resteemator-reached-500-followers
2018/01/13 22:02:54
| author | resteemator |
| permlink | resteemator-reached-500-followers |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18953133/Trx ed4edcfe0c5b883b2442a051c45611accafd3edc |
View Raw JSON Data
{
"block": 18953133,
"op": [
"vote",
{
"author": "resteemator",
"permlink": "resteemator-reached-500-followers",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T22:02:54",
"trx_id": "ed4edcfe0c5b883b2442a051c45611accafd3edc",
"trx_in_block": 53,
"virtual_op": 0
}dustvoicefollowed @resteemator2018/01/13 22:02:51
dustvoicefollowed @resteemator
2018/01/13 22:02:51
| id | follow |
| json | ["follow",{"follower":"dustvoice","following":"resteemator","what":["blog"]}] |
| required auths | [] |
| required posting auths | ["dustvoice"] |
| Transaction Info | Block #18953132/Trx 0fce0701cb53cb765daee9c053bea79a8aaf5b5b |
View Raw JSON Data
{
"block": 18953132,
"op": [
"custom_json",
{
"id": "follow",
"json": "[\"follow\",{\"follower\":\"dustvoice\",\"following\":\"resteemator\",\"what\":[\"blog\"]}]",
"required_auths": [],
"required_posting_auths": [
"dustvoice"
]
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T22:02:51",
"trx_id": "0fce0701cb53cb765daee9c053bea79a8aaf5b5b",
"trx_in_block": 47,
"virtual_op": 0
}2018/01/13 22:02:30
2018/01/13 22:02:30
| author | dustvoice |
| permlink | re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18953125/Trx fb2fff2ceded00f53b06c8e3753ff3c258767c94 |
View Raw JSON Data
{
"block": 18953125,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T22:02:30",
"trx_id": "fb2fff2ceded00f53b06c8e3753ff3c258767c94",
"trx_in_block": 20,
"virtual_op": 0
}dustvoiceclaimed reward balance: 2.886 SBD, 0.696 SP2018/01/13 22:00:54
dustvoiceclaimed reward balance: 2.886 SBD, 0.696 SP
2018/01/13 22:00:54
| account | dustvoice |
| reward sbd | 2.886 SBD |
| reward steem | 0.000 STEEM |
| reward vests | 1132.522840 VESTS |
| Transaction Info | Block #18953093/Trx f251ab9f502aee28fef5476460ec88bf8955e500 |
View Raw JSON Data
{
"block": 18953093,
"op": [
"claim_reward_balance",
{
"account": "dustvoice",
"reward_sbd": "2.886 SBD",
"reward_steem": "0.000 STEEM",
"reward_vests": "1132.522840 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T22:00:54",
"trx_id": "f251ab9f502aee28fef5476460ec88bf8955e500",
"trx_in_block": 41,
"virtual_op": 0
}dustvoicereceived 2.886 SBD, 0.696 SP author reward for @dustvoice / create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required2018/01/13 04:02:15
dustvoicereceived 2.886 SBD, 0.696 SP author reward for @dustvoice / create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required
2018/01/13 04:02:15
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| sbd payout | 2.886 SBD |
| steem payout | 0.000 STEEM |
| vesting payout | 1132.522840 VESTS |
| Transaction Info | Block #18931552/Virtual Operation #41 |
View Raw JSON Data
{
"block": 18931552,
"op": [
"author_reward",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"sbd_payout": "2.886 SBD",
"steem_payout": "0.000 STEEM",
"vesting_payout": "1132.522840 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T04:02:15",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 41
}dustvoiceclaimed reward balance: 0.076 SBD, 0.021 SP2018/01/13 00:00:09
dustvoiceclaimed reward balance: 0.076 SBD, 0.021 SP
2018/01/13 00:00:09
| account | dustvoice |
| reward sbd | 0.076 SBD |
| reward steem | 0.000 STEEM |
| reward vests | 34.818087 VESTS |
| Transaction Info | Block #18926719/Trx eacee19de396a66f17af5ba262310a57c10ad536 |
View Raw JSON Data
{
"block": 18926719,
"op": [
"claim_reward_balance",
{
"account": "dustvoice",
"reward_sbd": "0.076 SBD",
"reward_steem": "0.000 STEEM",
"reward_vests": "34.818087 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-13T00:00:09",
"trx_id": "eacee19de396a66f17af5ba262310a57c10ad536",
"trx_in_block": 3,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @cephalopod / 45-kb2018/01/12 23:58:39
dustvoiceupvoted (100.00%) @cephalopod / 45-kb
2018/01/12 23:58:39
| author | cephalopod |
| permlink | 45-kb |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926689/Trx e1d4e04e7c6e9701cd87ec7262232dd7cece3dc4 |
View Raw JSON Data
{
"block": 18926689,
"op": [
"vote",
{
"author": "cephalopod",
"permlink": "45-kb",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:58:39",
"trx_id": "e1d4e04e7c6e9701cd87ec7262232dd7cece3dc4",
"trx_in_block": 18,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @planet-rium / city-wanders-series-watercolour-made-by-me-planet-rium2018/01/12 23:58:36
dustvoiceupvoted (100.00%) @planet-rium / city-wanders-series-watercolour-made-by-me-planet-rium
2018/01/12 23:58:36
| author | planet-rium |
| permlink | city-wanders-series-watercolour-made-by-me-planet-rium |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926688/Trx 88e65a42c0b2f5f86768f63737f77d4535e62eb8 |
View Raw JSON Data
{
"block": 18926688,
"op": [
"vote",
{
"author": "planet-rium",
"permlink": "city-wanders-series-watercolour-made-by-me-planet-rium",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:58:36",
"trx_id": "88e65a42c0b2f5f86768f63737f77d4535e62eb8",
"trx_in_block": 26,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @onyemacourage / egghsej52018/01/12 23:57:57
dustvoiceupvoted (100.00%) @onyemacourage / egghsej5
2018/01/12 23:57:57
| author | onyemacourage |
| permlink | egghsej5 |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926675/Trx dda7ce91183102744d9f5204fa732a8b8733dbab |
View Raw JSON Data
{
"block": 18926675,
"op": [
"vote",
{
"author": "onyemacourage",
"permlink": "egghsej5",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:57",
"trx_id": "dda7ce91183102744d9f5204fa732a8b8733dbab",
"trx_in_block": 17,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @theaustrianguy / gnejloye2018/01/12 23:57:51
dustvoiceupvoted (100.00%) @theaustrianguy / gnejloye
2018/01/12 23:57:51
| author | theaustrianguy |
| permlink | gnejloye |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926673/Trx 992423ad31dd0606b32e7f6d5ba39604df6111f7 |
View Raw JSON Data
{
"block": 18926673,
"op": [
"vote",
{
"author": "theaustrianguy",
"permlink": "gnejloye",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:51",
"trx_id": "992423ad31dd0606b32e7f6d5ba39604df6111f7",
"trx_in_block": 5,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @nashbe / steem-and-steem-dollars-midnight-update2018/01/12 23:57:48
dustvoiceupvoted (100.00%) @nashbe / steem-and-steem-dollars-midnight-update
2018/01/12 23:57:48
| author | nashbe |
| permlink | steem-and-steem-dollars-midnight-update |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926672/Trx f3873725acbb6aba527409bf00b1eee586788727 |
View Raw JSON Data
{
"block": 18926672,
"op": [
"vote",
{
"author": "nashbe",
"permlink": "steem-and-steem-dollars-midnight-update",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:48",
"trx_id": "f3873725acbb6aba527409bf00b1eee586788727",
"trx_in_block": 19,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @dhruv120511 / what-will-earth-and-humans-look-like-in-1-000-years2018/01/12 23:57:45
dustvoiceupvoted (100.00%) @dhruv120511 / what-will-earth-and-humans-look-like-in-1-000-years
2018/01/12 23:57:45
| author | dhruv120511 |
| permlink | what-will-earth-and-humans-look-like-in-1-000-years |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926671/Trx 4c6a6f8d319279e73ee48c8d776f90b14242553c |
View Raw JSON Data
{
"block": 18926671,
"op": [
"vote",
{
"author": "dhruv120511",
"permlink": "what-will-earth-and-humans-look-like-in-1-000-years",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:45",
"trx_id": "4c6a6f8d319279e73ee48c8d776f90b14242553c",
"trx_in_block": 33,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @brandt / one-day-black-and-white-challenge-day-12018/01/12 23:57:39
dustvoiceupvoted (100.00%) @brandt / one-day-black-and-white-challenge-day-1
2018/01/12 23:57:39
| author | brandt |
| permlink | one-day-black-and-white-challenge-day-1 |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926669/Trx a2daaa3c277ee2d81189c8db7a99c986573e489d |
View Raw JSON Data
{
"block": 18926669,
"op": [
"vote",
{
"author": "brandt",
"permlink": "one-day-black-and-white-challenge-day-1",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:39",
"trx_id": "a2daaa3c277ee2d81189c8db7a99c986573e489d",
"trx_in_block": 32,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @leohira123 / daily-landscape-shot-the-beautiful-crops-c467d37c5ef52018/01/12 23:57:36
dustvoiceupvoted (100.00%) @leohira123 / daily-landscape-shot-the-beautiful-crops-c467d37c5ef5
2018/01/12 23:57:36
| author | leohira123 |
| permlink | daily-landscape-shot-the-beautiful-crops-c467d37c5ef5 |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926668/Trx 973e901ca1a0afb04bb907b6d974de6cc74be5b1 |
View Raw JSON Data
{
"block": 18926668,
"op": [
"vote",
{
"author": "leohira123",
"permlink": "daily-landscape-shot-the-beautiful-crops-c467d37c5ef5",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:36",
"trx_id": "973e901ca1a0afb04bb907b6d974de6cc74be5b1",
"trx_in_block": 49,
"virtual_op": 0
}2018/01/12 23:57:33
2018/01/12 23:57:33
| author | ledjo1991 |
| permlink | cryptocurrency-exchanges-need-smarter-faster-and-more-secure-id-verification-with-velix-id |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926667/Trx 53397db0f729eda5f581465891a756d3af574a7d |
View Raw JSON Data
{
"block": 18926667,
"op": [
"vote",
{
"author": "ledjo1991",
"permlink": "cryptocurrency-exchanges-need-smarter-faster-and-more-secure-id-verification-with-velix-id",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:33",
"trx_id": "53397db0f729eda5f581465891a756d3af574a7d",
"trx_in_block": 26,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @evernoticethat / a-thank-you-message-to-my-500-amazing-followers2018/01/12 23:57:30
dustvoiceupvoted (100.00%) @evernoticethat / a-thank-you-message-to-my-500-amazing-followers
2018/01/12 23:57:30
| author | evernoticethat |
| permlink | a-thank-you-message-to-my-500-amazing-followers |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18926666/Trx 95beb29eb10bb4129f1f6ea3853da8aa9049db74 |
View Raw JSON Data
{
"block": 18926666,
"op": [
"vote",
{
"author": "evernoticethat",
"permlink": "a-thank-you-message-to-my-500-amazing-followers",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T23:57:30",
"trx_id": "95beb29eb10bb4129f1f6ea3853da8aa9049db74",
"trx_in_block": 17,
"virtual_op": 0
}wbjadventuresreplied to @dustvoice / y6u2mff2l2018/01/12 02:51:30
wbjadventuresreplied to @dustvoice / y6u2mff2l
2018/01/12 02:51:30
| author | wbjadventures |
| body | Really nice |
| json metadata | {"app":"dtube/0.6"} |
| parent author | dustvoice |
| parent permlink | h8p9o8ul |
| permlink | y6u2mff2l |
| title | y6u2mff2l |
| Transaction Info | Block #18901399/Trx c0e05cc35f6b80bca690ddb00f7ca2adf179547c |
View Raw JSON Data
{
"block": 18901399,
"op": [
"comment",
{
"author": "wbjadventures",
"body": "Really nice ",
"json_metadata": "{\"app\":\"dtube/0.6\"}",
"parent_author": "dustvoice",
"parent_permlink": "h8p9o8ul",
"permlink": "y6u2mff2l",
"title": "y6u2mff2l"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-12T02:51:30",
"trx_id": "c0e05cc35f6b80bca690ddb00f7ca2adf179547c",
"trx_in_block": 5,
"virtual_op": 0
}framegamesupvoted (100.00%) @dustvoice / h8p9o8ul2018/01/11 22:48:21
framegamesupvoted (100.00%) @dustvoice / h8p9o8ul
2018/01/11 22:48:21
| author | dustvoice |
| permlink | h8p9o8ul |
| voter | framegames |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18896538/Trx e6f03066eb5be5e074089070ef83bf63209b9639 |
View Raw JSON Data
{
"block": 18896538,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "h8p9o8ul",
"voter": "framegames",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T22:48:21",
"trx_id": "e6f03066eb5be5e074089070ef83bf63209b9639",
"trx_in_block": 34,
"virtual_op": 0
}abdulrehman8833upvoted (100.00%) @dustvoice / h8p9o8ul2018/01/11 16:10:45
abdulrehman8833upvoted (100.00%) @dustvoice / h8p9o8ul
2018/01/11 16:10:45
| author | dustvoice |
| permlink | h8p9o8ul |
| voter | abdulrehman8833 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18888587/Trx 04812bd80ca73dd574f00b0602a61d69cca6cf59 |
View Raw JSON Data
{
"block": 18888587,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "h8p9o8ul",
"voter": "abdulrehman8833",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T16:10:45",
"trx_id": "04812bd80ca73dd574f00b0602a61d69cca6cf59",
"trx_in_block": 47,
"virtual_op": 0
}abdulrehman8833upvoted (100.00%) @dustvoice / h8p9o8ul2018/01/11 16:10:45
abdulrehman8833upvoted (100.00%) @dustvoice / h8p9o8ul
2018/01/11 16:10:45
| author | dustvoice |
| permlink | h8p9o8ul |
| voter | abdulrehman8833 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18888587/Trx 5c8b836ec9151c58a024648d5bba80e0c0499a2a |
View Raw JSON Data
{
"block": 18888587,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "h8p9o8ul",
"voter": "abdulrehman8833",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T16:10:45",
"trx_id": "5c8b836ec9151c58a024648d5bba80e0c0499a2a",
"trx_in_block": 32,
"virtual_op": 0
}stopyoutubereplied to @dustvoice / y6i4o69ri2018/01/11 15:35:18
stopyoutubereplied to @dustvoice / y6i4o69ri
2018/01/11 15:35:18
| author | stopyoutube |
| body | I gave you some lovin! How 'bout you give me some too? |
| json metadata | {"app":"dtube/0.6"} |
| parent author | dustvoice |
| parent permlink | h8p9o8ul |
| permlink | y6i4o69ri |
| title | y6i4o69ri |
| Transaction Info | Block #18887878/Trx 25c9d8ed284ad539592f3950198a3245f9f3a56a |
View Raw JSON Data
{
"block": 18887878,
"op": [
"comment",
{
"author": "stopyoutube",
"body": "I gave you some lovin! How 'bout you give me some too?",
"json_metadata": "{\"app\":\"dtube/0.6\"}",
"parent_author": "dustvoice",
"parent_permlink": "h8p9o8ul",
"permlink": "y6i4o69ri",
"title": "y6i4o69ri"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T15:35:18",
"trx_id": "25c9d8ed284ad539592f3950198a3245f9f3a56a",
"trx_in_block": 52,
"virtual_op": 0
}dustvoicereceived 0.053 SBD, 0.011 SP author reward for @dustvoice / re-abhishek77-photography-201814t1942933z-20180104t133603927z2018/01/11 13:36:06
dustvoicereceived 0.053 SBD, 0.011 SP author reward for @dustvoice / re-abhishek77-photography-201814t1942933z-20180104t133603927z
2018/01/11 13:36:06
| author | dustvoice |
| permlink | re-abhishek77-photography-201814t1942933z-20180104t133603927z |
| sbd payout | 0.053 SBD |
| steem payout | 0.000 STEEM |
| vesting payout | 18.433100 VESTS |
| Transaction Info | Block #18885494/Virtual Operation #16 |
View Raw JSON Data
{
"block": 18885494,
"op": [
"author_reward",
{
"author": "dustvoice",
"permlink": "re-abhishek77-photography-201814t1942933z-20180104t133603927z",
"sbd_payout": "0.053 SBD",
"steem_payout": "0.000 STEEM",
"vesting_payout": "18.433100 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T13:36:06",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 16
}dustvoicereceived 0.023 SBD, 0.005 SP author reward for @dustvoice / re-givemeyoursteem-the-8-secrets-of-the-happiest-people-20180104t133051960z2018/01/11 13:30:54
dustvoicereceived 0.023 SBD, 0.005 SP author reward for @dustvoice / re-givemeyoursteem-the-8-secrets-of-the-happiest-people-20180104t133051960z
2018/01/11 13:30:54
| author | dustvoice |
| permlink | re-givemeyoursteem-the-8-secrets-of-the-happiest-people-20180104t133051960z |
| sbd payout | 0.023 SBD |
| steem payout | 0.000 STEEM |
| vesting payout | 8.192490 VESTS |
| Transaction Info | Block #18885390/Virtual Operation #13 |
View Raw JSON Data
{
"block": 18885390,
"op": [
"author_reward",
{
"author": "dustvoice",
"permlink": "re-givemeyoursteem-the-8-secrets-of-the-happiest-people-20180104t133051960z",
"sbd_payout": "0.023 SBD",
"steem_payout": "0.000 STEEM",
"vesting_payout": "8.192490 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T13:30:54",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 13
}dustvoicereceived 0.004 SP curation reward for @tamsguitar / handycam-photography-euro-trip-random-pics-in-an-around-london-part-i2018/01/11 13:11:51
dustvoicereceived 0.004 SP curation reward for @tamsguitar / handycam-photography-euro-trip-random-pics-in-an-around-london-part-i
2018/01/11 13:11:51
| comment author | tamsguitar |
| comment permlink | handycam-photography-euro-trip-random-pics-in-an-around-london-part-i |
| curator | dustvoice |
| reward | 6.144372 VESTS |
| Transaction Info | Block #18885009/Virtual Operation #16 |
View Raw JSON Data
{
"block": 18885009,
"op": [
"curation_reward",
{
"comment_author": "tamsguitar",
"comment_permlink": "handycam-photography-euro-trip-random-pics-in-an-around-london-part-i",
"curator": "dustvoice",
"reward": "6.144372 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T13:11:51",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 16
}dustvoicereceived 0.001 SP curation reward for @izweed / mistakes-were-made-zg1hbmlh-gftdt2018/01/11 12:56:30
dustvoicereceived 0.001 SP curation reward for @izweed / mistakes-were-made-zg1hbmlh-gftdt
2018/01/11 12:56:30
| comment author | izweed |
| comment permlink | mistakes-were-made-zg1hbmlh-gftdt |
| curator | dustvoice |
| reward | 2.048125 VESTS |
| Transaction Info | Block #18884702/Virtual Operation #25 |
View Raw JSON Data
{
"block": 18884702,
"op": [
"curation_reward",
{
"comment_author": "izweed",
"comment_permlink": "mistakes-were-made-zg1hbmlh-gftdt",
"curator": "dustvoice",
"reward": "2.048125 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-11T12:56:30",
"trx_id": "0000000000000000000000000000000000000000",
"trx_in_block": 4294967295,
"virtual_op": 25
}steemdelegated 18.259 SP to @dustvoice2018/01/08 19:33:09
steemdelegated 18.259 SP to @dustvoice
2018/01/08 19:33:09
| delegatee | dustvoice |
| delegator | steem |
| vesting shares | 29700.586530 VESTS |
| Transaction Info | Block #18806285/Trx d7d322078df3778ae70e1c6676da8f511c7830b7 |
View Raw JSON Data
{
"block": 18806285,
"op": [
"delegate_vesting_shares",
{
"delegatee": "dustvoice",
"delegator": "steem",
"vesting_shares": "29700.586530 VESTS"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-08T19:33:09",
"trx_id": "d7d322078df3778ae70e1c6676da8f511c7830b7",
"trx_in_block": 10,
"virtual_op": 0
}2018/01/08 04:45:06
2018/01/08 04:45:06
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | dana-edwards |
| weight | 8555 (85.55%) |
| Transaction Info | Block #18788562/Trx 0346fc94746fd922ef2548c1688634a6acc6eb80 |
View Raw JSON Data
{
"block": 18788562,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "dana-edwards",
"weight": 8555
}
],
"op_in_trx": 0,
"timestamp": "2018-01-08T04:45:06",
"trx_id": "0346fc94746fd922ef2548c1688634a6acc6eb80",
"trx_in_block": 5,
"virtual_op": 0
}2018/01/07 18:17:12
2018/01/07 18:17:12
| author | dhruv123 |
| permlink | what-steem-price-is-it-a-new-year-gift-do-you-here-a-new-site-just-like-steemit |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18776010/Trx d6725e941aeab5d2807ccc901b50a069d16f00f6 |
View Raw JSON Data
{
"block": 18776010,
"op": [
"vote",
{
"author": "dhruv123",
"permlink": "what-steem-price-is-it-a-new-year-gift-do-you-here-a-new-site-just-like-steemit",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:17:12",
"trx_id": "d6725e941aeab5d2807ccc901b50a069d16f00f6",
"trx_in_block": 0,
"virtual_op": 0
}2018/01/07 18:16:51
2018/01/07 18:16:51
| author | dobartim |
| permlink | competition-for-the-best-yoga-move-the-prize-is-10-sbd-7-days-day-7-the-registration-is-in-progress-last-call-24-hour |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18776003/Trx 6094bbef962839d39bcc75b248aff8de291de50f |
View Raw JSON Data
{
"block": 18776003,
"op": [
"vote",
{
"author": "dobartim",
"permlink": "competition-for-the-best-yoga-move-the-prize-is-10-sbd-7-days-day-7-the-registration-is-in-progress-last-call-24-hour",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:51",
"trx_id": "6094bbef962839d39bcc75b248aff8de291de50f",
"trx_in_block": 17,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @medicbtom / 64exjn-crypto-trades-for-the-day2018/01/07 18:16:45
dustvoiceupvoted (100.00%) @medicbtom / 64exjn-crypto-trades-for-the-day
2018/01/07 18:16:45
| author | medicbtom |
| permlink | 64exjn-crypto-trades-for-the-day |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18776001/Trx 7ab788a256704c044c413bf1698fd370e1570d03 |
View Raw JSON Data
{
"block": 18776001,
"op": [
"vote",
{
"author": "medicbtom",
"permlink": "64exjn-crypto-trades-for-the-day",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:45",
"trx_id": "7ab788a256704c044c413bf1698fd370e1570d03",
"trx_in_block": 0,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @alamin0263 / boys-will-understand-zg1hbmlh-9y01q2018/01/07 18:16:33
dustvoiceupvoted (100.00%) @alamin0263 / boys-will-understand-zg1hbmlh-9y01q
2018/01/07 18:16:33
| author | alamin0263 |
| permlink | boys-will-understand-zg1hbmlh-9y01q |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775997/Trx 762e2f87522c15a6c407145027f4e2e9e89d8528 |
View Raw JSON Data
{
"block": 18775997,
"op": [
"vote",
{
"author": "alamin0263",
"permlink": "boys-will-understand-zg1hbmlh-9y01q",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:33",
"trx_id": "762e2f87522c15a6c407145027f4e2e9e89d8528",
"trx_in_block": 7,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @yuriks2000 / 9lesriuc2018/01/07 18:16:30
dustvoiceupvoted (100.00%) @yuriks2000 / 9lesriuc
2018/01/07 18:16:30
| author | yuriks2000 |
| permlink | 9lesriuc |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775996/Trx c483f74e87a7ac010ebd1986dbe05bad2bbdaafe |
View Raw JSON Data
{
"block": 18775996,
"op": [
"vote",
{
"author": "yuriks2000",
"permlink": "9lesriuc",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:30",
"trx_id": "c483f74e87a7ac010ebd1986dbe05bad2bbdaafe",
"trx_in_block": 6,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @floprime / happy-new-year2018/01/07 18:16:24
dustvoiceupvoted (100.00%) @floprime / happy-new-year
2018/01/07 18:16:24
| author | floprime |
| permlink | happy-new-year |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775994/Trx c60d3a929128df5f708389d8d53400c168392924 |
View Raw JSON Data
{
"block": 18775994,
"op": [
"vote",
{
"author": "floprime",
"permlink": "happy-new-year",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:24",
"trx_id": "c60d3a929128df5f708389d8d53400c168392924",
"trx_in_block": 2,
"virtual_op": 0
}2018/01/07 18:16:15
2018/01/07 18:16:15
| author | dana-edwards |
| permlink | end-game-regulation-the-us-government-will-eventually-demand-require-having-a-license-to-use-cryptocurrency |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775991/Trx 945f05e550d12bffbd9b8d7eb54a5cd42aa0b788 |
View Raw JSON Data
{
"block": 18775991,
"op": [
"vote",
{
"author": "dana-edwards",
"permlink": "end-game-regulation-the-us-government-will-eventually-demand-require-having-a-license-to-use-cryptocurrency",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:15",
"trx_id": "945f05e550d12bffbd9b8d7eb54a5cd42aa0b788",
"trx_in_block": 49,
"virtual_op": 0
}dustvoiceupvoted (100.00%) @clanbwarclan / they-are-multiplying2018/01/07 18:16:09
dustvoiceupvoted (100.00%) @clanbwarclan / they-are-multiplying
2018/01/07 18:16:09
| author | clanbwarclan |
| permlink | they-are-multiplying |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775989/Trx a3f56661c57c81d7f5fbb3a855790424a073a358 |
View Raw JSON Data
{
"block": 18775989,
"op": [
"vote",
{
"author": "clanbwarclan",
"permlink": "they-are-multiplying",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:16:09",
"trx_id": "a3f56661c57c81d7f5fbb3a855790424a073a358",
"trx_in_block": 38,
"virtual_op": 0
}2018/01/07 18:15:36
2018/01/07 18:15:36
| author | jout |
| permlink | german-short-film-english-subtitles-about-people-taking-pictures-of-an-accident-with-a-cruel-twist |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775978/Trx 6f897f7e80791b8fd2ada3d92513ba62b51e596a |
View Raw JSON Data
{
"block": 18775978,
"op": [
"vote",
{
"author": "jout",
"permlink": "german-short-film-english-subtitles-about-people-taking-pictures-of-an-accident-with-a-cruel-twist",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:15:36",
"trx_id": "6f897f7e80791b8fd2ada3d92513ba62b51e596a",
"trx_in_block": 44,
"virtual_op": 0
}2018/01/07 18:15:18
2018/01/07 18:15:18
| author | vm2904 |
| permlink | how-to-have-fantastic-memory-part-1-original-wildlife-photographs-and-thoughts |
| voter | dustvoice |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18775972/Trx 544bb11a21a23632ba51e89004a1bdbb74fad94b |
View Raw JSON Data
{
"block": 18775972,
"op": [
"vote",
{
"author": "vm2904",
"permlink": "how-to-have-fantastic-memory-part-1-original-wildlife-photographs-and-thoughts",
"voter": "dustvoice",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-07T18:15:18",
"trx_id": "544bb11a21a23632ba51e89004a1bdbb74fad94b",
"trx_in_block": 44,
"virtual_op": 0
}2018/01/06 17:34:51
2018/01/06 17:34:51
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | udin06 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18746404/Trx 6058c4a457bce2bafc9dc3fba3421aaa97fb01da |
View Raw JSON Data
{
"block": 18746404,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "udin06",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T17:34:51",
"trx_id": "6058c4a457bce2bafc9dc3fba3421aaa97fb01da",
"trx_in_block": 70,
"virtual_op": 0
}2018/01/06 16:29:36
2018/01/06 16:29:36
| author | hursh |
| body | Done |
| json metadata | {"tags":["resteem"],"app":"steemit/0.1"} |
| parent author | dustvoice |
| parent permlink | re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z |
| permlink | re-dustvoice-re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t162900741z |
| title | |
| Transaction Info | Block #18745099/Trx 4109d2dc7433d359327ba602e3db0e2520adf628 |
View Raw JSON Data
{
"block": 18745099,
"op": [
"comment",
{
"author": "hursh",
"body": "Done",
"json_metadata": "{\"tags\":[\"resteem\"],\"app\":\"steemit/0.1\"}",
"parent_author": "dustvoice",
"parent_permlink": "re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z",
"permlink": "re-dustvoice-re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t162900741z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T16:29:36",
"trx_id": "4109d2dc7433d359327ba602e3db0e2520adf628",
"trx_in_block": 31,
"virtual_op": 0
}2018/01/06 14:43:51
2018/01/06 14:43:51
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | omrusman |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18742985/Trx fe1d4e2ca395163217c26db850d0484b728a2bd6 |
View Raw JSON Data
{
"block": 18742985,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "omrusman",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T14:43:51",
"trx_id": "fe1d4e2ca395163217c26db850d0484b728a2bd6",
"trx_in_block": 6,
"virtual_op": 0
}2018/01/06 14:08:57
2018/01/06 14:08:57
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | ubg |
| weight | 100 (1.00%) |
| Transaction Info | Block #18742288/Trx f44d23503800a21e2d4de4a3781a8cfbdb17d9a0 |
View Raw JSON Data
{
"block": 18742288,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "ubg",
"weight": 100
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T14:08:57",
"trx_id": "f44d23503800a21e2d4de4a3781a8cfbdb17d9a0",
"trx_in_block": 15,
"virtual_op": 0
}2018/01/06 14:08:54
2018/01/06 14:08:54
| author | dustvoice |
| body | # What exactly is a blockchain? - How does a blockchain work? - What is Bitcoin or STEEM exactly? - How does a blockchain make sure of its correctness - What is **mining** exactly? **These are only some questions, probably many of you are asking yourselves. I'm here to provide you with kind of an unusual answer. My goal is to help you undestand the whole system behind blockchains, by actually creating a small blockchain yourself!** With the rise of cryptocurrencies and decentralized systems like Bitcoin or STEEM, many people want to really undestand, what this is all about. I will show you how the system works, by coding a blockchain from scratch in python with you. The guide will be mainly for Windows, but I will also add comments regarding Linux. You don't know a whole of python, to follow this guide, but it is advised to know the most basic basics. # Get ready ## Setup Python and pip First off, we need to install Python 3.6+ along with pip, with which you can install python packages. - On **Linux**, you can install python, by simply typing `sudo apt-get install python3.6` into the terminal. If you encounter errors, please try `sudo apt-get update` and then try to execute the command again. If it still doesn't work, please search for installation instructions for your specific system on your preferred search engine. - On **Windows**, 1. first download the binary, that suits your system best, under the [official python download page for windows.](https://www.python.org/downloads/windows/). Make sure you grab at least the _version 3.6_ or higher. 2. Install the file you just downloaded. Make sure you check the option to install pip, when provided with an option to do so. If not, don't worry, it probably is enabled by default. ## IDE or not? As a next step, you want to decide, whether to utilize an [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) or not. An IDE is an environment, which supports you in the creation process of your application, by providing many features, with for example syntax highlighting, or code completion, to name just 2 possible features. My favorite IDE for Python development is probably PyCharm, which you can fetch from [this link.](https://www.jetbrains.com/pycharm) Setting up PyCharm on Windows is as simple, as downloading the executable installer and installing it with the provided setup utility. PyCharm is free tu use, as long as you use the Community Edition. You can also use your coding notepad of choice. We will only work in one file, which you have to execute, so as long as your text-editing software is able to produce files with the ending `.py`, it will work fine. ## Setup your workspace (PyCharm) If you decided to use PyCharm, simply open it up and click on _Create Project_ in the popup dialogue. In the next dialogue you want to specify your project's name. I chose to use _MyBlockChain_ as my project name. Finally click on _create_ in the bottom right corner. On your left hand side, you're now provided with a file tree. 1. Right-Click on the folder which consists of your project's name, in my case _MyBlockChain_ 2. Go to _new_ 3. Finally click on _Python File_ and 4. Define a Filename. I chose `MyBlockChain.py` as my filename. ## Create your file (Simple Text Editor) If you use a simple text editor, just create a file anywhere you like with the file extension `.py`. In my case I chose to use the filename `MyBlockChain.py`. ## Install the needed packages ### PyCharm In PyCharm you can install packages very easily and add them to your project: 1. Make sure, you have your project open 2. Go to _File_ -> _Settings_ -> _Project: [your project name]_ -> _Project Interpreter_ 3. Then click on the green plus symbol. 4. In the search field type in _Flask_ first. Select the first entry, which simply says _Flask_. Then click on Install Package in the bottom left corner. 5. Do the same thing for _Requests_. ### Pip If you chose not to use PyCharm, you simply need to open up the terminal on linux or the command prompt, or PowerShell on Windows _(simply press **Windows+R** and type in cmd)_ and then execute `pip install Flask==0.12.2 requests==2.18.4 `. If this gives you an error, please double check that you have _pip_ installed. If the problem still occurs, please troubleshoot the error message using google, as there are too many possibilities, what the error could be caused by, for me to discuss every one of them here. ## Getting a HTTP-Client You will also need a HTTP-Client, as we need to make `GET` and `POST` requests to our Flask instance, at some Point in time. Mainly, there are two options - [_Postman_](https://www.getpostman.com/) or [_cURL_](https://curl.haxx.se/). I highly recommend _Postman_, as it is easy and intuitive to use, but if you just want a simple and quick solution, without having to register for an account, just use _cURL_. Online solutions, probably won't work, as the webserver you are going to be trying to access, is just on your local machine. ## Just gimme the source code! If you just want the source code and don't want to follow my instructions, please scroll down, to the very end of this post, where I will add the source code in its complete beauty. # The Blockchain - Your Blockchain ## Step 1: Creating a simplistic Blockchain A block chain is basically a chain of blocks that are linked together in some way, as you have already figured. In this case, every block contains a hash of the previous block. A hash is a hexadecimal number that is determined by a specific algorithm. Imagine a hash being a digital fingerprint of an item. A good hash algorithm will never return the same hash for two different items, so every hash is individual. In our case we will be using the [_SHA-256_](https://en.wikipedia.org/wiki/SHA-2) algorithm. Furthermore the hash algorithm is not reversible, so you can't determine the object, by having its hash, but you can always apply the hash algorithm on the object, to get its hash. As every item has the hash of its previous item, the blockchain is protected against violation, as one would have to change every following block, as the hash changes with every change, as two different items can't have the same hash. ### The Blockchain class Our Blockchain class basically consists of a constructor and some functions. In the constructor, we will create two empty lists - one to store our blockchain in and another, for our transactions. As for now, our class will have several functions: - A function to create new blocks - A function to create new transactions - A function, which hashes a block - A function to fetch the last block The python template looks like this: ```python class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] def create_new_block(self): # This function creates a new block and appends it to the chain pass def create_new_transaction(self): # This function creates a new transaction and adds it to the list of transactions pass @staticmethod def hash(block): # This method hashes the passed block pass @property def last_block(self): # This function returns the last block in the chain pass ``` ## Step 2: The blocks As described earlier, a blockchain consists of blocks. So I will cover them first. Each Block has 5 fields: - An index, - an Unix timestamp, - a list of transactions, - a proof, - and finally the hash of the previous block. Just to clarify things, here is an example of a block ```json example_block = { 'index': 5, 'timestamp': 1515110045.123456, 'transactions': [ { 'sender': "154d8554f9541e12354e1234f512a001", 'recipient': "8654e21254c6512a51bce156542d1e5c", 'amount': 212, } ], 'proof': 488612354568, 'previous_hash': "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e" } ``` ## Step 3: Hashing it As every block needs the hash of the previous block, we need a function, that calculates the hash of a block. To accomplish this, we simply modify our empty `hash` function ```python import hashlib import json class MyBlockChain(object): [...] @staticmethod def hash(block): """ This function hashes the block using the SHA-256 hash algorithm The function is given a block and returns a string, consisting of the hash :param block: <dict> Block :return: <str> """ # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes block_sorted = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_sorted).hexdigest() ``` ## Step 4: Transactions Our `create_new_transaction` method is responsible for adding new transactions to the `transactions` list, which will then be stored inside a block. The whole function is pretty self explanatory. But first we need a function to return the last block of the chain, as we will calculate the new index, at which the transaction will be stored, by increasing the index of the last block by 1 ```python class MyBlockChain(object): [...] @property def last_block(self): return self.blockchain[-1] ``` Now we can fill out the `create_new_transaction` function ```python class MyBlockChain(object): [...] def create_new_transaction(self, sender, recipient, amount): """ This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions. :param sender: <str> Sender's address :param recipient: <str> Recipient's address :param amount: <int> Amount to be transferred :return: <int> This is the index of the block that will contain this transaction """ self.transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 ``` ## Step 5: Creating new Blocks By now we almost have everything at our hands, what we need in order to make a new block. The only thing missing is the _proof_ field. As this is a little harder to understand, I will explain it later. But we still have one problem - if every block has the hash of the previous block, what shall we do with our first block, as there is no previous block for us to hash. This first block is called the _genesis_ block. ```python import hashlib import json from time import time class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] # Creation of the mentioned genesis block self.create_new_block(previous_hash=1, proof=100) def create_new_block(self, proof, previous_hash=None): """ Creates a new block and adds it to the blockchain :param proof: <int> This is generated by the proof of work algorithm :param previous_hash: (Optional) <str> Hash of the preleading Block :return: <dict> Return the new block """ new_block = { 'index': len(self.blockchain) + 1, 'timestamp': time(), 'transactions': self.transactions, 'proof': proof, 'previous_hash': previous_hash or self.hash(self.blockchain[-1]), } # As all pending transactions have been processed and added to the block, the list can be resetted self.transactions = [] # Add the new block to the blockchain self.blockchain.append(new_block) return new_block ``` ## Step 6: What's up with the proof?! ### The [Proof of Work (PoW) algorithm](https://en.wikipedia.org/wiki/Proof-of-work_system) This system wants to **proof** that work has been done. Now a little bit less abstract. Basically we are searching for a number that matches a specific criteria. The thing is, for the system to work, the process of finding a number matching the criteria has to be **difficult**, but the verification process has to be **quick and easy**. This is how new blocks are created, or expressed in another, more commonly used term, _mined_. #### Still too abstract? Ok here is an example: We are looking for a hash of a product of two integers that ends with a 1. So the result of the multiplication of `a` and `b` gets hashed and has to end with a 1. If we set a to `a` static value, our algorithm will determine a value for `b`, for which this criteria is matched. ```python from hashlib import sha256 a = 12 b = 0 # We don't know what b has to be. It will be determined by the algorithm while sha256(f'{a*b}'.encode()).hexdigest()[-1] != "1": b += 1 print(f'The solution is b = {b}') ``` This example outputs ``` The solution is b = 4 ``` This means that the hash of `12*4` ends with a 1 ``` hash(48) = 98010bd9270f9b100b6214a21754fd33bdc8d41b2bc9f9dd16ff54d3c34ffd71 ``` As you can tell, the verification process doesn't need countless loops, until it finds a matching pair. You just have to run the hash algorithm once on the numbers and you can easily determine, whether the numbers match the criteria, or not. ## Step 7: Implementing PoW Firstoff, we need a criteria, or commonly referred to as a rule. For now, our rule is that the hash has to have 5 leading zeroes, so `000005bc845e...` would be a valid result, but `000019ea76c4` would not. As the difficulty rises extremely, if we would require 6 leading zeros, the difficulty of our simple algorithm can be adjusted by changing the amount of leading zeroes required. This is the implementation in python: ```python [...] from uuid import uuid4 class MyBlockChain(object): [...] def pow(self, last_proof): """ Simple PoW Algorithm: - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof. :param last_proof: <int> :return: <int> """ current_proof = 0 while self.validate_proof(last_proof, current_proof) is False: current_proof += 1 return current_proof @staticmethod def validate_proof(last_proof, current_proof): """ Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes. :param last_proof: <int> Previous Proof Number :param current_proof: <int> Current Proof Number :return: <bool> """ possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest() return possible_hash[:5] == "00000" ``` ## Step 8: Interacting with our class Now we are able to create a blockchain. But we can't really interact with it, as we have only interacted with the functions and variables of our class. We are going to use HTTP requests to interact with our blockchain. So let's set it up! # HTTP in the house! To talk to our blockchain using Http requests, we need some help - which comes in the form of the _Flask Framework_. We will create and implement 3 basic methods for now: - `/blockchain` to retrieve the full blockchain - `/mining` to make our simple server mine a block - `/transactions/add` to add a new transaction to the blockchain. ## Step 9: Flask First we need to implement Flask. Here is the code: ```python [...] from textwrap import dedent from flask import Flask, jsonify, request class MyBlockChain(object): [...] app = Flask(__name__) node_identifier = str(uuid4()).replace('-', '') myblockchain = MyBlockChain() @app.route('/blockchain', methods=['GET']) def get_full_chain(): output = { 'chain': myblockchain.blockchain, 'length': len(myblockchain.blockchain), } return jsonify(output), 200 @app.route('/mining', methods=['GET']) def mining(): pass @app.route('/transactions/add', methods=['POST']) def add_transaction(): pass if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` ## Step 10: Adding Transactions As this is commonly the main task of a block chain and also required for a new block, I will cover this first. The user sends a _POST request_ to `/transactions/add` with a JSON object, containing all required fields. This is an example, for a possible, valid request: ```json { "sender": "154d8554f9541e12354e1234f512a001", "recipient": "8654e21254c6512a51bce156542d1e5c", "amount": 212 } ``` As you can see, a request has three fields: - A _sender_ field, with the sender id, - a _recipient_ field, with the recipient id - and an amount, which defines how many units are to be transferred Now we just need to implement this function ```python [...] @app.route('/transactions/add', methods=['POST']) def add_transaction(): values = request.get_json() # The POST request has to have the following required fields required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'There are values missing', 400 # Adds a new transaction by utilizing our function index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount']) output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'} return jsonify(output), 201 ``` This function simply calls the `create_new_transaction` method with the parameters of the POST request ## Step 11: Setup your mines As discussed earlier, future blocks have to be mined. If a new block is found/mined, all pending transactions are added to this block. To 'mine' a new block, our function hast to do three things 1. Calculate the proof 2. Reward the miner with a specific amount of coins (in our example 1 coin) 3. Add the new mined block to the blockchain _(The fact that the recipient is our current node, will make more sense, as we will talk about decentralization later on.)_ ```python @app.route('/mining', methods=['GET']) def mining(): # Calculate the new proof by using our PoW algorithm last_block = myblockchain.last_block last_proof = last_block['proof'] proof = myblockchain.pow(last_proof) # For finding/mining the proof, the miner is granted a reward # The sender is nobody, as this coin is coming out of the void myblockchain.create_new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Add the new created block to the chain previous_hash = myblockchain.hash(last_block) newblock = myblockchain.create_new_block(proof, previous_hash) output = { 'message': "A new block was mined", 'index': newblock['index'], 'transactions': newblock['transactions'], 'proof': newblock['proof'], 'previous_hash': newblock['previous_hash'], } return jsonify(output), 200 ``` # Hello World! Now, **finally** we will be interacting with our blockchain API. Grab your HTTP client of choice and get going! I will explain the procedure for Postman and cURL. ## Step 12: Start up the chain First off fire up your server. In PyCharm simply click on _Run_ -> _Run..._ and select _[your filename].py_ as the file to run, in my case _myblockchain.py_. If you chose to use a simple python file without an IDE, fire up a terminal or command prompt window inside the directory your file is located in and execute the following command ``` python blockchain.py ``` as a result you will see something similar to this ``` $ python blockchain.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ``` **Note that you may be prompted with a dialogue of your firewall. Just accept it.** ## Step 13: Mining it all! Ok let's try the most exciting thing first, **mining**. I'm sure you can't wait to mine your first block, so let's do it! As you can see in the code ```python [...] @app.route('/mining', methods=['GET']) def mining(): [...] ``` we will be interacting with the `mining` part of our API through `GET` requests. This makes sense, as our only goal with this part of the API is, to mine a new block. We don't have to pass anything to the function at all, so a `GET` request is the way to go. ### Postman If you chose to use Postman, here is a quick tutorial on how to make a post request. 1. First you have to register for an account, if you haven't already done that 2. and login after that. 3. If you are logged in, you shoudl be provided with several options such as _Request_, _Collection_, _Environment_. 4. You could simply use the option _Request_, but to keep things organized, we are going to select the _Collection_ option. 5. After that simply choose a name for your collection, in my case I'm using _MyBlockChain_ as my name. 6. Now click on the _New_ Button, located in the top left corner and select _Request_ this time. We are now going to be creating 3 requests. 7. The first request is going to be our _mining_ request, second is a _new transaction_ request and finally we will have a _get whole blockchain_ request. Of course you can customize the request names as you like. 8. **Make sure, as you create these 3 requests, that you select our newly created Collection in the bottom section of the dialogue!** 9. Now click on our Collection's folder in the left sidebar. You should see your 3 newly created requests. Select the _mining_ request first. 10. On the right you are now provided with an interface. As you can see by the selected item from the dropdown box, left to the text field, a `GET` request is the standard request type, so we don't have to change that yet. 11. In the text field `Enter request URL` we are now going to enter the `mining` URL of our API, so enter `http://localhost:5000/mining` as the request URL. ### cURL If you chose to use cURL as your HTTP client, simply type `curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/mining` in a terminal window ### Either way With both methods you should see something similar to this as a result ```json { "index": 2, "message": "New Block Forged", "previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a", "proof": 888273, "transactions": [ { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] } ``` ## Step 14: Transactions are great! As our mining algorithm seems to be working fine, let's create a new transaction, to be stored in a fresh mined block. To do that in ### Postman 1. Click on the _new transaction_ request in the left sidebar. 2. Click on the _GET_ button with the small arrow pointing down. 3. Select _POST_ from the drop-down list. 4. In the navbar below the text field, select _Body_ 5. and then mark the _raw_ option. 6. You should now be provided with an editor like text area, and another drop-down menu next to the _binary_ option, where _Text_ is currently selected. Select _JSON (application/json)_ from this list. 7. Now type in the following into the text area. ```json { "sender": "your-node-address", "recipient": "another-node-address", "amount": 212 } ``` ### cURL Just execute `curl -X POST -H "Content-Type: application/json" -d '{"sender": "your-node-address", "recipient": "another-node-address", "amount": 212}' "http://localhost:5000/transactions/add"` in a terminal window. ### In both cases Replace `your-node-address` with the _recipient_ address from you mining request, as this is your node address. You can also select any other 32 digit hexadezimal number, for now. Furthermore replace `another-node-address` with a random 32 digit hexadezimal number for now, as we currently don't have any other nodes. Adjust the `amount` to your liking. And **voilà**! You have registered your first transaction! ## Step 15: The Chain... So let's inspect our chain! If you're using ### Postman Click on the _get whole blockchain_ request in the left sidebar and simply enter `http://localhost:5000/blockchain` in the `Enter request URL` text field, as _GET_ should be preselected. If not, please change it to the _GET_ option. ### cURL just execute `curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/blockchain` in a terminal window. ### Either way you should get your whole blockchain in form of a JSON-Object. In my case, my blockchain looks like this, after mining 2 times, adding 3 transactions and mining another block: ```json { "chain": [ { "index": 1, "previous_hash": "1", "proof": 100, "timestamp": 1515156902.4502413, "transactions": [] }, { "index": 2, "previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a", "proof": 888273, "timestamp": 1515156913.2686973, "transactions": [ { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] }, { "index": 3, "previous_hash": "1d2ae4a41f4a82ce6f04944e8227bf9c4d951d560f8763b910d7314634dfe09c", "proof": 1156297, "timestamp": 1515158367.3596537, "transactions": [ { "amount": 212, "recipient": "d4ee26eee15148ee92c6cd394edd974e", "sender": "491002ea048a42609d967027cc46a07b" }, { "amount": 212, "recipient": "5654c5654e1b551a65e15f5e4651c156", "sender": "491002ea048a42609d967027cc46a07b" }, { "amount": 111, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "5654c5654e1b551a65e15f5e4651c156" }, { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] } ], "length": 3 } ``` # Decentralizing it Ok, so we got a blockchain, that's working fine and we can interact with it. But one very important thing is still missing: **Decentralization**. This is a core element of the blockchain idea. First off by decentralizing the whole thing, safety and integrity of the blockchain is guaranteed, as a change in the blockchain will be recognized, by the other nodes, as they also have a copy of the blockchain, and discarded. The second reason is that we somehow have to make sense of our transactions. For this we need other nodes and why shouldn't they contribute to our network, by being a part of the blockchain system?! But we still have a conflict there: What if a node has a blockchain that differs from all the others? For our simple blockchain we will always make the **longest valid** blockchain authorative. ## Step 16: Someone out there?! But first things first. To get started, we first have to implement other nodes into our system. To accomplish this task, we have to adjust a few things in our API. First off, we are going to implement some new endpoints: 1. `/nodes/add` to add new nodes in form of a list of URLs 2. `/nodes/resolve` to resolve the conflict we discussed previously by using a consensus algorithm. To do this, we have to modify our Blockchain class first: ```python [...] from urllib.parse import urlparse class MyBlockChain(object): def __init__(self): [...] self.nodes = set() def add_node(self, address): """ Register a new node :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000' :return: None """ node_url = urlparse(address) self.nodes.add(node_url.netloc) ``` ## Step 17: We need Consensus To resolve the conflict of differing blockchains between the nodes, we are going to implement a consensus algorithm. As we discussed earlier, we are always utilizing the longest valid blockchain. First we are implementing a function that validates a blockchain: ```python [...] import requests class MyBlockChain(object) [...] def validate_blockchain(self, test_chain): """ Evaluate, whether a blockchain is valid :param chain: <list> The blockchain that shall be tested :return: <bool> Returns true if the blockchain is valid """ last_block = test_chain[0] current_index = 1 while current_index < len(test_chain): block = test_chain[current_index] # Determine, whether the hash is correct if block['previous_hash'] != self.hash(last_block): return False # Determine, wheter the proof of work is correct if not self.validate_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True ``` Now we just have to run every blockchain in our surrounding network through this consensus algorithm: ```python [...] class MyBlockChain(object) [...] def resolve(self): """ This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously. :return: <bool> Returns true, if our chain was substituted with an updated version """ surrounding_nodes = self.nodes updated_chain = None # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain max_length = len(self.blockchain) # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain. for node_iterator in surrounding_nodes: response = requests.get(f'http://{node_iterator}/blockchain') if response.status_code == 200: length = response.json()['length'] chain_iterator = response.json()['chain'] if length > max_length and self.validate_blockchain(chain_iterator): max_length = length updated_chain = chain_iterator # If we found a new blockchain, replace our current blockchain with the new one if updated_chain: self.blockchain = updated_chain return True return False ``` Now we just have to register our new functions as new endpoints in the API: ```python @app.route('/nodes/add', methods=['POST']) def add_nodes(): values = request.get_json() nodes = values.get('nodes') if nodes is None: return "You didn't post a valid list of nodes. Please double check your input!", 400 for node in nodes: myblockchain.add_node(node) response = { 'message': 'All of your specified nodes have been added to the network!', 'total_nodes': list(myblockchain.nodes), } return jsonify(response), 201 @app.route('/nodes/resolve', methods=['GET']) def consensus(): replaced = myblockchain.resolve() if replaced: response = { 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.', 'new_chain': myblockchain.blockchain } else: response = { 'message': 'The blockchain of this node is already the longest valid chain within the network.', 'chain': myblockchain.blockchain } return jsonify(response), 200 ``` ## Step 18: Friends are great. Even if they're virtual... To test our decentralization algorithms, either grab some friends, to run some nodes for you, or just run the same program on different ports, so you would have to start up an instance of our blockchain-program, then change to port to 5001 for example, save, fire up an instance of this _modified_ program, change the port to 5002, etc. ## Step 19: Add them to your friends list If you got some instances, you just have to register them. To do this, simply send a `POST` request with the following content ```json { "nodes": ["http://[ip-of-another-node-or-127.0.0.1-for-your-local-machine]:[the-port-number]"] } ``` to `http://[the-node-ip-you-want-to-register-it-to]:[port]/nodes/add` ## Step 20: Consensus check After that try to mine some blocks and even add some transactions on let's say node 2 and then run `http://[a-node-ip]:[port]/nodes/resolve` on let's say node 1. The consensus algorithm should step into action and update the blockchain of node 1. # The whole thang I keep my promises ```python import hashlib import json import requests from time import time from uuid import uuid4 from textwrap import dedent from flask import Flask, jsonify, request from urllib.parse import urlparse class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] # Creation of the mentioned genesis block self.create_new_block(previous_hash=1, proof=100) self.nodes = set() def create_new_block(self, proof, previous_hash=None): """ Creates a new block and adds it to the blockchain :param proof: <int> This is generated by the proof of work algorithm :param previous_hash: (Optional) <str> Hash of the preleading Block :return: <dict> Return the new block """ new_block = { 'index': len(self.blockchain) + 1, 'timestamp': time(), 'transactions': self.transactions, 'proof': proof, 'previous_hash': previous_hash or self.hash(self.blockchain[-1]), } # As all pending transactions have been processed and added to the block, the list can be resetted self.transactions = [] # Add the new block to the blockchain self.blockchain.append(new_block) return new_block def create_new_transaction(self, sender, recipient, amount): """ This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions. :param sender: <str> Sender's address :param recipient: <str> Recipient's address :param amount: <int> Amount to be transferred :return: <int> This is the index of the block that will contain this transaction """ self.transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 @staticmethod def hash(block): """ This function hashes the block using the SHA-256 hash algorithm The function is given a block and returns a string, consisting of the hash :param block: <dict> Block :return: <str> """ # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes block_sorted = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_sorted).hexdigest() @property def last_block(self): return self.blockchain[-1] def pow(self, last_proof): """ Simple PoW Algorithm: - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof. :param last_proof: <int> :return: <int> """ current_proof = 0 while self.validate_proof(last_proof, current_proof) is False: current_proof += 1 return current_proof @staticmethod def validate_proof(last_proof, current_proof): """ Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes. :param last_proof: <int> Previous Proof Number :param current_proof: <int> Current Proof Number :return: <bool> """ possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest() return possible_hash[:5] == "00000" def add_node(self, address): """ Register a new node :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000' :return: None """ node_url = urlparse(address) self.nodes.add(node_url.netloc) def validate_blockchain(self, test_chain): """ Evaluate, whether a blockchain is valid :param chain: <list> The blockchain that shall be tested :return: <bool> Returns true if the blockchain is valid """ last_block = test_chain[0] current_index = 1 while current_index < len(test_chain): block = test_chain[current_index] # Determine, whether the hash is correct if block['previous_hash'] != self.hash(last_block): return False # Determine, wheter the proof of work is correct if not self.validate_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True def resolve(self): """ This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously. :return: <bool> Returns true, if our chain was substituted with an updated version """ surrounding_nodes = self.nodes updated_chain = None # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain max_length = len(self.blockchain) # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain. for node_iterator in surrounding_nodes: response = requests.get(f'http://{node_iterator}/blockchain') if response.status_code == 200: length = response.json()['length'] chain_iterator = response.json()['chain'] if length > max_length and self.validate_blockchain(chain_iterator): max_length = length updated_chain = chain_iterator # If we found a new blockchain, replace our current blockchain with the new one if updated_chain: self.blockchain = updated_chain return True return False app = Flask(__name__) node_identifier = str(uuid4()).replace('-', '') myblockchain = MyBlockChain() @app.route('/blockchain', methods=['GET']) def get_full_chain(): output = { 'chain': myblockchain.blockchain, 'length': len(myblockchain.blockchain), } return jsonify(output), 200 @app.route('/mining', methods=['GET']) def mining(): # Calculate the new proof by using our PoW algorithm last_block = myblockchain.last_block last_proof = last_block['proof'] proof = myblockchain.pow(last_proof) # For finding/mining the proof, the miner is granted a reward # The sender is nobody, as this coin is coming out of the void myblockchain.create_new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Add the new created block to the chain previous_hash = myblockchain.hash(last_block) newblock = myblockchain.create_new_block(proof, previous_hash) output = { 'message': "A new block was mined", 'index': newblock['index'], 'transactions': newblock['transactions'], 'proof': newblock['proof'], 'previous_hash': newblock['previous_hash'], } return jsonify(output), 200 @app.route('/transactions/add', methods=['POST']) def add_transaction(): values = request.get_json() # The POST request has to have the following required fields required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'There are values missing', 400 # Adds a new transaction by utilizing our function index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount']) output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'} return jsonify(output), 201 @app.route('/nodes/add', methods=['POST']) def add_nodes(): values = request.get_json() nodes = values.get('nodes') if nodes is None: return "You didn't post a valid list of nodes. Please double check your input!", 400 for node in nodes: myblockchain.add_node(node) response = { 'message': 'All of your specified nodes have been added to the network!', 'total_nodes': list(myblockchain.nodes), } return jsonify(response), 201 @app.route('/nodes/resolve', methods=['GET']) def consensus(): replaced = myblockchain.resolve() if replaced: response = { 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.', 'new_chain': myblockchain.blockchain } else: response = { 'message': 'The blockchain of this node is already the longest valid chain within the network.', 'chain': myblockchain.blockchain } return jsonify(response), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` # Final thoughts And there you have it! Your very own blockchain. Wasn't very hard, was it? I sincerely hope you had fun thorughout this tutorial and were able to understand the concept and system behind it better. If you have any questions, please ask them in the comments and I'll try my best to answer them. # The obligatory begging As this whole tutorial took me over 2 days to finish, I would love to see a lot of comments and feedback under this post. Also if you find any typos, grammar mistakes, etc. and care to tell me, i would be mor than happy about it, as English is not my native language. So please excuse any mistakes I made. I tried my very best. Of course please upvote, if you worship my effort to enrich the community, and if you want to see more such content, consider following me. I wish you only the best - See ya! |
| json metadata | {"tags":["steemit","blockchain","trevonjb","cryptocurrency","craigrant"],"links":["https://www.python.org/downloads/windows/","https://en.wikipedia.org/wiki/Integrated_development_environment","https://www.jetbrains.com/pycharm","https://www.getpostman.com/","https://curl.haxx.se/","https://en.wikipedia.org/wiki/SHA-2","https://en.wikipedia.org/wiki/Proof-of-work_system"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | steemit |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| title | Create your own blockchain & cryptocurrency! - understand blockchains by creating one in python - no knowledge required |
| Transaction Info | Block #18742287/Trx 1f80156352fe9f67a101ccd1e7eb3256f194e999 |
View Raw JSON Data
{
"block": 18742287,
"op": [
"comment",
{
"author": "dustvoice",
"body": "# What exactly is a blockchain?\n- How does a blockchain work?\n- What is Bitcoin or STEEM exactly?\n- How does a blockchain make sure of its correctness\n- What is **mining** exactly?\n\n**These are only some questions, probably many of you are asking yourselves. \nI'm here to provide you with kind of an unusual answer.\nMy goal is to help you undestand the whole system behind blockchains, by actually creating a small blockchain yourself!**\n\nWith the rise of cryptocurrencies and decentralized systems like Bitcoin or STEEM, many people want to really undestand, what this is all about. I will show you how the system works, by coding a blockchain from scratch in python with you. The guide will be mainly for Windows, but I will also add comments regarding Linux. You don't know a whole of python, to follow this guide, but it is advised to know the most basic basics.\n\n# Get ready\n## Setup Python and pip\nFirst off, we need to install Python 3.6+ along with pip, with which you can install python packages. \n- On **Linux**, you can install python, by simply typing `sudo apt-get install python3.6` into the terminal. If you encounter errors, please try `sudo apt-get update` and then try to execute the command again. If it still doesn't work, please search for installation instructions for your specific system on your preferred search engine.\n- On **Windows**,\n 1. first download the binary, that suits your system best, under the [official python download page for windows.](https://www.python.org/downloads/windows/). Make sure you grab at least the _version 3.6_ or higher.\n 2. Install the file you just downloaded. Make sure you check the option to install pip, when provided with an option to do so. If not, don't worry, it probably is enabled by default.\n\n## IDE or not?\nAs a next step, you want to decide, whether to utilize an [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) or not. An IDE is an environment, which supports you in the creation process of your application, by providing many features, with for example syntax highlighting, or code completion, to name just 2 possible features. My favorite IDE for Python development is probably PyCharm, which you can fetch from [this link.](https://www.jetbrains.com/pycharm) Setting up PyCharm on Windows is as simple, as downloading the executable installer and installing it with the provided setup utility. PyCharm is free tu use, as long as you use the Community Edition.\nYou can also use your coding notepad of choice. We will only work in one file, which you have to execute, so as long as your text-editing software is able to produce files with the ending `.py`, it will work fine.\n\n## Setup your workspace (PyCharm)\nIf you decided to use PyCharm, simply open it up and click on _Create Project_ in the popup dialogue. In the next dialogue you want to specify your project's name. I chose to use _MyBlockChain_ as my project name. Finally click on _create_ in the bottom right corner. On your left hand side, you're now provided with a file tree. \n1. Right-Click on the folder which consists of your project's name, in my case _MyBlockChain_\n2. Go to _new_ \n3. Finally click on _Python File_ and\n4. Define a Filename. I chose `MyBlockChain.py` as my filename.\n\n## Create your file (Simple Text Editor)\nIf you use a simple text editor, just create a file anywhere you like with the file extension `.py`. In my case I chose to use the filename `MyBlockChain.py`.\n\n## Install the needed packages\n### PyCharm\nIn PyCharm you can install packages very easily and add them to your project:\n1. Make sure, you have your project open\n2. Go to _File_ -> _Settings_ -> _Project: [your project name]_ -> _Project Interpreter_\n3. Then click on the green plus symbol.\n4. In the search field type in _Flask_ first. Select the first entry, which simply says _Flask_. Then click on Install Package in the bottom left corner.\n5. Do the same thing for _Requests_.\n### Pip\nIf you chose not to use PyCharm, you simply need to open up the terminal on linux or the command prompt, or PowerShell on Windows _(simply press **Windows+R** and type in cmd)_ and then execute `pip install Flask==0.12.2 requests==2.18.4 `. If this gives you an error, please double check that you have _pip_ installed. If the problem still occurs, please troubleshoot the error message using google, as there are too many possibilities, what the error could be caused by, for me to discuss every one of them here.\n\n## Getting a HTTP-Client\nYou will also need a HTTP-Client, as we need to make `GET` and `POST` requests to our Flask instance, at some Point in time. Mainly, there are two options - [_Postman_](https://www.getpostman.com/) or [_cURL_](https://curl.haxx.se/). I highly recommend _Postman_, as it is easy and intuitive to use, but if you just want a simple and quick solution, without having to register for an account, just use _cURL_. Online solutions, probably won't work, as the webserver you are going to be trying to access, is just on your local machine. \n\n## Just gimme the source code!\nIf you just want the source code and don't want to follow my instructions, please scroll down, to the very end of this post, where I will add the source code in its complete beauty.\n\n# The Blockchain - Your Blockchain\n## Step 1: Creating a simplistic Blockchain\nA block chain is basically a chain of blocks that are linked together in some way, as you have already figured. In this case, every block contains a hash of the previous block. A hash is a hexadecimal number that is determined by a specific algorithm. Imagine a hash being a digital fingerprint of an item. A good hash algorithm will never return the same hash for two different items, so every hash is individual. In our case we will be using the [_SHA-256_](https://en.wikipedia.org/wiki/SHA-2) algorithm. Furthermore the hash algorithm is not reversible, so you can't determine the object, by having its hash, but you can always apply the hash algorithm on the object, to get its hash. As every item has the hash of its previous item, the blockchain is protected against violation, as one would have to change every following block, as the hash changes with every change, as two different items can't have the same hash.\n### The Blockchain class\nOur Blockchain class basically consists of a constructor and some functions. In the constructor, we will create two empty lists - one to store our blockchain in and another, for our transactions. As for now, our class will have several functions:\n- A function to create new blocks\n- A function to create new transactions\n- A function, which hashes a block\n- A function to fetch the last block\nThe python template looks like this:\n```python\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n \n def create_new_block(self):\n # This function creates a new block and appends it to the chain\n pass\n \n def create_new_transaction(self):\n # This function creates a new transaction and adds it to the list of transactions\n pass\n \n @staticmethod\n def hash(block):\n # This method hashes the passed block\n pass\n\n @property\n def last_block(self):\n # This function returns the last block in the chain\n pass\n```\n## Step 2: The blocks\nAs described earlier, a blockchain consists of blocks. So I will cover them first.\nEach Block has 5 fields:\n- An index,\n- an Unix timestamp,\n- a list of transactions,\n- a proof,\n- and finally the hash of the previous block.\nJust to clarify things, here is an example of a block\n```json\nexample_block = {\n 'index': 5,\n 'timestamp': 1515110045.123456,\n 'transactions': \n [\n {\n 'sender': \"154d8554f9541e12354e1234f512a001\",\n 'recipient': \"8654e21254c6512a51bce156542d1e5c\",\n 'amount': 212,\n }\n ],\n 'proof': 488612354568,\n 'previous_hash': \"c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e\"\n}\n```\n## Step 3: Hashing it\nAs every block needs the hash of the previous block, we need a function, that calculates the hash of a block. To accomplish this, we simply modify our empty `hash` function\n```python\nimport hashlib\nimport json\n\nclass MyBlockChain(object):\n [...]\n @staticmethod\n def hash(block):\n \"\"\"\n This function hashes the block using the SHA-256 hash algorithm\n The function is given a block and returns a string, consisting of the hash\n :param block: <dict> Block\n :return: <str>\n \"\"\"\n\n # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes\n block_sorted = json.dumps(block, sort_keys=True).encode()\n return hashlib.sha256(block_sorted).hexdigest()\n```\n## Step 4: Transactions\nOur `create_new_transaction` method is responsible for adding new transactions to the `transactions` list, which will then be stored inside a block. The whole function is pretty self explanatory. But first we need a function to return the last block of the chain, as we will calculate the new index, at which the transaction will be stored, by increasing the index of the last block by 1\n```python\nclass MyBlockChain(object):\n [...]\n @property\n def last_block(self):\n return self.blockchain[-1]\n```\nNow we can fill out the `create_new_transaction` function\n```python\nclass MyBlockChain(object):\n [...]\n def create_new_transaction(self, sender, recipient, amount):\n \"\"\"\n This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.\n \n :param sender: <str> Sender's address\n :param recipient: <str> Recipient's address\n :param amount: <int> Amount to be transferred\n :return: <int> This is the index of the block that will contain this transaction\n \"\"\"\n\n self.transactions.append({\n 'sender': sender,\n 'recipient': recipient,\n 'amount': amount,\n })\n\n return self.last_block['index'] + 1\n```\n## Step 5: Creating new Blocks\nBy now we almost have everything at our hands, what we need in order to make a new block. The only thing missing is the _proof_ field. As this is a little harder to understand, I will explain it later. \nBut we still have one problem - if every block has the hash of the previous block, what shall we do with our first block, as there is no previous block for us to hash. \nThis first block is called the _genesis_ block.\n```python\nimport hashlib\nimport json\nfrom time import time\n\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n\n # Creation of the mentioned genesis block\n self.create_new_block(previous_hash=1, proof=100)\n\n def create_new_block(self, proof, previous_hash=None):\n \"\"\"\n Creates a new block and adds it to the blockchain\n :param proof: <int> This is generated by the proof of work algorithm\n :param previous_hash: (Optional) <str> Hash of the preleading Block\n :return: <dict> Return the new block\n \"\"\"\n\n new_block = {\n 'index': len(self.blockchain) + 1,\n 'timestamp': time(),\n 'transactions': self.transactions,\n 'proof': proof,\n 'previous_hash': previous_hash or self.hash(self.blockchain[-1]),\n }\n\n # As all pending transactions have been processed and added to the block, the list can be resetted\n self.transactions = []\n \n # Add the new block to the blockchain\n self.blockchain.append(new_block)\n return new_block\n```\n## Step 6: What's up with the proof?!\n### The [Proof of Work (PoW) algorithm](https://en.wikipedia.org/wiki/Proof-of-work_system)\nThis system wants to **proof** that work has been done. Now a little bit less abstract. Basically we are searching for a number that matches a specific criteria. The thing is, for the system to work, the process of finding a number matching the criteria has to be **difficult**, but the verification process has to be **quick and easy**. This is how new blocks are created, or expressed in another, more commonly used term, _mined_.\n#### Still too abstract? Ok here is an example:\nWe are looking for a hash of a product of two integers that ends with a 1.\nSo the result of the multiplication of `a` and `b` gets hashed and has to end with a 1. If we set a to `a` static value, our algorithm will determine a value for `b`, for which this criteria is matched. \n```python\nfrom hashlib import sha256\na = 12\nb = 0 # We don't know what b has to be. It will be determined by the algorithm\nwhile sha256(f'{a*b}'.encode()).hexdigest()[-1] != \"1\":\n b += 1\nprint(f'The solution is b = {b}')\n```\nThis example outputs\n```\nThe solution is b = 4\n```\nThis means that the hash of `12*4` ends with a 1\n```\nhash(48) = 98010bd9270f9b100b6214a21754fd33bdc8d41b2bc9f9dd16ff54d3c34ffd71\n```\nAs you can tell, the verification process doesn't need countless loops, until it finds a matching pair. You just have to run the hash algorithm once on the numbers and you can easily determine, whether the numbers match the criteria, or not.\n## Step 7: Implementing PoW\nFirstoff, we need a criteria, or commonly referred to as a rule. For now, our rule is that the hash has to have 5 leading zeroes, so `000005bc845e...` would be a valid result, but `000019ea76c4` would not. As the difficulty rises extremely, if we would require 6 leading zeros, the difficulty of our simple algorithm can be adjusted by changing the amount of leading zeroes required. This is the implementation in python:\n```python\n[...]\nfrom uuid import uuid4\n\nclass MyBlockChain(object):\n [...]\n \n def pow(self, last_proof):\n \"\"\"\n Simple PoW Algorithm:\n - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.\n :param last_proof: <int>\n :return: <int>\n \"\"\"\n \n current_proof = 0\n while self.validate_proof(last_proof, current_proof) is False:\n current_proof += 1\n\n return current_proof\n\n @staticmethod\n def validate_proof(last_proof, current_proof):\n \"\"\"\n Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.\n :param last_proof: <int> Previous Proof Number\n :param current_proof: <int> Current Proof Number\n :return: <bool>\n \"\"\"\n\n possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()\n return possible_hash[:5] == \"00000\"\n```\n## Step 8: Interacting with our class\nNow we are able to create a blockchain. But we can't really interact with it, as we have only interacted with the functions and variables of our class.\nWe are going to use HTTP requests to interact with our blockchain. So let's set it up!\n# HTTP in the house!\nTo talk to our blockchain using Http requests, we need some help - which comes in the form of the _Flask Framework_. We will create and implement 3 basic methods for now:\n- `/blockchain` to retrieve the full blockchain\n- `/mining` to make our simple server mine a block\n- `/transactions/add` to add a new transaction to the blockchain.\n## Step 9: Flask\nFirst we need to implement Flask. Here is the code:\n```python\n[...]\nfrom textwrap import dedent\nfrom flask import Flask, jsonify, request\n\nclass MyBlockChain(object):\n [...]\n \napp = Flask(__name__)\nnode_identifier = str(uuid4()).replace('-', '')\n\nmyblockchain = MyBlockChain()\n\[email protected]('/blockchain', methods=['GET'])\ndef get_full_chain():\n output = {\n 'chain': myblockchain.blockchain,\n 'length': len(myblockchain.blockchain),\n }\n return jsonify(output), 200\n\[email protected]('/mining', methods=['GET'])\ndef mining():\n pass\[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n pass\nif __name__ == '__main__':\n app.run(host='0.0.0.0', port=5000)\n```\n## Step 10: Adding Transactions\nAs this is commonly the main task of a block chain and also required for a new block, I will cover this first. The user sends a _POST request_ to `/transactions/add` with a JSON object, containing all required fields. This is an example, for a possible, valid request:\n```json\n{\n \"sender\": \"154d8554f9541e12354e1234f512a001\",\n \"recipient\": \"8654e21254c6512a51bce156542d1e5c\",\n \"amount\": 212\n}\n```\nAs you can see, a request has three fields:\n- A _sender_ field, with the sender id,\n- a _recipient_ field, with the recipient id\n- and an amount, which defines how many units are to be transferred\nNow we just need to implement this function\n```python\n[...]\n\[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n values = request.get_json()\n\n # The POST request has to have the following required fields\n required = ['sender', 'recipient', 'amount']\n if not all(k in values for k in required):\n return 'There are values missing', 400\n\n # Adds a new transaction by utilizing our function\n index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])\n\n output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}\n return jsonify(output), 201\n```\nThis function simply calls the `create_new_transaction` method with the parameters of the POST request\n## Step 11: Setup your mines\nAs discussed earlier, future blocks have to be mined. If a new block is found/mined, all pending transactions are added to this block. To 'mine' a new block, our function hast to do three things\n1. Calculate the proof\n2. Reward the miner with a specific amount of coins (in our example 1 coin)\n3. Add the new mined block to the blockchain\n_(The fact that the recipient is our current node, will make more sense, as we will talk about decentralization later on.)_\n```python\[email protected]('/mining', methods=['GET'])\ndef mining():\n # Calculate the new proof by using our PoW algorithm\n last_block = myblockchain.last_block\n last_proof = last_block['proof']\n proof = myblockchain.pow(last_proof)\n\n # For finding/mining the proof, the miner is granted a reward\n # The sender is nobody, as this coin is coming out of the void\n myblockchain.create_new_transaction(\n sender=\"0\",\n recipient=node_identifier,\n amount=1,\n )\n\n # Add the new created block to the chain\n previous_hash = myblockchain.hash(last_block)\n newblock = myblockchain.create_new_block(proof, previous_hash)\n\n output = {\n 'message': \"A new block was mined\",\n 'index': newblock['index'],\n 'transactions': newblock['transactions'],\n 'proof': newblock['proof'],\n 'previous_hash': newblock['previous_hash'],\n }\n return jsonify(output), 200\n```\n# Hello World!\nNow, **finally** we will be interacting with our blockchain API. Grab your HTTP client of choice and get going! I will explain the procedure for Postman and cURL.\n## Step 12: Start up the chain\nFirst off fire up your server. \nIn PyCharm simply click on _Run_ -> _Run..._ and select _[your filename].py_ as the file to run, in my case _myblockchain.py_.\nIf you chose to use a simple python file without an IDE, fire up a terminal or command prompt window inside the directory your file is located in and execute the following command\n```\npython blockchain.py\n```\nas a result you will see something similar to this\n```\n$ python blockchain.py\n* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n```\n**Note that you may be prompted with a dialogue of your firewall. Just accept it.**\n## Step 13: Mining it all!\nOk let's try the most exciting thing first, **mining**. I'm sure you can't wait to mine your first block, so let's do it!\nAs you can see in the code\n```python\n[...]\[email protected]('/mining', methods=['GET'])\ndef mining():\n [...]\n```\nwe will be interacting with the `mining` part of our API through `GET` requests. This makes sense, as our only goal with this part of the API is, to mine a new block. We don't have to pass anything to the function at all, so a `GET` request is the way to go.\n### Postman\nIf you chose to use Postman, here is a quick tutorial on how to make a post request. \n1. First you have to register for an account, if you haven't already done that\n2. and login after that. \n3. If you are logged in, you shoudl be provided with several options such as _Request_, _Collection_, _Environment_.\n4. You could simply use the option _Request_, but to keep things organized, we are going to select the _Collection_ option. \n5. After that simply choose a name for your collection, in my case I'm using _MyBlockChain_ as my name.\n6. Now click on the _New_ Button, located in the top left corner and select _Request_ this time. We are now going to be creating 3 requests.\n7. The first request is going to be our _mining_ request, second is a _new transaction_ request and finally we will have a _get whole blockchain_ request. Of course you can customize the request names as you like. \n8. **Make sure, as you create these 3 requests, that you select our newly created Collection in the bottom section of the dialogue!**\n9. Now click on our Collection's folder in the left sidebar. You should see your 3 newly created requests. Select the _mining_ request first.\n10. On the right you are now provided with an interface. As you can see by the selected item from the dropdown box, left to the text field, a `GET` request is the standard request type, so we don't have to change that yet. \n11. In the text field `Enter request URL` we are now going to enter the `mining` URL of our API, so enter `http://localhost:5000/mining` as the request URL.\n### cURL\nIf you chose to use cURL as your HTTP client, simply type `curl -i -H \"Content-Type: application/json\" -X GET http://localhost:5000/mining` in a terminal window\n### Either way\nWith both methods you should see something similar to this as a result\n```json\n{\n \"index\": 2,\n \"message\": \"New Block Forged\",\n \"previous_hash\": \"0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a\",\n \"proof\": 888273,\n \"transactions\": [\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n}\n```\n## Step 14: Transactions are great!\nAs our mining algorithm seems to be working fine, let's create a new transaction, to be stored in a fresh mined block. To do that in\n### Postman\n1. Click on the _new transaction_ request in the left sidebar.\n2. Click on the _GET_ button with the small arrow pointing down.\n3. Select _POST_ from the drop-down list.\n4. In the navbar below the text field, select _Body_ \n5. and then mark the _raw_ option.\n6. You should now be provided with an editor like text area, and another drop-down menu next to the _binary_ option, where _Text_ is currently selected. Select _JSON (application/json)_ from this list.\n7. Now type in the following into the text area.\n```json\n{\n \"sender\": \"your-node-address\",\n \"recipient\": \"another-node-address\",\n \"amount\": 212\n}\n```\n### cURL\nJust execute `curl -X POST -H \"Content-Type: application/json\" -d '{\"sender\": \"your-node-address\", \"recipient\": \"another-node-address\", \"amount\": 212}' \"http://localhost:5000/transactions/add\"` in a terminal window.\n### In both cases\nReplace `your-node-address` with the _recipient_ address from you mining request, as this is your node address. You can also select any other 32 digit hexadezimal number, for now. Furthermore replace `another-node-address` with a random 32 digit hexadezimal number for now, as we currently don't have any other nodes.\nAdjust the `amount` to your liking.\nAnd **voilà**! You have registered your first transaction!\n## Step 15: The Chain...\nSo let's inspect our chain! If you're using\n### Postman\nClick on the _get whole blockchain_ request in the left sidebar and simply enter `http://localhost:5000/blockchain` in the `Enter request URL` text field, as _GET_ should be preselected. If not, please change it to the _GET_ option.\n### cURL\njust execute `curl -i -H \"Content-Type: application/json\" -X GET http://localhost:5000/blockchain` in a terminal window.\n### Either way\nyou should get your whole blockchain in form of a JSON-Object. In my case, my blockchain looks like this, after mining 2 times, adding 3 transactions and mining another block:\n```json\n{\n \"chain\": [\n {\n \"index\": 1,\n \"previous_hash\": \"1\",\n \"proof\": 100,\n \"timestamp\": 1515156902.4502413,\n \"transactions\": []\n },\n {\n \"index\": 2,\n \"previous_hash\": \"0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a\",\n \"proof\": 888273,\n \"timestamp\": 1515156913.2686973,\n \"transactions\": [\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n },\n {\n \"index\": 3,\n \"previous_hash\": \"1d2ae4a41f4a82ce6f04944e8227bf9c4d951d560f8763b910d7314634dfe09c\",\n \"proof\": 1156297,\n \"timestamp\": 1515158367.3596537,\n \"transactions\": [\n {\n \"amount\": 212,\n \"recipient\": \"d4ee26eee15148ee92c6cd394edd974e\",\n \"sender\": \"491002ea048a42609d967027cc46a07b\"\n },\n {\n \"amount\": 212,\n \"recipient\": \"5654c5654e1b551a65e15f5e4651c156\",\n \"sender\": \"491002ea048a42609d967027cc46a07b\"\n },\n {\n \"amount\": 111,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"5654c5654e1b551a65e15f5e4651c156\"\n },\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n }\n ],\n \"length\": 3\n}\n```\n# Decentralizing it\nOk, so we got a blockchain, that's working fine and we can interact with it. But one very important thing is still missing: **Decentralization**. \nThis is a core element of the blockchain idea. \nFirst off by decentralizing the whole thing, safety and integrity of the blockchain is guaranteed, as a change in the blockchain will be recognized, by the other nodes, as they also have a copy of the blockchain, and discarded. \nThe second reason is that we somehow have to make sense of our transactions. For this we need other nodes and why shouldn't they contribute to our network, by being a part of the blockchain system?! \nBut we still have a conflict there: What if a node has a blockchain that differs from all the others? \nFor our simple blockchain we will always make the **longest valid** blockchain authorative.\n## Step 16: Someone out there?!\nBut first things first. \nTo get started, we first have to implement other nodes into our system.\nTo accomplish this task, we have to adjust a few things in our API. First off, we are going to implement some new endpoints:\n1. `/nodes/add` to add new nodes in form of a list of URLs\n2. `/nodes/resolve` to resolve the conflict we discussed previously by using a consensus algorithm.\nTo do this, we have to modify our Blockchain class first:\n```python\n[...]\nfrom urllib.parse import urlparse\n\nclass MyBlockChain(object):\n def __init__(self):\n [...]\n self.nodes = set()\n\n def add_node(self, address):\n \"\"\"\n Register a new node\n :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'\n :return: None\n \"\"\"\n\n node_url = urlparse(address)\n self.nodes.add(node_url.netloc)\n```\n## Step 17: We need Consensus\nTo resolve the conflict of differing blockchains between the nodes, we are going to implement a consensus algorithm. As we discussed earlier, we are always utilizing the longest valid blockchain.\nFirst we are implementing a function that validates a blockchain:\n```python\n[...]\nimport requests\n\nclass MyBlockChain(object)\n [...]\n \n def validate_blockchain(self, test_chain):\n \"\"\"\n Evaluate, whether a blockchain is valid\n :param chain: <list> The blockchain that shall be tested\n :return: <bool> Returns true if the blockchain is valid\n \"\"\"\n\n last_block = test_chain[0]\n current_index = 1\n\n while current_index < len(test_chain):\n block = test_chain[current_index]\n \n # Determine, whether the hash is correct\n if block['previous_hash'] != self.hash(last_block):\n return False\n\n # Determine, wheter the proof of work is correct\n if not self.validate_proof(last_block['proof'], block['proof']):\n return False\n\n last_block = block\n current_index += 1\n\n return True\n```\nNow we just have to run every blockchain in our surrounding network through this consensus algorithm:\n```python\n [...]\n\nclass MyBlockChain(object)\n [...]\n def resolve(self):\n \"\"\"\n This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.\n :return: <bool> Returns true, if our chain was substituted with an updated version\n \"\"\"\n\n surrounding_nodes = self.nodes\n updated_chain = None\n\n # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain\n max_length = len(self.blockchain)\n\n # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.\n for node_iterator in surrounding_nodes:\n response = requests.get(f'http://{node_iterator}/blockchain')\n\n if response.status_code == 200:\n length = response.json()['length']\n chain_iterator = response.json()['chain']\n\n if length > max_length and self.validate_blockchain(chain_iterator):\n max_length = length\n updated_chain = chain_iterator\n\n # If we found a new blockchain, replace our current blockchain with the new one\n if updated_chain:\n self.blockchain = updated_chain\n return True\n\n return False\n```\nNow we just have to register our new functions as new endpoints in the API:\n```python\[email protected]('/nodes/add', methods=['POST'])\ndef add_nodes():\n values = request.get_json()\n\n nodes = values.get('nodes')\n if nodes is None:\n return \"You didn't post a valid list of nodes. Please double check your input!\", 400\n\n for node in nodes:\n myblockchain.add_node(node)\n\n response = {\n 'message': 'All of your specified nodes have been added to the network!',\n 'total_nodes': list(myblockchain.nodes),\n }\n return jsonify(response), 201\n\n\[email protected]('/nodes/resolve', methods=['GET'])\ndef consensus():\n replaced = myblockchain.resolve()\n\n if replaced:\n response = {\n 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',\n 'new_chain': myblockchain.blockchain\n }\n else:\n response = {\n 'message': 'The blockchain of this node is already the longest valid chain within the network.',\n 'chain': myblockchain.blockchain\n }\n\n return jsonify(response), 200\n```\n## Step 18: Friends are great. Even if they're virtual...\nTo test our decentralization algorithms, either grab some friends, to run some nodes for you, or just run the same program on different ports, so you would have to start up an instance of our blockchain-program, then change to port to 5001 for example, save, fire up an instance of this _modified_ program, change the port to 5002, etc. \n## Step 19: Add them to your friends list\nIf you got some instances, you just have to register them. To do this, simply send a `POST` request with the following content\n```json\n{\n \"nodes\": [\"http://[ip-of-another-node-or-127.0.0.1-for-your-local-machine]:[the-port-number]\"]\n}\n```\nto `http://[the-node-ip-you-want-to-register-it-to]:[port]/nodes/add`\n## Step 20: Consensus check\nAfter that try to mine some blocks and even add some transactions on let's say node 2 and then run `http://[a-node-ip]:[port]/nodes/resolve` on let's say node 1. The consensus algorithm should step into action and update the blockchain of node 1.\n# The whole thang\nI keep my promises\n```python\nimport hashlib\nimport json\nimport requests\nfrom time import time\nfrom uuid import uuid4\nfrom textwrap import dedent\nfrom flask import Flask, jsonify, request\nfrom urllib.parse import urlparse\n\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n \n # Creation of the mentioned genesis block\n self.create_new_block(previous_hash=1, proof=100)\n \n self.nodes = set()\n \n def create_new_block(self, proof, previous_hash=None):\n \"\"\"\n Creates a new block and adds it to the blockchain\n :param proof: <int> This is generated by the proof of work algorithm\n :param previous_hash: (Optional) <str> Hash of the preleading Block\n :return: <dict> Return the new block\n \"\"\"\n\n new_block = {\n 'index': len(self.blockchain) + 1,\n 'timestamp': time(),\n 'transactions': self.transactions,\n 'proof': proof,\n 'previous_hash': previous_hash or self.hash(self.blockchain[-1]),\n }\n\n # As all pending transactions have been processed and added to the block, the list can be resetted\n self.transactions = []\n \n # Add the new block to the blockchain\n self.blockchain.append(new_block)\n return new_block\n \n def create_new_transaction(self, sender, recipient, amount):\n \"\"\"\n This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.\n \n :param sender: <str> Sender's address\n :param recipient: <str> Recipient's address\n :param amount: <int> Amount to be transferred\n :return: <int> This is the index of the block that will contain this transaction\n \"\"\"\n\n self.transactions.append({\n 'sender': sender,\n 'recipient': recipient,\n 'amount': amount,\n })\n\n return self.last_block['index'] + 1\n \n @staticmethod\n def hash(block):\n \"\"\"\n This function hashes the block using the SHA-256 hash algorithm\n The function is given a block and returns a string, consisting of the hash\n :param block: <dict> Block\n :return: <str>\n \"\"\"\n\n # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes\n block_sorted = json.dumps(block, sort_keys=True).encode()\n return hashlib.sha256(block_sorted).hexdigest()\n\n @property\n def last_block(self):\n return self.blockchain[-1]\n \n def pow(self, last_proof):\n \"\"\"\n Simple PoW Algorithm:\n - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.\n :param last_proof: <int>\n :return: <int>\n \"\"\"\n \n current_proof = 0\n while self.validate_proof(last_proof, current_proof) is False:\n current_proof += 1\n\n return current_proof\n\n @staticmethod\n def validate_proof(last_proof, current_proof):\n \"\"\"\n Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.\n :param last_proof: <int> Previous Proof Number\n :param current_proof: <int> Current Proof Number\n :return: <bool>\n \"\"\"\n\n possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()\n return possible_hash[:5] == \"00000\"\n \n def add_node(self, address):\n \"\"\"\n Register a new node\n :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'\n :return: None\n \"\"\"\n\n node_url = urlparse(address)\n self.nodes.add(node_url.netloc)\n \n def validate_blockchain(self, test_chain):\n \"\"\"\n Evaluate, whether a blockchain is valid\n :param chain: <list> The blockchain that shall be tested\n :return: <bool> Returns true if the blockchain is valid\n \"\"\"\n\n last_block = test_chain[0]\n current_index = 1\n\n while current_index < len(test_chain):\n block = test_chain[current_index]\n \n # Determine, whether the hash is correct\n if block['previous_hash'] != self.hash(last_block):\n return False\n\n # Determine, wheter the proof of work is correct\n if not self.validate_proof(last_block['proof'], block['proof']):\n return False\n\n last_block = block\n current_index += 1\n\n return True\n \n def resolve(self):\n \"\"\"\n This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.\n :return: <bool> Returns true, if our chain was substituted with an updated version\n \"\"\"\n\n surrounding_nodes = self.nodes\n updated_chain = None\n\n # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain\n max_length = len(self.blockchain)\n\n # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.\n for node_iterator in surrounding_nodes:\n response = requests.get(f'http://{node_iterator}/blockchain')\n\n if response.status_code == 200:\n length = response.json()['length']\n chain_iterator = response.json()['chain']\n\n if length > max_length and self.validate_blockchain(chain_iterator):\n max_length = length\n updated_chain = chain_iterator\n\n # If we found a new blockchain, replace our current blockchain with the new one\n if updated_chain:\n self.blockchain = updated_chain\n return True\n\n return False\n\napp = Flask(__name__)\nnode_identifier = str(uuid4()).replace('-', '')\n\nmyblockchain = MyBlockChain()\n\[email protected]('/blockchain', methods=['GET'])\ndef get_full_chain():\n output = {\n 'chain': myblockchain.blockchain,\n 'length': len(myblockchain.blockchain),\n }\n return jsonify(output), 200\n\[email protected]('/mining', methods=['GET'])\ndef mining():\n # Calculate the new proof by using our PoW algorithm\n last_block = myblockchain.last_block\n last_proof = last_block['proof']\n proof = myblockchain.pow(last_proof)\n\n # For finding/mining the proof, the miner is granted a reward\n # The sender is nobody, as this coin is coming out of the void\n myblockchain.create_new_transaction(\n sender=\"0\",\n recipient=node_identifier,\n amount=1,\n )\n\n # Add the new created block to the chain\n previous_hash = myblockchain.hash(last_block)\n newblock = myblockchain.create_new_block(proof, previous_hash)\n\n output = {\n 'message': \"A new block was mined\",\n 'index': newblock['index'],\n 'transactions': newblock['transactions'],\n 'proof': newblock['proof'],\n 'previous_hash': newblock['previous_hash'],\n }\n return jsonify(output), 200\n \[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n values = request.get_json()\n\n # The POST request has to have the following required fields\n required = ['sender', 'recipient', 'amount']\n if not all(k in values for k in required):\n return 'There are values missing', 400\n\n # Adds a new transaction by utilizing our function\n index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])\n\n output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}\n return jsonify(output), 201\n \[email protected]('/nodes/add', methods=['POST'])\ndef add_nodes():\n values = request.get_json()\n\n nodes = values.get('nodes')\n if nodes is None:\n return \"You didn't post a valid list of nodes. Please double check your input!\", 400\n\n for node in nodes:\n myblockchain.add_node(node)\n\n response = {\n 'message': 'All of your specified nodes have been added to the network!',\n 'total_nodes': list(myblockchain.nodes),\n }\n return jsonify(response), 201\n\n\[email protected]('/nodes/resolve', methods=['GET'])\ndef consensus():\n replaced = myblockchain.resolve()\n\n if replaced:\n response = {\n 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',\n 'new_chain': myblockchain.blockchain\n }\n else:\n response = {\n 'message': 'The blockchain of this node is already the longest valid chain within the network.',\n 'chain': myblockchain.blockchain\n }\n\n return jsonify(response), 200\n\nif __name__ == '__main__':\n app.run(host='0.0.0.0', port=5000)\n```\n# Final thoughts\nAnd there you have it!\nYour very own blockchain. Wasn't very hard, was it?\nI sincerely hope you had fun thorughout this tutorial and were able to understand the concept and system behind it better. If you have any questions, please ask them in the comments and I'll try my best to answer them.\n# The obligatory begging\nAs this whole tutorial took me over 2 days to finish, I would love to see a lot of comments and feedback under this post. Also if you find any typos, grammar mistakes, etc. and care to tell me, i would be mor than happy about it, as English is not my native language. So please excuse any mistakes I made. I tried my very best.\nOf course please upvote, if you worship my effort to enrich the community, and if you want to see more such content, consider following me. \nI wish you only the best - See ya!",
"json_metadata": "{\"tags\":[\"steemit\",\"blockchain\",\"trevonjb\",\"cryptocurrency\",\"craigrant\"],\"links\":[\"https://www.python.org/downloads/windows/\",\"https://en.wikipedia.org/wiki/Integrated_development_environment\",\"https://www.jetbrains.com/pycharm\",\"https://www.getpostman.com/\",\"https://curl.haxx.se/\",\"https://en.wikipedia.org/wiki/SHA-2\",\"https://en.wikipedia.org/wiki/Proof-of-work_system\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "steemit",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"title": "Create your own blockchain & cryptocurrency! - understand blockchains by creating one in python - no knowledge required"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T14:08:54",
"trx_id": "1f80156352fe9f67a101ccd1e7eb3256f194e999",
"trx_in_block": 22,
"virtual_op": 0
}2018/01/06 14:07:54
2018/01/06 14:07:54
| author | dustvoice |
| body | # What exactly is a blockchain? - How does a blockchain work? - What is Bitcoin or STEEM exactly? - How does a blockchain make sure of its correctness - What is **mining** exactly? **These are only some questions, probably many of you are asking yourselves. I'm here to provide you with kind of an unusual answer. My goal is to help you undestand the whole system behind blockchains, by actually creating a small blockchain yourself!** With the rise of cryptocurrencies and decentralized systems like Bitcoin or STEEM, many people want to really undestand, what this is all about. I will show you how the system works, by coding a blockchain from scratch in python with you. The guide will be mainly for Windows, but I will also add comments regarding Linux. You don't know a whole of python, to follow this guide, but it is advised to know the most basic basics. # Get ready ## Setup Python and pip First off, we need to install Python 3.6+ along with pip, with which you can install python packages. - On **Linux**, you can install python, by simply typing `sudo apt-get install python3.6` into the terminal. If you encounter errors, please try `sudo apt-get update` and then try to execute the command again. If it still doesn't work, please search for installation instructions for your specific system on your preferred search engine. - On **Windows**, 1. first download the binary, that suits your system best, under the [official python download page for windows.](https://www.python.org/downloads/windows/). Make sure you grab at least the _version 3.6_ or higher. 2. Install the file you just downloaded. Make sure you check the option to install pip, when provided with an option to do so. If not, don't worry, it probably is enabled by default. ## IDE or not? As a next step, you want to decide, whether to utilize an [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) or not. An IDE is an environment, which supports you in the creation process of your application, by providing many features, with for example syntax highlighting, or code completion, to name just 2 possible features. My favorite IDE for Python development is probably PyCharm, which you can fetch from [this link.](https://www.jetbrains.com/pycharm) Setting up PyCharm on Windows is as simple, as downloading the executable installer and installing it with the provided setup utility. PyCharm is free tu use, as long as you use the Community Edition. You can also use your coding notepad of choice. We will only work in one file, which you have to execute, so as long as your text-editing software is able to produce files with the ending `.py`, it will work fine. ## Setup your workspace (PyCharm) If you decided to use PyCharm, simply open it up and click on _Create Project_ in the popup dialogue. In the next dialogue you want to specify your project's name. I chose to use _MyBlockChain_ as my project name. Finally click on _create_ in the bottom right corner. On your left hand side, you're now provided with a file tree. 1. Right-Click on the folder which consists of your project's name, in my case _MyBlockChain_ 2. Go to _new_ 3. Finally click on _Python File_ and 4. Define a Filename. I chose `MyBlockChain.py` as my filename. ## Create your file (Simple Text Editor) If you use a simple text editor, just create a file anywhere you like with the file extension `.py`. In my case I chose to use the filename `MyBlockChain.py`. ## Install the needed packages ### PyCharm In PyCharm you can install packages very easily and add them to your project: 1. Make sure, you have your project open 2. Go to _File_ -> _Settings_ -> _Project: [your project name]_ -> _Project Interpreter_ 3. Then click on the green plus symbol. 4. In the search field type in _Flask_ first. Select the first entry, which simply says _Flask_. Then click on Install Package in the bottom left corner. 5. Do the same thing for _Requests_. ### Pip If you chose not to use PyCharm, you simply need to open up the terminal on linux or the command prompt, or PowerShell on Windows _(simply press **Windows+R** and type in cmd)_ and then execute `pip install Flask==0.12.2 requests==2.18.4 `. If this gives you an error, please double check that you have _pip_ installed. If the problem still occurs, please troubleshoot the error message using google, as there are too many possibilities, what the error could be caused by, for me to discuss every one of them here. ## Getting a HTTP-Client You will also need a HTTP-Client, as we need to make `GET` and `POST` requests to our Flask instance, at some Point in time. Mainly, there are two options - [_Postman_](https://www.getpostman.com/) or [_cURL_](https://curl.haxx.se/). I highly recommend _Postman_, as it is easy and intuitive to use, but if you just want a simple and quick solution, without having to register for an account, just use _cURL_. Online solutions, probably won't work, as the webserver you are going to be trying to access, is just on your local machine. ## Just gimme the source code! If you just want the source code and don't want to follow my instructions, please scroll down, to the very end of this post, where I will add the source code in its complete beauty. # The Blockchain - Your Blockchain ## Step 1: Creating a simplistic Blockchain A block chain is basically a chain of blocks that are linked together in some way, as you have already figured. In this case, every block contains a hash of the previous block. A hash is a hexadecimal number that is determined by a specific algorithm. Imagine a hash being a digital fingerprint of an item. A good hash algorithm will never return the same hash for two different items, so every hash is individual. In our case we will be using the [_SHA-256_](https://en.wikipedia.org/wiki/SHA-2) algorithm. Furthermore the hash algorithm is not reversible, so you can't determine the object, by having its hash, but you can always apply the hash algorithm on the object, to get its hash. As every item has the hash of its previous item, the blockchain is protected against violation, as one would have to change every following block, as the hash changes with every change, as two different items can't have the same hash. ### The Blockchain class Our Blockchain class basically consists of a constructor and some functions. In the constructor, we will create two empty lists - one to store our blockchain in and another, for our transactions. As for now, our class will have several functions: - A function to create new blocks - A function to create new transactions - A function, which hashes a block - A function to fetch the last block The python template looks like this: ```python class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] def create_new_block(self): # This function creates a new block and appends it to the chain pass def create_new_transaction(self): # This function creates a new transaction and adds it to the list of transactions pass @staticmethod def hash(block): # This method hashes the passed block pass @property def last_block(self): # This function returns the last block in the chain pass ``` ## Step 2: The blocks As described earlier, a blockchain consists of blocks. So I will cover them first. Each Block has 5 fields: - An index, - an Unix timestamp, - a list of transactions, - a proof, - and finally the hash of the previous block. Just to clarify things, here is an example of a block ```json example_block = { 'index': 5, 'timestamp': 1515110045.123456, 'transactions': [ { 'sender': "154d8554f9541e12354e1234f512a001", 'recipient': "8654e21254c6512a51bce156542d1e5c", 'amount': 212, } ], 'proof': 488612354568, 'previous_hash': "c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e" } ``` ## Step 3: Hashing it As every block needs the hash of the previous block, we need a function, that calculates the hash of a block. To accomplish this, we simply modify our empty `hash` function ```python import hashlib import json class MyBlockChain(object): [...] @staticmethod def hash(block): """ This function hashes the block using the SHA-256 hash algorithm The function is given a block and returns a string, consisting of the hash :param block: <dict> Block :return: <str> """ # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes block_sorted = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_sorted).hexdigest() ``` ## Step 4: Transactions Our `create_new_transaction` method is responsible for adding new transactions to the `transactions` list, which will then be stored inside a block. The whole function is pretty self explanatory. But first we need a function to return the last block of the chain, as we will calculate the new index, at which the transaction will be stored, by increasing the index of the last block by 1 ```python class MyBlockChain(object): [...] @property def last_block(self): return self.blockchain[-1] ``` Now we can fill out the `create_new_transaction` function ```python class MyBlockChain(object): [...] def create_new_transaction(self, sender, recipient, amount): """ This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions. :param sender: <str> Sender's address :param recipient: <str> Recipient's address :param amount: <int> Amount to be transferred :return: <int> This is the index of the block that will contain this transaction """ self.transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 ``` ## Step 5: Creating new Blocks By now we almost have everything at our hands, what we need in order to make a new block. The only thing missing is the _proof_ field. As this is a little harder to understand, I will explain it later. But we still have one problem - if every block has the hash of the previous block, what shall we do with our first block, as there is no previous block for us to hash. This first block is called the _genesis_ block. ```python import hashlib import json from time import time class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] # Creation of the mentioned genesis block self.create_new_block(previous_hash=1, proof=100) def create_new_block(self, proof, previous_hash=None): """ Creates a new block and adds it to the blockchain :param proof: <int> This is generated by the proof of work algorithm :param previous_hash: (Optional) <str> Hash of the preleading Block :return: <dict> Return the new block """ new_block = { 'index': len(self.blockchain) + 1, 'timestamp': time(), 'transactions': self.transactions, 'proof': proof, 'previous_hash': previous_hash or self.hash(self.blockchain[-1]), } # As all pending transactions have been processed and added to the block, the list can be resetted self.transactions = [] # Add the new block to the blockchain self.blockchain.append(new_block) return new_block ``` ## Step 6: What's up with the proof?! ### The [Proof of Work (PoW) algorithm](https://en.wikipedia.org/wiki/Proof-of-work_system) This system wants to **proof** that work has been done. Now a little bit less abstract. Basically we are searching for a number that matches a specific criteria. The thing is, for the system to work, the process of finding a number matching the criteria has to be **difficult**, but the verification process has to be **quick and easy**. This is how new blocks are created, or expressed in another, more commonly used term, _mined_. #### Still too abstract? Ok here is an example: We are looking for a hash of a product of two integers that ends with a 1. So the result of the multiplication of `a` and `b` gets hashed and has to end with a 1. If we set a to `a` static value, our algorithm will determine a value for `b`, for which this criteria is matched. ```python from hashlib import sha256 a = 12 b = 0 # We don't know what b has to be. It will be determined by the algorithm while sha256(f'{a*b}'.encode()).hexdigest()[-1] != "1": b += 1 print(f'The solution is b = {b}') ``` This example outputs ``` The solution is b = 4 ``` This means that the hash of `12*4` ends with a 1 ``` hash(48) = 98010bd9270f9b100b6214a21754fd33bdc8d41b2bc9f9dd16ff54d3c34ffd71 ``` As you can tell, the verification process doesn't need countless loops, until it finds a matching pair. You just have to run the hash algorithm once on the numbers and you can easily determine, whether the numbers match the criteria, or not. ## Step 7: Implementing PoW Firstoff, we need a criteria, or commonly referred to as a rule. For now, our rule is that the hash has to have 5 leading zeroes, so `000005bc845e...` would be a valid result, but `000019ea76c4` would not. As the difficulty rises extremely, if we would require 6 leading zeros, the difficulty of our simple algorithm can be adjusted by changing the amount of leading zeroes required. This is the implementation in python: ```python [...] from uuid import uuid4 class MyBlockChain(object): [...] def pow(self, last_proof): """ Simple PoW Algorithm: - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof. :param last_proof: <int> :return: <int> """ current_proof = 0 while self.validate_proof(last_proof, current_proof) is False: current_proof += 1 return current_proof @staticmethod def validate_proof(last_proof, current_proof): """ Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes. :param last_proof: <int> Previous Proof Number :param current_proof: <int> Current Proof Number :return: <bool> """ possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest() return possible_hash[:5] == "00000" ``` ## Step 8: Interacting with our class Now we are able to create a blockchain. But we can't really interact with it, as we have only interacted with the functions and variables of our class. We are going to use HTTP requests to interact with our blockchain. So let's set it up! # HTTP in the house! To talk to our blockchain using Http requests, we need some help - which comes in the form of the _Flask Framework_. We will create and implement 3 basic methods for now: - `/blockchain` to retrieve the full blockchain - `/mining` to make our simple server mine a block - `/transactions/add` to add a new transaction to the blockchain. ## Step 9: Flask First we need to implement Flask. Here is the code: ```python [...] from textwrap import dedent from flask import Flask, jsonify, request class MyBlockChain(object): [...] app = Flask(__name__) node_identifier = str(uuid4()).replace('-', '') myblockchain = MyBlockChain() @app.route('/blockchain', methods=['GET']) def get_full_chain(): output = { 'chain': myblockchain.blockchain, 'length': len(myblockchain.blockchain), } return jsonify(output), 200 @app.route('/mining', methods=['GET']) def mining(): pass @app.route('/transactions/add', methods=['POST']) def add_transaction(): pass if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` ## Step 10: Adding Transactions As this is commonly the main task of a block chain and also required for a new block, I will cover this first. The user sends a _POST request_ to `/transactions/add` with a JSON object, containing all required fields. This is an example, for a possible, valid request: ```json { "sender": "154d8554f9541e12354e1234f512a001", "recipient": "8654e21254c6512a51bce156542d1e5c", "amount": 212 } ``` As you can see, a request has three fields: - A _sender_ field, with the sender id, - a _recipient_ field, with the recipient id - and an amount, which defines how many units are to be transferred Now we just need to implement this function ```python [...] @app.route('/transactions/add', methods=['POST']) def add_transaction(): values = request.get_json() # The POST request has to have the following required fields required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'There are values missing', 400 # Adds a new transaction by utilizing our function index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount']) output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'} return jsonify(output), 201 ``` This function simply calls the `create_new_transaction` method with the parameters of the POST request ## Step 11: Setup your mines As discussed earlier, future blocks have to be mined. If a new block is found/mined, all pending transactions are added to this block. To 'mine' a new block, our function hast to do three things 1. Calculate the proof 2. Reward the miner with a specific amount of coins (in our example 1 coin) 3. Add the new mined block to the blockchain _(The fact that the recipient is our current node, will make more sense, as we will talk about decentralization later on.)_ ```python @app.route('/mining', methods=['GET']) def mining(): # Calculate the new proof by using our PoW algorithm last_block = myblockchain.last_block last_proof = last_block['proof'] proof = myblockchain.pow(last_proof) # For finding/mining the proof, the miner is granted a reward # The sender is nobody, as this coin is coming out of the void myblockchain.create_new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Add the new created block to the chain previous_hash = myblockchain.hash(last_block) newblock = myblockchain.create_new_block(proof, previous_hash) output = { 'message': "A new block was mined", 'index': newblock['index'], 'transactions': newblock['transactions'], 'proof': newblock['proof'], 'previous_hash': newblock['previous_hash'], } return jsonify(output), 200 ``` # Hello World! Now, **finally** we will be interacting with our blockchain API. Grab your HTTP client of choice and get going! I will explain the procedure for Postman and cURL. ## Step 12: Start up the chain First off fire up your server. In PyCharm simply click on _Run_ -> _Run..._ and select _[your filename].py_ as the file to run, in my case _myblockchain.py_. If you chose to use a simple python file without an IDE, fire up a terminal or command prompt window inside the directory your file is located in and execute the following command ``` python blockchain.py ``` as a result you will see something similar to this ``` $ python blockchain.py * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) ``` **Note that you may be prompted with a dialogue of your firewall. Just accept it.** ## Step 13: Mining it all! Ok let's try the most exciting thing first, **mining**. I'm sure you can't wait to mine your first block, so let's do it! As you can see in the code ```python [...] @app.route('/mining', methods=['GET']) def mining(): [...] ``` we will be interacting with the `mining` part of our API through `GET` requests. This makes sense, as our only goal with this part of the API is, to mine a new block. We don't have to pass anything to the function at all, so a `GET` request is the way to go. ### Postman If you chose to use Postman, here is a quick tutorial on how to make a post request. 1. First you have to register for an account, if you haven't already done that 2. and login after that. 3. If you are logged in, you shoudl be provided with several options such as _Request_, _Collection_, _Environment_. 4. You could simply use the option _Request_, but to keep things organized, we are going to select the _Collection_ option. 5. After that simply choose a name for your collection, in my case I'm using _MyBlockChain_ as my name. 6. Now click on the _New_ Button, located in the top left corner and select _Request_ this time. We are now going to be creating 3 requests. 7. The first request is going to be our _mining_ request, second is a _new transaction_ request and finally we will have a _get whole blockchain_ request. Of course you can customize the request names as you like. 8. **Make sure, as you create these 3 requests, that you select our newly created Collection in the bottom section of the dialogue!** 9. Now click on our Collection's folder in the left sidebar. You should see your 3 newly created requests. Select the _mining_ request first. 10. On the right you are now provided with an interface. As you can see by the selected item from the dropdown box, left to the text field, a `GET` request is the standard request type, so we don't have to change that yet. 11. In the text field `Enter request URL` we are now going to enter the `mining` URL of our API, so enter `http://localhost:5000/mining` as the request URL. ### cURL If you chose to use cURL as your HTTP client, simply type `curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/mining` in a terminal window ### Either way With both methods you should see something similar to this as a result ```json { "index": 2, "message": "New Block Forged", "previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a", "proof": 888273, "transactions": [ { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] } ``` ## Step 14: Transactions are great! As our mining algorithm seems to be working fine, let's create a new transaction, to be stored in a fresh mined block. To do that in ### Postman 1. Click on the _new transaction_ request in the left sidebar. 2. Click on the _GET_ button with the small arrow pointing down. 3. Select _POST_ from the drop-down list. 4. In the navbar below the text field, select _Body_ 5. and then mark the _raw_ option. 6. You should now be provided with an editor like text area, and another drop-down menu next to the _binary_ option, where _Text_ is currently selected. Select _JSON (application/json)_ from this list. 7. Now type in the following into the text area. ```json { "sender": "your-node-address", "recipient": "another-node-address", "amount": 212 } ``` ### cURL Just execute `curl -X POST -H "Content-Type: application/json" -d '{"sender": "your-node-address", "recipient": "another-node-address", "amount": 212}' "http://localhost:5000/transactions/add"` in a terminal window. ### In both cases Replace `your-node-address` with the _recipient_ address from you mining request, as this is your node address. You can also select any other 32 digit hexadezimal number, for now. Furthermore replace `another-node-address` with a random 32 digit hexadezimal number for now, as we currently don't have any other nodes. Adjust the `amount` to your liking. And **voilà**! You have registered your first transaction! ## Step 15: The Chain... So let's inspect our chain! If you're using ### Postman Click on the _get whole blockchain_ request in the left sidebar and simply enter `http://localhost:5000/blockchain` in the `Enter request URL` text field, as _GET_ should be preselected. If not, please change it to the _GET_ option. ### cURL just execute `curl -i -H "Content-Type: application/json" -X GET http://localhost:5000/blockchain` in a terminal window. ### Either way you should get your whole blockchain in form of a JSON-Object. In my case, my blockchain looks like this, after mining 2 times, adding 3 transactions and mining another block: ```json { "chain": [ { "index": 1, "previous_hash": "1", "proof": 100, "timestamp": 1515156902.4502413, "transactions": [] }, { "index": 2, "previous_hash": "0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a", "proof": 888273, "timestamp": 1515156913.2686973, "transactions": [ { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] }, { "index": 3, "previous_hash": "1d2ae4a41f4a82ce6f04944e8227bf9c4d951d560f8763b910d7314634dfe09c", "proof": 1156297, "timestamp": 1515158367.3596537, "transactions": [ { "amount": 212, "recipient": "d4ee26eee15148ee92c6cd394edd974e", "sender": "491002ea048a42609d967027cc46a07b" }, { "amount": 212, "recipient": "5654c5654e1b551a65e15f5e4651c156", "sender": "491002ea048a42609d967027cc46a07b" }, { "amount": 111, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "5654c5654e1b551a65e15f5e4651c156" }, { "amount": 1, "recipient": "491002ea048a42609d967027cc46a07b", "sender": "0" } ] } ], "length": 3 } ``` # Decentralizing it Ok, so we got a blockchain, that's working fine and we can interact with it. But one very important thing is still missing: **Decentralization**. This is a core element of the blockchain idea. First off by decentralizing the whole thing, safety and integrity of the blockchain is guaranteed, as a change in the blockchain will be recognized, by the other nodes, as they also have a copy of the blockchain, and discarded. The second reason is that we somehow have to make sense of our transactions. For this we need other nodes and why shouldn't they contribute to our network, by being a part of the blockchain system?! But we still have a conflict there: What if a node has a blockchain that differs from all the others? For our simple blockchain we will always make the **longest valid** blockchain authorative. ## Step 16: Someone out there?! But first things first. To get started, we first have to implement other nodes into our system. To accomplish this task, we have to adjust a few things in our API. First off, we are going to implement some new endpoints: 1. `/nodes/add` to add new nodes in form of a list of URLs 2. `/nodes/resolve` to resolve the conflict we discussed previously by using a consensus algorithm. To do this, we have to modify our Blockchain class first: ```python [...] from urllib.parse import urlparse class MyBlockChain(object): def __init__(self): [...] self.nodes = set() def add_node(self, address): """ Register a new node :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000' :return: None """ node_url = urlparse(address) self.nodes.add(node_url.netloc) ``` ## Step 17: We need Consensus To resolve the conflict of differing blockchains between the nodes, we are going to implement a consensus algorithm. As we discussed earlier, we are always utilizing the longest valid blockchain. First we are implementing a function that validates a blockchain: ```python [...] import requests class MyBlockChain(object) [...] def validate_blockchain(self, test_chain): """ Evaluate, whether a blockchain is valid :param chain: <list> The blockchain that shall be tested :return: <bool> Returns true if the blockchain is valid """ last_block = test_chain[0] current_index = 1 while current_index < len(test_chain): block = test_chain[current_index] # Determine, whether the hash is correct if block['previous_hash'] != self.hash(last_block): return False # Determine, wheter the proof of work is correct if not self.validate_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True ``` Now we just have to run every blockchain in our surrounding network through this consensus algorithm: ```python [...] class MyBlockChain(object) [...] def resolve(self): """ This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously. :return: <bool> Returns true, if our chain was substituted with an updated version """ surrounding_nodes = self.nodes updated_chain = None # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain max_length = len(self.blockchain) # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain. for node_iterator in surrounding_nodes: response = requests.get(f'http://{node_iterator}/blockchain') if response.status_code == 200: length = response.json()['length'] chain_iterator = response.json()['chain'] if length > max_length and self.validate_blockchain(chain_iterator): max_length = length updated_chain = chain_iterator # If we found a new blockchain, replace our current blockchain with the new one if updated_chain: self.blockchain = updated_chain return True return False ``` Now we just have to register our new functions as new endpoints in the API: ```python @app.route('/nodes/add', methods=['POST']) def add_nodes(): values = request.get_json() nodes = values.get('nodes') if nodes is None: return "You didn't post a valid list of nodes. Please double check your input!", 400 for node in nodes: myblockchain.add_node(node) response = { 'message': 'All of your specified nodes have been added to the network!', 'total_nodes': list(myblockchain.nodes), } return jsonify(response), 201 @app.route('/nodes/resolve', methods=['GET']) def consensus(): replaced = myblockchain.resolve() if replaced: response = { 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.', 'new_chain': myblockchain.blockchain } else: response = { 'message': 'The blockchain of this node is already the longest valid chain within the network.', 'chain': myblockchain.blockchain } return jsonify(response), 200 ``` ## Step 18: Friends are great. Even if they're virtual... To test our decentralization algorithms, either grab some friends, to run some nodes for you, or just run the same program on different ports, so you would have to start up an instance of our blockchain-program, then change to port to 5001 for example, save, fire up an instance of this _modified_ program, change the port to 5002, etc. ## Step 19: Add them to your friends list If you got some instances, you just have to register them. To do this, simply send a `POST` request with the following content ```json { "nodes": ["http://[ip-of-another-node-or-127.0.0.1-for-your-local-machine]:[the-port-number]"] } ``` to `http://[the-node-ip-you-want-to-register-it-to]:[port]/nodes/add` ## Step 20: Consensus check After that try to mine some blocks and even add some transactions on let's say node 2 and then run `http://[a-node-ip]:[port]/nodes/resolve` on let's say node 1. The consensus algorithm should step into action and update the blockchain of node 1. # The whole thang I keep my promises ```python import hashlib import json import requests from time import time from uuid import uuid4 from textwrap import dedent from flask import Flask, jsonify, request from urllib.parse import urlparse class MyBlockChain(object): def __init__(self): self.blockchain = [] self.transactions = [] # Creation of the mentioned genesis block self.create_new_block(previous_hash=1, proof=100) self.nodes = set() def create_new_block(self, proof, previous_hash=None): """ Creates a new block and adds it to the blockchain :param proof: <int> This is generated by the proof of work algorithm :param previous_hash: (Optional) <str> Hash of the preleading Block :return: <dict> Return the new block """ new_block = { 'index': len(self.blockchain) + 1, 'timestamp': time(), 'transactions': self.transactions, 'proof': proof, 'previous_hash': previous_hash or self.hash(self.blockchain[-1]), } # As all pending transactions have been processed and added to the block, the list can be resetted self.transactions = [] # Add the new block to the blockchain self.blockchain.append(new_block) return new_block def create_new_transaction(self, sender, recipient, amount): """ This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions. :param sender: <str> Sender's address :param recipient: <str> Recipient's address :param amount: <int> Amount to be transferred :return: <int> This is the index of the block that will contain this transaction """ self.transactions.append({ 'sender': sender, 'recipient': recipient, 'amount': amount, }) return self.last_block['index'] + 1 @staticmethod def hash(block): """ This function hashes the block using the SHA-256 hash algorithm The function is given a block and returns a string, consisting of the hash :param block: <dict> Block :return: <str> """ # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes block_sorted = json.dumps(block, sort_keys=True).encode() return hashlib.sha256(block_sorted).hexdigest() @property def last_block(self): return self.blockchain[-1] def pow(self, last_proof): """ Simple PoW Algorithm: - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof. :param last_proof: <int> :return: <int> """ current_proof = 0 while self.validate_proof(last_proof, current_proof) is False: current_proof += 1 return current_proof @staticmethod def validate_proof(last_proof, current_proof): """ Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes. :param last_proof: <int> Previous Proof Number :param current_proof: <int> Current Proof Number :return: <bool> """ possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest() return possible_hash[:5] == "00000" def add_node(self, address): """ Register a new node :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000' :return: None """ node_url = urlparse(address) self.nodes.add(node_url.netloc) def validate_blockchain(self, test_chain): """ Evaluate, whether a blockchain is valid :param chain: <list> The blockchain that shall be tested :return: <bool> Returns true if the blockchain is valid """ last_block = test_chain[0] current_index = 1 while current_index < len(test_chain): block = test_chain[current_index] # Determine, whether the hash is correct if block['previous_hash'] != self.hash(last_block): return False # Determine, wheter the proof of work is correct if not self.validate_proof(last_block['proof'], block['proof']): return False last_block = block current_index += 1 return True def resolve(self): """ This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously. :return: <bool> Returns true, if our chain was substituted with an updated version """ surrounding_nodes = self.nodes updated_chain = None # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain max_length = len(self.blockchain) # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain. for node_iterator in surrounding_nodes: response = requests.get(f'http://{node_iterator}/blockchain') if response.status_code == 200: length = response.json()['length'] chain_iterator = response.json()['chain'] if length > max_length and self.validate_blockchain(chain_iterator): max_length = length updated_chain = chain_iterator # If we found a new blockchain, replace our current blockchain with the new one if updated_chain: self.blockchain = updated_chain return True return False app = Flask(__name__) node_identifier = str(uuid4()).replace('-', '') myblockchain = MyBlockChain() @app.route('/blockchain', methods=['GET']) def get_full_chain(): output = { 'chain': myblockchain.blockchain, 'length': len(myblockchain.blockchain), } return jsonify(output), 200 @app.route('/mining', methods=['GET']) def mining(): # Calculate the new proof by using our PoW algorithm last_block = myblockchain.last_block last_proof = last_block['proof'] proof = myblockchain.pow(last_proof) # For finding/mining the proof, the miner is granted a reward # The sender is nobody, as this coin is coming out of the void myblockchain.create_new_transaction( sender="0", recipient=node_identifier, amount=1, ) # Add the new created block to the chain previous_hash = myblockchain.hash(last_block) newblock = myblockchain.create_new_block(proof, previous_hash) output = { 'message': "A new block was mined", 'index': newblock['index'], 'transactions': newblock['transactions'], 'proof': newblock['proof'], 'previous_hash': newblock['previous_hash'], } return jsonify(output), 200 @app.route('/transactions/add', methods=['POST']) def add_transaction(): values = request.get_json() # The POST request has to have the following required fields required = ['sender', 'recipient', 'amount'] if not all(k in values for k in required): return 'There are values missing', 400 # Adds a new transaction by utilizing our function index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount']) output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'} return jsonify(output), 201 @app.route('/nodes/add', methods=['POST']) def add_nodes(): values = request.get_json() nodes = values.get('nodes') if nodes is None: return "You didn't post a valid list of nodes. Please double check your input!", 400 for node in nodes: myblockchain.add_node(node) response = { 'message': 'All of your specified nodes have been added to the network!', 'total_nodes': list(myblockchain.nodes), } return jsonify(response), 201 @app.route('/nodes/resolve', methods=['GET']) def consensus(): replaced = myblockchain.resolve() if replaced: response = { 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.', 'new_chain': myblockchain.blockchain } else: response = { 'message': 'The blockchain of this node is already the longest valid chain within the network.', 'chain': myblockchain.blockchain } return jsonify(response), 200 if __name__ == '__main__': app.run(host='0.0.0.0', port=5000) ``` # Final thoughts And there you have it! Your very own blockchain. Wasn't very hard, was it? I sincerely hope you had fun thorughout this tutorial and were able to understand the concept and system behind it better. If you have any questions, please ask them in the comments and I'll try my best to answer them. # The obligatory begging As this whole tutorial took me over 2 days to finish, I would love to see a lot of comments and feedback under this post. Also if you find any typos, grammar mistakes, etc. and care to tell me, i would be mor than happy about it, as English is not my native language. So please excuse any mistakes I made. I tried my very best. Of course please upvote, if you worship my effort to enrich the community, and if you want to see more such content, consider following me. I wish you only the best - See ya! |
| json metadata | {"tags":["steemit","blockchain","trevonjb","cryptocurrency","upvoteforupvote"],"links":["https://www.python.org/downloads/windows/","https://en.wikipedia.org/wiki/Integrated_development_environment","https://www.jetbrains.com/pycharm","https://www.getpostman.com/","https://curl.haxx.se/","https://en.wikipedia.org/wiki/SHA-2","https://en.wikipedia.org/wiki/Proof-of-work_system"],"app":"steemit/0.1","format":"markdown"} |
| parent author | |
| parent permlink | steemit |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| title | Create your own blockchain & cryptocurrency! - understand blockchains by creating one in python - no knowledge required |
| Transaction Info | Block #18742267/Trx 0db3d5121f95452bf55e53ad31c21e9b92ec940b |
View Raw JSON Data
{
"block": 18742267,
"op": [
"comment",
{
"author": "dustvoice",
"body": "# What exactly is a blockchain?\n- How does a blockchain work?\n- What is Bitcoin or STEEM exactly?\n- How does a blockchain make sure of its correctness\n- What is **mining** exactly?\n\n**These are only some questions, probably many of you are asking yourselves. \nI'm here to provide you with kind of an unusual answer.\nMy goal is to help you undestand the whole system behind blockchains, by actually creating a small blockchain yourself!**\n\nWith the rise of cryptocurrencies and decentralized systems like Bitcoin or STEEM, many people want to really undestand, what this is all about. I will show you how the system works, by coding a blockchain from scratch in python with you. The guide will be mainly for Windows, but I will also add comments regarding Linux. You don't know a whole of python, to follow this guide, but it is advised to know the most basic basics.\n\n# Get ready\n## Setup Python and pip\nFirst off, we need to install Python 3.6+ along with pip, with which you can install python packages. \n- On **Linux**, you can install python, by simply typing `sudo apt-get install python3.6` into the terminal. If you encounter errors, please try `sudo apt-get update` and then try to execute the command again. If it still doesn't work, please search for installation instructions for your specific system on your preferred search engine.\n- On **Windows**,\n 1. first download the binary, that suits your system best, under the [official python download page for windows.](https://www.python.org/downloads/windows/). Make sure you grab at least the _version 3.6_ or higher.\n 2. Install the file you just downloaded. Make sure you check the option to install pip, when provided with an option to do so. If not, don't worry, it probably is enabled by default.\n\n## IDE or not?\nAs a next step, you want to decide, whether to utilize an [IDE](https://en.wikipedia.org/wiki/Integrated_development_environment) or not. An IDE is an environment, which supports you in the creation process of your application, by providing many features, with for example syntax highlighting, or code completion, to name just 2 possible features. My favorite IDE for Python development is probably PyCharm, which you can fetch from [this link.](https://www.jetbrains.com/pycharm) Setting up PyCharm on Windows is as simple, as downloading the executable installer and installing it with the provided setup utility. PyCharm is free tu use, as long as you use the Community Edition.\nYou can also use your coding notepad of choice. We will only work in one file, which you have to execute, so as long as your text-editing software is able to produce files with the ending `.py`, it will work fine.\n\n## Setup your workspace (PyCharm)\nIf you decided to use PyCharm, simply open it up and click on _Create Project_ in the popup dialogue. In the next dialogue you want to specify your project's name. I chose to use _MyBlockChain_ as my project name. Finally click on _create_ in the bottom right corner. On your left hand side, you're now provided with a file tree. \n1. Right-Click on the folder which consists of your project's name, in my case _MyBlockChain_\n2. Go to _new_ \n3. Finally click on _Python File_ and\n4. Define a Filename. I chose `MyBlockChain.py` as my filename.\n\n## Create your file (Simple Text Editor)\nIf you use a simple text editor, just create a file anywhere you like with the file extension `.py`. In my case I chose to use the filename `MyBlockChain.py`.\n\n## Install the needed packages\n### PyCharm\nIn PyCharm you can install packages very easily and add them to your project:\n1. Make sure, you have your project open\n2. Go to _File_ -> _Settings_ -> _Project: [your project name]_ -> _Project Interpreter_\n3. Then click on the green plus symbol.\n4. In the search field type in _Flask_ first. Select the first entry, which simply says _Flask_. Then click on Install Package in the bottom left corner.\n5. Do the same thing for _Requests_.\n### Pip\nIf you chose not to use PyCharm, you simply need to open up the terminal on linux or the command prompt, or PowerShell on Windows _(simply press **Windows+R** and type in cmd)_ and then execute `pip install Flask==0.12.2 requests==2.18.4 `. If this gives you an error, please double check that you have _pip_ installed. If the problem still occurs, please troubleshoot the error message using google, as there are too many possibilities, what the error could be caused by, for me to discuss every one of them here.\n\n## Getting a HTTP-Client\nYou will also need a HTTP-Client, as we need to make `GET` and `POST` requests to our Flask instance, at some Point in time. Mainly, there are two options - [_Postman_](https://www.getpostman.com/) or [_cURL_](https://curl.haxx.se/). I highly recommend _Postman_, as it is easy and intuitive to use, but if you just want a simple and quick solution, without having to register for an account, just use _cURL_. Online solutions, probably won't work, as the webserver you are going to be trying to access, is just on your local machine. \n\n## Just gimme the source code!\nIf you just want the source code and don't want to follow my instructions, please scroll down, to the very end of this post, where I will add the source code in its complete beauty.\n\n# The Blockchain - Your Blockchain\n## Step 1: Creating a simplistic Blockchain\nA block chain is basically a chain of blocks that are linked together in some way, as you have already figured. In this case, every block contains a hash of the previous block. A hash is a hexadecimal number that is determined by a specific algorithm. Imagine a hash being a digital fingerprint of an item. A good hash algorithm will never return the same hash for two different items, so every hash is individual. In our case we will be using the [_SHA-256_](https://en.wikipedia.org/wiki/SHA-2) algorithm. Furthermore the hash algorithm is not reversible, so you can't determine the object, by having its hash, but you can always apply the hash algorithm on the object, to get its hash. As every item has the hash of its previous item, the blockchain is protected against violation, as one would have to change every following block, as the hash changes with every change, as two different items can't have the same hash.\n### The Blockchain class\nOur Blockchain class basically consists of a constructor and some functions. In the constructor, we will create two empty lists - one to store our blockchain in and another, for our transactions. As for now, our class will have several functions:\n- A function to create new blocks\n- A function to create new transactions\n- A function, which hashes a block\n- A function to fetch the last block\nThe python template looks like this:\n```python\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n \n def create_new_block(self):\n # This function creates a new block and appends it to the chain\n pass\n \n def create_new_transaction(self):\n # This function creates a new transaction and adds it to the list of transactions\n pass\n \n @staticmethod\n def hash(block):\n # This method hashes the passed block\n pass\n\n @property\n def last_block(self):\n # This function returns the last block in the chain\n pass\n```\n## Step 2: The blocks\nAs described earlier, a blockchain consists of blocks. So I will cover them first.\nEach Block has 5 fields:\n- An index,\n- an Unix timestamp,\n- a list of transactions,\n- a proof,\n- and finally the hash of the previous block.\nJust to clarify things, here is an example of a block\n```json\nexample_block = {\n 'index': 5,\n 'timestamp': 1515110045.123456,\n 'transactions': \n [\n {\n 'sender': \"154d8554f9541e12354e1234f512a001\",\n 'recipient': \"8654e21254c6512a51bce156542d1e5c\",\n 'amount': 212,\n }\n ],\n 'proof': 488612354568,\n 'previous_hash': \"c7be1ed902fb8dd4d48997c6452f5d7e509fbcdbe2808b16bcf4edce4c07d14e\"\n}\n```\n## Step 3: Hashing it\nAs every block needs the hash of the previous block, we need a function, that calculates the hash of a block. To accomplish this, we simply modify our empty `hash` function\n```python\nimport hashlib\nimport json\n\nclass MyBlockChain(object):\n [...]\n @staticmethod\n def hash(block):\n \"\"\"\n This function hashes the block using the SHA-256 hash algorithm\n The function is given a block and returns a string, consisting of the hash\n :param block: <dict> Block\n :return: <str>\n \"\"\"\n\n # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes\n block_sorted = json.dumps(block, sort_keys=True).encode()\n return hashlib.sha256(block_sorted).hexdigest()\n```\n## Step 4: Transactions\nOur `create_new_transaction` method is responsible for adding new transactions to the `transactions` list, which will then be stored inside a block. The whole function is pretty self explanatory. But first we need a function to return the last block of the chain, as we will calculate the new index, at which the transaction will be stored, by increasing the index of the last block by 1\n```python\nclass MyBlockChain(object):\n [...]\n @property\n def last_block(self):\n return self.blockchain[-1]\n```\nNow we can fill out the `create_new_transaction` function\n```python\nclass MyBlockChain(object):\n [...]\n def create_new_transaction(self, sender, recipient, amount):\n \"\"\"\n This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.\n \n :param sender: <str> Sender's address\n :param recipient: <str> Recipient's address\n :param amount: <int> Amount to be transferred\n :return: <int> This is the index of the block that will contain this transaction\n \"\"\"\n\n self.transactions.append({\n 'sender': sender,\n 'recipient': recipient,\n 'amount': amount,\n })\n\n return self.last_block['index'] + 1\n```\n## Step 5: Creating new Blocks\nBy now we almost have everything at our hands, what we need in order to make a new block. The only thing missing is the _proof_ field. As this is a little harder to understand, I will explain it later. \nBut we still have one problem - if every block has the hash of the previous block, what shall we do with our first block, as there is no previous block for us to hash. \nThis first block is called the _genesis_ block.\n```python\nimport hashlib\nimport json\nfrom time import time\n\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n\n # Creation of the mentioned genesis block\n self.create_new_block(previous_hash=1, proof=100)\n\n def create_new_block(self, proof, previous_hash=None):\n \"\"\"\n Creates a new block and adds it to the blockchain\n :param proof: <int> This is generated by the proof of work algorithm\n :param previous_hash: (Optional) <str> Hash of the preleading Block\n :return: <dict> Return the new block\n \"\"\"\n\n new_block = {\n 'index': len(self.blockchain) + 1,\n 'timestamp': time(),\n 'transactions': self.transactions,\n 'proof': proof,\n 'previous_hash': previous_hash or self.hash(self.blockchain[-1]),\n }\n\n # As all pending transactions have been processed and added to the block, the list can be resetted\n self.transactions = []\n \n # Add the new block to the blockchain\n self.blockchain.append(new_block)\n return new_block\n```\n## Step 6: What's up with the proof?!\n### The [Proof of Work (PoW) algorithm](https://en.wikipedia.org/wiki/Proof-of-work_system)\nThis system wants to **proof** that work has been done. Now a little bit less abstract. Basically we are searching for a number that matches a specific criteria. The thing is, for the system to work, the process of finding a number matching the criteria has to be **difficult**, but the verification process has to be **quick and easy**. This is how new blocks are created, or expressed in another, more commonly used term, _mined_.\n#### Still too abstract? Ok here is an example:\nWe are looking for a hash of a product of two integers that ends with a 1.\nSo the result of the multiplication of `a` and `b` gets hashed and has to end with a 1. If we set a to `a` static value, our algorithm will determine a value for `b`, for which this criteria is matched. \n```python\nfrom hashlib import sha256\na = 12\nb = 0 # We don't know what b has to be. It will be determined by the algorithm\nwhile sha256(f'{a*b}'.encode()).hexdigest()[-1] != \"1\":\n b += 1\nprint(f'The solution is b = {b}')\n```\nThis example outputs\n```\nThe solution is b = 4\n```\nThis means that the hash of `12*4` ends with a 1\n```\nhash(48) = 98010bd9270f9b100b6214a21754fd33bdc8d41b2bc9f9dd16ff54d3c34ffd71\n```\nAs you can tell, the verification process doesn't need countless loops, until it finds a matching pair. You just have to run the hash algorithm once on the numbers and you can easily determine, whether the numbers match the criteria, or not.\n## Step 7: Implementing PoW\nFirstoff, we need a criteria, or commonly referred to as a rule. For now, our rule is that the hash has to have 5 leading zeroes, so `000005bc845e...` would be a valid result, but `000019ea76c4` would not. As the difficulty rises extremely, if we would require 6 leading zeros, the difficulty of our simple algorithm can be adjusted by changing the amount of leading zeroes required. This is the implementation in python:\n```python\n[...]\nfrom uuid import uuid4\n\nclass MyBlockChain(object):\n [...]\n \n def pow(self, last_proof):\n \"\"\"\n Simple PoW Algorithm:\n - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.\n :param last_proof: <int>\n :return: <int>\n \"\"\"\n \n current_proof = 0\n while self.validate_proof(last_proof, current_proof) is False:\n current_proof += 1\n\n return current_proof\n\n @staticmethod\n def validate_proof(last_proof, current_proof):\n \"\"\"\n Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.\n :param last_proof: <int> Previous Proof Number\n :param current_proof: <int> Current Proof Number\n :return: <bool>\n \"\"\"\n\n possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()\n return possible_hash[:5] == \"00000\"\n```\n## Step 8: Interacting with our class\nNow we are able to create a blockchain. But we can't really interact with it, as we have only interacted with the functions and variables of our class.\nWe are going to use HTTP requests to interact with our blockchain. So let's set it up!\n# HTTP in the house!\nTo talk to our blockchain using Http requests, we need some help - which comes in the form of the _Flask Framework_. We will create and implement 3 basic methods for now:\n- `/blockchain` to retrieve the full blockchain\n- `/mining` to make our simple server mine a block\n- `/transactions/add` to add a new transaction to the blockchain.\n## Step 9: Flask\nFirst we need to implement Flask. Here is the code:\n```python\n[...]\nfrom textwrap import dedent\nfrom flask import Flask, jsonify, request\n\nclass MyBlockChain(object):\n [...]\n \napp = Flask(__name__)\nnode_identifier = str(uuid4()).replace('-', '')\n\nmyblockchain = MyBlockChain()\n\[email protected]('/blockchain', methods=['GET'])\ndef get_full_chain():\n output = {\n 'chain': myblockchain.blockchain,\n 'length': len(myblockchain.blockchain),\n }\n return jsonify(output), 200\n\[email protected]('/mining', methods=['GET'])\ndef mining():\n pass\[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n pass\nif __name__ == '__main__':\n app.run(host='0.0.0.0', port=5000)\n```\n## Step 10: Adding Transactions\nAs this is commonly the main task of a block chain and also required for a new block, I will cover this first. The user sends a _POST request_ to `/transactions/add` with a JSON object, containing all required fields. This is an example, for a possible, valid request:\n```json\n{\n \"sender\": \"154d8554f9541e12354e1234f512a001\",\n \"recipient\": \"8654e21254c6512a51bce156542d1e5c\",\n \"amount\": 212\n}\n```\nAs you can see, a request has three fields:\n- A _sender_ field, with the sender id,\n- a _recipient_ field, with the recipient id\n- and an amount, which defines how many units are to be transferred\nNow we just need to implement this function\n```python\n[...]\n\[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n values = request.get_json()\n\n # The POST request has to have the following required fields\n required = ['sender', 'recipient', 'amount']\n if not all(k in values for k in required):\n return 'There are values missing', 400\n\n # Adds a new transaction by utilizing our function\n index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])\n\n output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}\n return jsonify(output), 201\n```\nThis function simply calls the `create_new_transaction` method with the parameters of the POST request\n## Step 11: Setup your mines\nAs discussed earlier, future blocks have to be mined. If a new block is found/mined, all pending transactions are added to this block. To 'mine' a new block, our function hast to do three things\n1. Calculate the proof\n2. Reward the miner with a specific amount of coins (in our example 1 coin)\n3. Add the new mined block to the blockchain\n_(The fact that the recipient is our current node, will make more sense, as we will talk about decentralization later on.)_\n```python\[email protected]('/mining', methods=['GET'])\ndef mining():\n # Calculate the new proof by using our PoW algorithm\n last_block = myblockchain.last_block\n last_proof = last_block['proof']\n proof = myblockchain.pow(last_proof)\n\n # For finding/mining the proof, the miner is granted a reward\n # The sender is nobody, as this coin is coming out of the void\n myblockchain.create_new_transaction(\n sender=\"0\",\n recipient=node_identifier,\n amount=1,\n )\n\n # Add the new created block to the chain\n previous_hash = myblockchain.hash(last_block)\n newblock = myblockchain.create_new_block(proof, previous_hash)\n\n output = {\n 'message': \"A new block was mined\",\n 'index': newblock['index'],\n 'transactions': newblock['transactions'],\n 'proof': newblock['proof'],\n 'previous_hash': newblock['previous_hash'],\n }\n return jsonify(output), 200\n```\n# Hello World!\nNow, **finally** we will be interacting with our blockchain API. Grab your HTTP client of choice and get going! I will explain the procedure for Postman and cURL.\n## Step 12: Start up the chain\nFirst off fire up your server. \nIn PyCharm simply click on _Run_ -> _Run..._ and select _[your filename].py_ as the file to run, in my case _myblockchain.py_.\nIf you chose to use a simple python file without an IDE, fire up a terminal or command prompt window inside the directory your file is located in and execute the following command\n```\npython blockchain.py\n```\nas a result you will see something similar to this\n```\n$ python blockchain.py\n* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)\n```\n**Note that you may be prompted with a dialogue of your firewall. Just accept it.**\n## Step 13: Mining it all!\nOk let's try the most exciting thing first, **mining**. I'm sure you can't wait to mine your first block, so let's do it!\nAs you can see in the code\n```python\n[...]\[email protected]('/mining', methods=['GET'])\ndef mining():\n [...]\n```\nwe will be interacting with the `mining` part of our API through `GET` requests. This makes sense, as our only goal with this part of the API is, to mine a new block. We don't have to pass anything to the function at all, so a `GET` request is the way to go.\n### Postman\nIf you chose to use Postman, here is a quick tutorial on how to make a post request. \n1. First you have to register for an account, if you haven't already done that\n2. and login after that. \n3. If you are logged in, you shoudl be provided with several options such as _Request_, _Collection_, _Environment_.\n4. You could simply use the option _Request_, but to keep things organized, we are going to select the _Collection_ option. \n5. After that simply choose a name for your collection, in my case I'm using _MyBlockChain_ as my name.\n6. Now click on the _New_ Button, located in the top left corner and select _Request_ this time. We are now going to be creating 3 requests.\n7. The first request is going to be our _mining_ request, second is a _new transaction_ request and finally we will have a _get whole blockchain_ request. Of course you can customize the request names as you like. \n8. **Make sure, as you create these 3 requests, that you select our newly created Collection in the bottom section of the dialogue!**\n9. Now click on our Collection's folder in the left sidebar. You should see your 3 newly created requests. Select the _mining_ request first.\n10. On the right you are now provided with an interface. As you can see by the selected item from the dropdown box, left to the text field, a `GET` request is the standard request type, so we don't have to change that yet. \n11. In the text field `Enter request URL` we are now going to enter the `mining` URL of our API, so enter `http://localhost:5000/mining` as the request URL.\n### cURL\nIf you chose to use cURL as your HTTP client, simply type `curl -i -H \"Content-Type: application/json\" -X GET http://localhost:5000/mining` in a terminal window\n### Either way\nWith both methods you should see something similar to this as a result\n```json\n{\n \"index\": 2,\n \"message\": \"New Block Forged\",\n \"previous_hash\": \"0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a\",\n \"proof\": 888273,\n \"transactions\": [\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n}\n```\n## Step 14: Transactions are great!\nAs our mining algorithm seems to be working fine, let's create a new transaction, to be stored in a fresh mined block. To do that in\n### Postman\n1. Click on the _new transaction_ request in the left sidebar.\n2. Click on the _GET_ button with the small arrow pointing down.\n3. Select _POST_ from the drop-down list.\n4. In the navbar below the text field, select _Body_ \n5. and then mark the _raw_ option.\n6. You should now be provided with an editor like text area, and another drop-down menu next to the _binary_ option, where _Text_ is currently selected. Select _JSON (application/json)_ from this list.\n7. Now type in the following into the text area.\n```json\n{\n \"sender\": \"your-node-address\",\n \"recipient\": \"another-node-address\",\n \"amount\": 212\n}\n```\n### cURL\nJust execute `curl -X POST -H \"Content-Type: application/json\" -d '{\"sender\": \"your-node-address\", \"recipient\": \"another-node-address\", \"amount\": 212}' \"http://localhost:5000/transactions/add\"` in a terminal window.\n### In both cases\nReplace `your-node-address` with the _recipient_ address from you mining request, as this is your node address. You can also select any other 32 digit hexadezimal number, for now. Furthermore replace `another-node-address` with a random 32 digit hexadezimal number for now, as we currently don't have any other nodes.\nAdjust the `amount` to your liking.\nAnd **voilà**! You have registered your first transaction!\n## Step 15: The Chain...\nSo let's inspect our chain! If you're using\n### Postman\nClick on the _get whole blockchain_ request in the left sidebar and simply enter `http://localhost:5000/blockchain` in the `Enter request URL` text field, as _GET_ should be preselected. If not, please change it to the _GET_ option.\n### cURL\njust execute `curl -i -H \"Content-Type: application/json\" -X GET http://localhost:5000/blockchain` in a terminal window.\n### Either way\nyou should get your whole blockchain in form of a JSON-Object. In my case, my blockchain looks like this, after mining 2 times, adding 3 transactions and mining another block:\n```json\n{\n \"chain\": [\n {\n \"index\": 1,\n \"previous_hash\": \"1\",\n \"proof\": 100,\n \"timestamp\": 1515156902.4502413,\n \"transactions\": []\n },\n {\n \"index\": 2,\n \"previous_hash\": \"0f9cb2f06100899cb31b85fef221e243d1088c0aa6840a568e58d4a27dbd186a\",\n \"proof\": 888273,\n \"timestamp\": 1515156913.2686973,\n \"transactions\": [\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n },\n {\n \"index\": 3,\n \"previous_hash\": \"1d2ae4a41f4a82ce6f04944e8227bf9c4d951d560f8763b910d7314634dfe09c\",\n \"proof\": 1156297,\n \"timestamp\": 1515158367.3596537,\n \"transactions\": [\n {\n \"amount\": 212,\n \"recipient\": \"d4ee26eee15148ee92c6cd394edd974e\",\n \"sender\": \"491002ea048a42609d967027cc46a07b\"\n },\n {\n \"amount\": 212,\n \"recipient\": \"5654c5654e1b551a65e15f5e4651c156\",\n \"sender\": \"491002ea048a42609d967027cc46a07b\"\n },\n {\n \"amount\": 111,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"5654c5654e1b551a65e15f5e4651c156\"\n },\n {\n \"amount\": 1,\n \"recipient\": \"491002ea048a42609d967027cc46a07b\",\n \"sender\": \"0\"\n }\n ]\n }\n ],\n \"length\": 3\n}\n```\n# Decentralizing it\nOk, so we got a blockchain, that's working fine and we can interact with it. But one very important thing is still missing: **Decentralization**. \nThis is a core element of the blockchain idea. \nFirst off by decentralizing the whole thing, safety and integrity of the blockchain is guaranteed, as a change in the blockchain will be recognized, by the other nodes, as they also have a copy of the blockchain, and discarded. \nThe second reason is that we somehow have to make sense of our transactions. For this we need other nodes and why shouldn't they contribute to our network, by being a part of the blockchain system?! \nBut we still have a conflict there: What if a node has a blockchain that differs from all the others? \nFor our simple blockchain we will always make the **longest valid** blockchain authorative.\n## Step 16: Someone out there?!\nBut first things first. \nTo get started, we first have to implement other nodes into our system.\nTo accomplish this task, we have to adjust a few things in our API. First off, we are going to implement some new endpoints:\n1. `/nodes/add` to add new nodes in form of a list of URLs\n2. `/nodes/resolve` to resolve the conflict we discussed previously by using a consensus algorithm.\nTo do this, we have to modify our Blockchain class first:\n```python\n[...]\nfrom urllib.parse import urlparse\n\nclass MyBlockChain(object):\n def __init__(self):\n [...]\n self.nodes = set()\n\n def add_node(self, address):\n \"\"\"\n Register a new node\n :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'\n :return: None\n \"\"\"\n\n node_url = urlparse(address)\n self.nodes.add(node_url.netloc)\n```\n## Step 17: We need Consensus\nTo resolve the conflict of differing blockchains between the nodes, we are going to implement a consensus algorithm. As we discussed earlier, we are always utilizing the longest valid blockchain.\nFirst we are implementing a function that validates a blockchain:\n```python\n[...]\nimport requests\n\nclass MyBlockChain(object)\n [...]\n \n def validate_blockchain(self, test_chain):\n \"\"\"\n Evaluate, whether a blockchain is valid\n :param chain: <list> The blockchain that shall be tested\n :return: <bool> Returns true if the blockchain is valid\n \"\"\"\n\n last_block = test_chain[0]\n current_index = 1\n\n while current_index < len(test_chain):\n block = test_chain[current_index]\n \n # Determine, whether the hash is correct\n if block['previous_hash'] != self.hash(last_block):\n return False\n\n # Determine, wheter the proof of work is correct\n if not self.validate_proof(last_block['proof'], block['proof']):\n return False\n\n last_block = block\n current_index += 1\n\n return True\n```\nNow we just have to run every blockchain in our surrounding network through this consensus algorithm:\n```python\n [...]\n\nclass MyBlockChain(object)\n [...]\n def resolve(self):\n \"\"\"\n This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.\n :return: <bool> Returns true, if our chain was substituted with an updated version\n \"\"\"\n\n surrounding_nodes = self.nodes\n updated_chain = None\n\n # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain\n max_length = len(self.blockchain)\n\n # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.\n for node_iterator in surrounding_nodes:\n response = requests.get(f'http://{node_iterator}/blockchain')\n\n if response.status_code == 200:\n length = response.json()['length']\n chain_iterator = response.json()['chain']\n\n if length > max_length and self.validate_blockchain(chain_iterator):\n max_length = length\n updated_chain = chain_iterator\n\n # If we found a new blockchain, replace our current blockchain with the new one\n if updated_chain:\n self.blockchain = updated_chain\n return True\n\n return False\n```\nNow we just have to register our new functions as new endpoints in the API:\n```python\[email protected]('/nodes/add', methods=['POST'])\ndef add_nodes():\n values = request.get_json()\n\n nodes = values.get('nodes')\n if nodes is None:\n return \"You didn't post a valid list of nodes. Please double check your input!\", 400\n\n for node in nodes:\n myblockchain.add_node(node)\n\n response = {\n 'message': 'All of your specified nodes have been added to the network!',\n 'total_nodes': list(myblockchain.nodes),\n }\n return jsonify(response), 201\n\n\[email protected]('/nodes/resolve', methods=['GET'])\ndef consensus():\n replaced = myblockchain.resolve()\n\n if replaced:\n response = {\n 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',\n 'new_chain': myblockchain.blockchain\n }\n else:\n response = {\n 'message': 'The blockchain of this node is already the longest valid chain within the network.',\n 'chain': myblockchain.blockchain\n }\n\n return jsonify(response), 200\n```\n## Step 18: Friends are great. Even if they're virtual...\nTo test our decentralization algorithms, either grab some friends, to run some nodes for you, or just run the same program on different ports, so you would have to start up an instance of our blockchain-program, then change to port to 5001 for example, save, fire up an instance of this _modified_ program, change the port to 5002, etc. \n## Step 19: Add them to your friends list\nIf you got some instances, you just have to register them. To do this, simply send a `POST` request with the following content\n```json\n{\n \"nodes\": [\"http://[ip-of-another-node-or-127.0.0.1-for-your-local-machine]:[the-port-number]\"]\n}\n```\nto `http://[the-node-ip-you-want-to-register-it-to]:[port]/nodes/add`\n## Step 20: Consensus check\nAfter that try to mine some blocks and even add some transactions on let's say node 2 and then run `http://[a-node-ip]:[port]/nodes/resolve` on let's say node 1. The consensus algorithm should step into action and update the blockchain of node 1.\n# The whole thang\nI keep my promises\n```python\nimport hashlib\nimport json\nimport requests\nfrom time import time\nfrom uuid import uuid4\nfrom textwrap import dedent\nfrom flask import Flask, jsonify, request\nfrom urllib.parse import urlparse\n\nclass MyBlockChain(object):\n def __init__(self):\n self.blockchain = []\n self.transactions = []\n \n # Creation of the mentioned genesis block\n self.create_new_block(previous_hash=1, proof=100)\n \n self.nodes = set()\n \n def create_new_block(self, proof, previous_hash=None):\n \"\"\"\n Creates a new block and adds it to the blockchain\n :param proof: <int> This is generated by the proof of work algorithm\n :param previous_hash: (Optional) <str> Hash of the preleading Block\n :return: <dict> Return the new block\n \"\"\"\n\n new_block = {\n 'index': len(self.blockchain) + 1,\n 'timestamp': time(),\n 'transactions': self.transactions,\n 'proof': proof,\n 'previous_hash': previous_hash or self.hash(self.blockchain[-1]),\n }\n\n # As all pending transactions have been processed and added to the block, the list can be resetted\n self.transactions = []\n \n # Add the new block to the blockchain\n self.blockchain.append(new_block)\n return new_block\n \n def create_new_transaction(self, sender, recipient, amount):\n \"\"\"\n This function creates a new transaction that will then be placed in a new block, alone or bundled together with other transactions.\n \n :param sender: <str> Sender's address\n :param recipient: <str> Recipient's address\n :param amount: <int> Amount to be transferred\n :return: <int> This is the index of the block that will contain this transaction\n \"\"\"\n\n self.transactions.append({\n 'sender': sender,\n 'recipient': recipient,\n 'amount': amount,\n })\n\n return self.last_block['index'] + 1\n \n @staticmethod\n def hash(block):\n \"\"\"\n This function hashes the block using the SHA-256 hash algorithm\n The function is given a block and returns a string, consisting of the hash\n :param block: <dict> Block\n :return: <str>\n \"\"\"\n\n # As every change in an item changes the hash, we first have to do a little sort operation, or else we would get inconsistent hashes\n block_sorted = json.dumps(block, sort_keys=True).encode()\n return hashlib.sha256(block_sorted).hexdigest()\n\n @property\n def last_block(self):\n return self.blockchain[-1]\n \n def pow(self, last_proof):\n \"\"\"\n Simple PoW Algorithm:\n - Find a number y, so that hash(xy) starts with 5 zeroes. x is the last y aka last_proof. y is then the new proof.\n :param last_proof: <int>\n :return: <int>\n \"\"\"\n \n current_proof = 0\n while self.validate_proof(last_proof, current_proof) is False:\n current_proof += 1\n\n return current_proof\n\n @staticmethod\n def validate_proof(last_proof, current_proof):\n \"\"\"\n Returns, whether the hash of the lastproof and the current_proof contains 5 leading zeroes.\n :param last_proof: <int> Previous Proof Number\n :param current_proof: <int> Current Proof Number\n :return: <bool>\n \"\"\"\n\n possible_hash = hashlib.sha256(f'{last_proof}{current_proof}'.encode()).hexdigest()\n return possible_hash[:5] == \"00000\"\n \n def add_node(self, address):\n \"\"\"\n Register a new node\n :param address: <str> This is the new node's address, for example 'http://192.168.1.112:5000'\n :return: None\n \"\"\"\n\n node_url = urlparse(address)\n self.nodes.add(node_url.netloc)\n \n def validate_blockchain(self, test_chain):\n \"\"\"\n Evaluate, whether a blockchain is valid\n :param chain: <list> The blockchain that shall be tested\n :return: <bool> Returns true if the blockchain is valid\n \"\"\"\n\n last_block = test_chain[0]\n current_index = 1\n\n while current_index < len(test_chain):\n block = test_chain[current_index]\n \n # Determine, whether the hash is correct\n if block['previous_hash'] != self.hash(last_block):\n return False\n\n # Determine, wheter the proof of work is correct\n if not self.validate_proof(last_block['proof'], block['proof']):\n return False\n\n last_block = block\n current_index += 1\n\n return True\n \n def resolve(self):\n \"\"\"\n This algorithm resolves conflicts between our nodes. It is the consensus algorithm which I mentioned previously.\n :return: <bool> Returns true, if our chain was substituted with an updated version\n \"\"\"\n\n surrounding_nodes = self.nodes\n updated_chain = None\n\n # If we follow our consensus algorithm description, we only want blockchains that are longer, than our current chain\n max_length = len(self.blockchain)\n\n # Iterate every surrounding nodes in our network through the consensus algorithm, which means checking if the node's blockchain is longer than our current blockchain and valid and set it as our new blockchain and continue checking, as there could be an even longer and valid blockchain.\n for node_iterator in surrounding_nodes:\n response = requests.get(f'http://{node_iterator}/blockchain')\n\n if response.status_code == 200:\n length = response.json()['length']\n chain_iterator = response.json()['chain']\n\n if length > max_length and self.validate_blockchain(chain_iterator):\n max_length = length\n updated_chain = chain_iterator\n\n # If we found a new blockchain, replace our current blockchain with the new one\n if updated_chain:\n self.blockchain = updated_chain\n return True\n\n return False\n\napp = Flask(__name__)\nnode_identifier = str(uuid4()).replace('-', '')\n\nmyblockchain = MyBlockChain()\n\[email protected]('/blockchain', methods=['GET'])\ndef get_full_chain():\n output = {\n 'chain': myblockchain.blockchain,\n 'length': len(myblockchain.blockchain),\n }\n return jsonify(output), 200\n\[email protected]('/mining', methods=['GET'])\ndef mining():\n # Calculate the new proof by using our PoW algorithm\n last_block = myblockchain.last_block\n last_proof = last_block['proof']\n proof = myblockchain.pow(last_proof)\n\n # For finding/mining the proof, the miner is granted a reward\n # The sender is nobody, as this coin is coming out of the void\n myblockchain.create_new_transaction(\n sender=\"0\",\n recipient=node_identifier,\n amount=1,\n )\n\n # Add the new created block to the chain\n previous_hash = myblockchain.hash(last_block)\n newblock = myblockchain.create_new_block(proof, previous_hash)\n\n output = {\n 'message': \"A new block was mined\",\n 'index': newblock['index'],\n 'transactions': newblock['transactions'],\n 'proof': newblock['proof'],\n 'previous_hash': newblock['previous_hash'],\n }\n return jsonify(output), 200\n \[email protected]('/transactions/add', methods=['POST'])\ndef add_transaction():\n values = request.get_json()\n\n # The POST request has to have the following required fields\n required = ['sender', 'recipient', 'amount']\n if not all(k in values for k in required):\n return 'There are values missing', 400\n\n # Adds a new transaction by utilizing our function\n index = myblockchain.create_new_transaction(values['sender'], values['recipient'], values['amount'])\n\n output = {'message': f'Your registered Transaction is going to be a part of the block with the index of {index}'}\n return jsonify(output), 201\n \[email protected]('/nodes/add', methods=['POST'])\ndef add_nodes():\n values = request.get_json()\n\n nodes = values.get('nodes')\n if nodes is None:\n return \"You didn't post a valid list of nodes. Please double check your input!\", 400\n\n for node in nodes:\n myblockchain.add_node(node)\n\n response = {\n 'message': 'All of your specified nodes have been added to the network!',\n 'total_nodes': list(myblockchain.nodes),\n }\n return jsonify(response), 201\n\n\[email protected]('/nodes/resolve', methods=['GET'])\ndef consensus():\n replaced = myblockchain.resolve()\n\n if replaced:\n response = {\n 'message': 'There was a longer valid chain within the network. The blockchain of this node has been replaced.',\n 'new_chain': myblockchain.blockchain\n }\n else:\n response = {\n 'message': 'The blockchain of this node is already the longest valid chain within the network.',\n 'chain': myblockchain.blockchain\n }\n\n return jsonify(response), 200\n\nif __name__ == '__main__':\n app.run(host='0.0.0.0', port=5000)\n```\n# Final thoughts\nAnd there you have it!\nYour very own blockchain. Wasn't very hard, was it?\nI sincerely hope you had fun thorughout this tutorial and were able to understand the concept and system behind it better. If you have any questions, please ask them in the comments and I'll try my best to answer them.\n# The obligatory begging\nAs this whole tutorial took me over 2 days to finish, I would love to see a lot of comments and feedback under this post. Also if you find any typos, grammar mistakes, etc. and care to tell me, i would be mor than happy about it, as English is not my native language. So please excuse any mistakes I made. I tried my very best.\nOf course please upvote, if you worship my effort to enrich the community, and if you want to see more such content, consider following me. \nI wish you only the best - See ya!",
"json_metadata": "{\"tags\":[\"steemit\",\"blockchain\",\"trevonjb\",\"cryptocurrency\",\"upvoteforupvote\"],\"links\":[\"https://www.python.org/downloads/windows/\",\"https://en.wikipedia.org/wiki/Integrated_development_environment\",\"https://www.jetbrains.com/pycharm\",\"https://www.getpostman.com/\",\"https://curl.haxx.se/\",\"https://en.wikipedia.org/wiki/SHA-2\",\"https://en.wikipedia.org/wiki/Proof-of-work_system\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}",
"parent_author": "",
"parent_permlink": "steemit",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"title": "Create your own blockchain & cryptocurrency! - understand blockchains by creating one in python - no knowledge required"
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T14:07:54",
"trx_id": "0db3d5121f95452bf55e53ad31c21e9b92ec940b",
"trx_in_block": 19,
"virtual_op": 0
}2018/01/06 14:03:00
2018/01/06 14:03:00
| author | dustvoice |
| body | Mine is an interesting article too: https://steemit.com/steemit/@dustvoice/create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| json metadata | {"tags":["resteem"],"links":["https://steemit.com/steemit/@dustvoice/create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required"],"app":"steemit/0.1"} |
| parent author | hursh |
| parent permlink | 7hpiuf-free-re-steem-to-all-my-steemit-friends |
| permlink | re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z |
| title | |
| Transaction Info | Block #18742169/Trx 49d8a6cfd021d39d9a3788962c31a59305ec16e6 |
View Raw JSON Data
{
"block": 18742169,
"op": [
"comment",
{
"author": "dustvoice",
"body": "Mine is an interesting article too: https://steemit.com/steemit/@dustvoice/create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"json_metadata": "{\"tags\":[\"resteem\"],\"links\":[\"https://steemit.com/steemit/@dustvoice/create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required\"],\"app\":\"steemit/0.1\"}",
"parent_author": "hursh",
"parent_permlink": "7hpiuf-free-re-steem-to-all-my-steemit-friends",
"permlink": "re-hursh-7hpiuf-free-re-steem-to-all-my-steemit-friends-20180106t140259089z",
"title": ""
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T14:03:00",
"trx_id": "49d8a6cfd021d39d9a3788962c31a59305ec16e6",
"trx_in_block": 34,
"virtual_op": 0
}2018/01/06 13:10:48
2018/01/06 13:10:48
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | fitinfun |
| weight | 300 (3.00%) |
| Transaction Info | Block #18741125/Trx f9e3d11de148b45c54a63835d10de34667e75cd1 |
View Raw JSON Data
{
"block": 18741125,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "fitinfun",
"weight": 300
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T13:10:48",
"trx_id": "f9e3d11de148b45c54a63835d10de34667e75cd1",
"trx_in_block": 23,
"virtual_op": 0
}2018/01/06 10:58:36
2018/01/06 10:58:36
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | mittymartz |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18738481/Trx d7b153bd5522159b49457e145f0588d42c1111b5 |
View Raw JSON Data
{
"block": 18738481,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "mittymartz",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T10:58:36",
"trx_id": "d7b153bd5522159b49457e145f0588d42c1111b5",
"trx_in_block": 16,
"virtual_op": 0
}2018/01/06 10:39:09
2018/01/06 10:39:09
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | paraalttiredoks |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18738093/Trx ff9e3093663795f9f975e2057fbc8e7de5099ee4 |
View Raw JSON Data
{
"block": 18738093,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "paraalttiredoks",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T10:39:09",
"trx_id": "ff9e3093663795f9f975e2057fbc8e7de5099ee4",
"trx_in_block": 30,
"virtual_op": 0
}2018/01/06 08:44:24
2018/01/06 08:44:24
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | vijaynag6 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18735798/Trx 8b4ece4f0f288e6daa923ece3fa31b97128bd29e |
View Raw JSON Data
{
"block": 18735798,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "vijaynag6",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T08:44:24",
"trx_id": "8b4ece4f0f288e6daa923ece3fa31b97128bd29e",
"trx_in_block": 0,
"virtual_op": 0
}2018/01/06 07:18:36
2018/01/06 07:18:36
| author | dustvoice |
| permlink | create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required |
| voter | amit-negi |
| weight | 10000 (100.00%) |
| Transaction Info | Block #18734084/Trx 008ed48e69b54855e68c1ec26258e3981b984ebd |
View Raw JSON Data
{
"block": 18734084,
"op": [
"vote",
{
"author": "dustvoice",
"permlink": "create-your-own-blockchain-and-cryptocurrency-understand-blockchains-by-creating-one-in-python-no-knowledge-required",
"voter": "amit-negi",
"weight": 10000
}
],
"op_in_trx": 0,
"timestamp": "2018-01-06T07:18:36",
"trx_id": "008ed48e69b54855e68c1ec26258e3981b984ebd",
"trx_in_block": 35,
"virtual_op": 0
}Manabar
Voting Power100.00%
Downvote Power100.00%
Resource Credits100.00%
Reputation Progress90.37%
{
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779061515
},
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779061515
},
"rc_account": {
"account": "dustvoice",
"max_rc": "10164408779",
"max_rc_creation_adjustment": {
"amount": "2020748973",
"nai": "@@000000037",
"precision": 6
},
"rc_manabar": {
"current_mana": "10164408779",
"last_update_time": 1779061515
}
}
}Account Metadata
| POSTING JSON METADATA | |
| profile | {"profile_image":"https://cdn.steemitimages.com/DQmYdjT45r1aPnK4Cigvc5LK6Wjo1SDuhGS8LUb8YfzvwSa/crop_image.jpg","cover_image":"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500","name":"DustVoice","website":"https://dustvoice.de","version":2} |
| JSON METADATA | |
| profile | {"profile_image":"https://s14.postimg.org/a0f3n4az5/Dust_Voice.png","cover_image":"https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500","name":"DustVoice","website":"http://dustvoice.de"} |
{
"posting_json_metadata": {
"profile": {
"profile_image": "https://cdn.steemitimages.com/DQmYdjT45r1aPnK4Cigvc5LK6Wjo1SDuhGS8LUb8YfzvwSa/crop_image.jpg",
"cover_image": "https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500",
"name": "DustVoice",
"website": "https://dustvoice.de",
"version": 2
}
},
"json_metadata": {
"profile": {
"profile_image": "https://s14.postimg.org/a0f3n4az5/Dust_Voice.png",
"cover_image": "https://pbs.twimg.com/profile_banners/720211137070022656/1475770438/1500x500",
"name": "DustVoice",
"website": "http://dustvoice.de"
}
}
}Auth Keys
Owner
Single Signature
Public Keys
STM73fjp31DVRWU9tynHwLsQYYFqBfsB76iEdpJSFhSJsVTuKab491/1
Active
Single Signature
Public Keys
STM8fVSCYFindrWfyACRBjd3e6LRthpKDG47KPrw6n3Tg2zK3yG211/1
Posting
Single Signature
Public Keys
STM5XZRiDa7pe2QwkytkGamYZ1TtzBgKxDoWeDjVzP1oySuD9gLrK1/1
App Permissions
@dmania.app1/1
Memo
STM69fmSxgcza1TYkcSgtWYPvnKvuaWoDBovN7bq7vvyHYCubJ2z4
{
"owner": {
"account_auths": [],
"key_auths": [
[
"STM73fjp31DVRWU9tynHwLsQYYFqBfsB76iEdpJSFhSJsVTuKab49",
1
]
],
"weight_threshold": 1
},
"active": {
"account_auths": [],
"key_auths": [
[
"STM8fVSCYFindrWfyACRBjd3e6LRthpKDG47KPrw6n3Tg2zK3yG21",
1
]
],
"weight_threshold": 1
},
"posting": {
"account_auths": [
[
"dmania.app",
1
]
],
"key_auths": [
[
"STM5XZRiDa7pe2QwkytkGamYZ1TtzBgKxDoWeDjVzP1oySuD9gLrK",
1
]
],
"weight_threshold": 1
},
"memo": "STM69fmSxgcza1TYkcSgtWYPvnKvuaWoDBovN7bq7vvyHYCubJ2z4"
}Witness Votes
0 / 30
No active witness votes.
[]