VOTING POWER100.00%
DOWNVOTE POWER100.00%
RESOURCE CREDITS100.00%
REPUTATION PROGRESS77.41%
Net Worth
0.097USD
STEEM
0.002STEEM
SBD
0.171SBD
Effective Power
5.001SP
├── Own SP
0.237SP
└── Incoming DelegationsDeleg
+4.764SP
Detailed Balance
| STEEM | ||
| balance | 0.002STEEM | STEEM |
| market_balance | 0.000STEEM | STEEM |
| savings_balance | 0.000STEEM | STEEM |
| reward_steem_balance | 0.000STEEM | STEEM |
| STEEM POWER | ||
| Own SP | 0.237SP | SP |
| Delegated Out | 0.000SP | SP |
| Delegation In | 4.764SP | SP |
| Effective Power | 5.001SP | SP |
| Reward SP (pending) | 0.000SP | SP |
| SBD | ||
| sbd_balance | 0.171SBD | 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.002 STEEM",
"savings_balance": "0.000 STEEM",
"reward_steem_balance": "0.000 STEEM",
"vesting_shares": "385.738774 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "7757.921032 VESTS",
"sbd_balance": "0.171 SBD",
"savings_sbd_balance": "0.000 SBD",
"reward_sbd_balance": "0.000 SBD",
"conversions": []
}Account Info
| name | yeshulin |
| id | 863270 |
| rank | 440,588 |
| reputation | 2033465974 |
| created | 2018-03-16T01:45:42 |
| recovery_account | steem |
| proxy | None |
| post_count | 23 |
| comment_count | 0 |
| lifetime_vote_count | 0 |
| witnesses_voted_for | 0 |
| last_post | 2018-03-24T05:11:33 |
| last_root_post | 2018-03-21T03:51:39 |
| last_vote_time | 2018-03-24T05:09:24 |
| proxied_vsf_votes | 0, 0, 0, 0 |
| can_vote | 1 |
| voting_power | 0 |
| delayed_votes | 0 |
| balance | 0.002 STEEM |
| savings_balance | 0.000 STEEM |
| sbd_balance | 0.171 SBD |
| savings_sbd_balance | 0.000 SBD |
| vesting_shares | 385.738774 VESTS |
| delegated_vesting_shares | 0.000000 VESTS |
| received_vesting_shares | 7757.921032 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 | 1970-01-01T00:00:00 |
| mined | No |
| sbd_seconds | 0 |
| sbd_last_interest_payment | 1970-01-01T00:00:00 |
| savings_sbd_last_interest_payment | 1970-01-01T00:00:00 |
{
"id": 863270,
"name": "yeshulin",
"owner": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM7rSAVPXTrmQbzfugk6gWDK193Da2gUXAqiVi1nFgG4dVRexj22",
1
]
]
},
"active": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM76teMDP8hsztZUYTt5VdgSibWss8Zkz9oZmmxo8P8MwVC9zFwb",
1
]
]
},
"posting": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM7CqxpAcgFgjvGZrejU98rNX9uLHuibQvXLsK15Wp8xhwt3QKrj",
1
]
]
},
"memo_key": "STM67Tnv2sf3KryMnUHHxQX4KWgMYp4vihyQvGB9vJwZpDEsoHAFq",
"json_metadata": "{}",
"posting_json_metadata": "",
"proxy": "",
"last_owner_update": "1970-01-01T00:00:00",
"last_account_update": "1970-01-01T00:00:00",
"created": "2018-03-16T01:45:42",
"mined": false,
"recovery_account": "steem",
"last_account_recovery": "1970-01-01T00:00:00",
"reset_account": "null",
"comment_count": 0,
"lifetime_vote_count": 0,
"post_count": 23,
"can_vote": true,
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779092841
},
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779092841
},
"voting_power": 0,
"balance": "0.002 STEEM",
"savings_balance": "0.000 STEEM",
"sbd_balance": "0.171 SBD",
"sbd_seconds": "0",
"sbd_seconds_last_update": "2018-03-26T12:14:30",
"sbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_sbd_balance": "0.000 SBD",
"savings_sbd_seconds": "0",
"savings_sbd_seconds_last_update": "1970-01-01T00:00:00",
"savings_sbd_last_interest_payment": "1970-01-01T00:00:00",
"savings_withdraw_requests": 0,
"reward_sbd_balance": "0.000 SBD",
"reward_steem_balance": "0.000 STEEM",
"reward_vesting_balance": "0.000000 VESTS",
"reward_vesting_steem": "0.000 STEEM",
"vesting_shares": "385.738774 VESTS",
"delegated_vesting_shares": "0.000000 VESTS",
"received_vesting_shares": "7757.921032 VESTS",
"vesting_withdraw_rate": "0.000000 VESTS",
"next_vesting_withdrawal": "1969-12-31T23:59:59",
"withdrawn": 0,
"to_withdraw": 0,
"withdraw_routes": 0,
"curation_rewards": 0,
"posting_rewards": 178,
"proxied_vsf_votes": [
0,
0,
0,
0
],
"witnesses_voted_for": 0,
"last_post": "2018-03-24T05:11:33",
"last_root_post": "2018-03-21T03:51:39",
"last_vote_time": "2018-03-24T05:09:24",
"post_bandwidth": 0,
"pending_claimed_accounts": 0,
"vesting_balance": "0.000 STEEM",
"reputation": 2033465974,
"transfer_history": [],
"market_history": [],
"post_history": [],
"vote_history": [],
"other_history": [],
"witness_votes": [],
"tags_usage": [],
"guest_bloggers": [],
"rank": 440588
}Withdraw Routes
| Incoming | Outgoing |
|---|---|
Empty | Empty |
{
"incoming": [],
"outgoing": []
}From Date
To Date
2026/05/18 08:27:21
2026/05/18 08:27:21
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 7757.921032 VESTS |
| Transaction Info | Block #106153251/Trx 05200649ec9a323a4e30d18fc4dc494b61c052fa |
View Raw JSON Data
{
"trx_id": "05200649ec9a323a4e30d18fc4dc494b61c052fa",
"block": 106153251,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2026-05-18T08:27:21",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "7757.921032 VESTS"
}
]
}2026/05/13 13:10:54
2026/05/13 13:10:54
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 5045.710627 VESTS |
| Transaction Info | Block #106015630/Trx 7fe21f18337844092606a095d0fbd89d8c25df38 |
View Raw JSON Data
{
"trx_id": "7fe21f18337844092606a095d0fbd89d8c25df38",
"block": 106015630,
"trx_in_block": 22,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2026-05-13T13:10:54",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "5045.710627 VESTS"
}
]
}2026/04/26 07:36:00
2026/04/26 07:36:00
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 7770.436788 VESTS |
| Transaction Info | Block #105520675/Trx 45e3fd89dc9ce606efd059ade59126bb4969b2ef |
View Raw JSON Data
{
"trx_id": "45e3fd89dc9ce606efd059ade59126bb4969b2ef",
"block": 105520675,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2026-04-26T07:36:00",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "7770.436788 VESTS"
}
]
}2026/01/24 05:51:57
2026/01/24 05:51:57
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 5087.257446 VESTS |
| Transaction Info | Block #102878233/Trx b8094e1458d8d2beb1f33a64a1a90b357f38b802 |
View Raw JSON Data
{
"trx_id": "b8094e1458d8d2beb1f33a64a1a90b357f38b802",
"block": 102878233,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2026-01-24T05:51:57",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "5087.257446 VESTS"
}
]
}2024/12/18 01:00:45
2024/12/18 01:00:45
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 5251.476643 VESTS |
| Transaction Info | Block #91324427/Trx 1deb857757eb3a32940b2d5663b345d0a4c3a44b |
View Raw JSON Data
{
"trx_id": "1deb857757eb3a32940b2d5663b345d0a4c3a44b",
"block": 91324427,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2024-12-18T01:00:45",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "5251.476643 VESTS"
}
]
}2023/11/14 16:40:00
2023/11/14 16:40:00
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 5420.610175 VESTS |
| Transaction Info | Block #79878528/Trx bb080a2cf3bdae28d9e55735d2200a49364a9a86 |
View Raw JSON Data
{
"trx_id": "bb080a2cf3bdae28d9e55735d2200a49364a9a86",
"block": 79878528,
"trx_in_block": 4,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2023-11-14T16:40:00",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "5420.610175 VESTS"
}
]
}2023/09/22 12:57:48
2023/09/22 12:57:48
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 8357.518961 VESTS |
| Transaction Info | Block #78365942/Trx 39c290196dc3dcdd2b7cc5b9f0e9d42943001be9 |
View Raw JSON Data
{
"trx_id": "39c290196dc3dcdd2b7cc5b9f0e9d42943001be9",
"block": 78365942,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2023-09-22T12:57:48",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "8357.518961 VESTS"
}
]
}2022/11/03 20:05:18
2022/11/03 20:05:18
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 8579.570399 VESTS |
| Transaction Info | Block #69123278/Trx e71c3072cca4ed349f8f1c86823dee872b705667 |
View Raw JSON Data
{
"trx_id": "e71c3072cca4ed349f8f1c86823dee872b705667",
"block": 69123278,
"trx_in_block": 15,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2022-11-03T20:05:18",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "8579.570399 VESTS"
}
]
}2022/01/18 01:04:33
2022/01/18 01:04:33
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 8799.678000 VESTS |
| Transaction Info | Block #60826274/Trx 2933d373c6e1c586702dbe0bc84979ce6a854ef5 |
View Raw JSON Data
{
"trx_id": "2933d373c6e1c586702dbe0bc84979ce6a854ef5",
"block": 60826274,
"trx_in_block": 9,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2022-01-18T01:04:33",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "8799.678000 VESTS"
}
]
}2021/06/14 08:10:12
2021/06/14 08:10:12
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 8983.872288 VESTS |
| Transaction Info | Block #54616485/Trx 8a4de88b0786c41122552fa4acf934b78059cee3 |
View Raw JSON Data
{
"trx_id": "8a4de88b0786c41122552fa4acf934b78059cee3",
"block": 54616485,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2021-06-14T08:10:12",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "8983.872288 VESTS"
}
]
}2020/12/11 18:20:15
2020/12/11 18:20:15
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 9171.294262 VESTS |
| Transaction Info | Block #49363677/Trx 8c94b6cdbffd5eb3e2366188bef88f777f98fa7b |
View Raw JSON Data
{
"trx_id": "8c94b6cdbffd5eb3e2366188bef88f777f98fa7b",
"block": 49363677,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-12-11T18:20:15",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "9171.294262 VESTS"
}
]
}2020/12/06 11:55:09
2020/12/06 11:55:09
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 1912.543513 VESTS |
| Transaction Info | Block #49215188/Trx 45ec01c9095d08e5df07b9845aac94318b160164 |
View Raw JSON Data
{
"trx_id": "45ec01c9095d08e5df07b9845aac94318b160164",
"block": 49215188,
"trx_in_block": 4,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-12-06T11:55:09",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "1912.543513 VESTS"
}
]
}2020/12/05 21:57:54
2020/12/05 21:57:54
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 9177.502116 VESTS |
| Transaction Info | Block #49198758/Trx 4618e357884fb9279cba1e87b9732fed7f1513c1 |
View Raw JSON Data
{
"trx_id": "4618e357884fb9279cba1e87b9732fed7f1513c1",
"block": 49198758,
"trx_in_block": 3,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-12-05T21:57:54",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "9177.502116 VESTS"
}
]
}2020/11/03 06:34:51
2020/11/03 06:34:51
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 1920.017158 VESTS |
| Transaction Info | Block #48275395/Trx 508c6c7620da9e40b516b4e1255021cec4a83d65 |
View Raw JSON Data
{
"trx_id": "508c6c7620da9e40b516b4e1255021cec4a83d65",
"block": 48275395,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-11-03T06:34:51",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "1920.017158 VESTS"
}
]
}2020/05/09 13:00:12
2020/05/09 13:00:12
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 9380.307475 VESTS |
| Transaction Info | Block #43225547/Trx 077467507e7a4e420db3e60efe0658b69fdff0c7 |
View Raw JSON Data
{
"trx_id": "077467507e7a4e420db3e60efe0658b69fdff0c7",
"block": 43225547,
"trx_in_block": 12,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-05-09T13:00:12",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "9380.307475 VESTS"
}
]
}2020/05/08 17:42:03
2020/05/08 17:42:03
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 1953.311140 VESTS |
| Transaction Info | Block #43202926/Trx 4a258be65b269750bfbd4de9426e0fbd02a1b49d |
View Raw JSON Data
{
"trx_id": "4a258be65b269750bfbd4de9426e0fbd02a1b49d",
"block": 43202926,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-05-08T17:42:03",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "1953.311140 VESTS"
}
]
}2020/03/16 06:58:54
2020/03/16 06:58:54
| parent author | yeshulin |
| parent permlink | btc |
| author | steemitboard |
| permlink | steemitboard-notify-yeshulin-20200316t065854000z |
| title | |
| body | Congratulations @yeshulin! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@yeshulin/birthday2.png</td><td>Happy Steem 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/@yeshulin) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=yeshulin)_</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"]} |
| Transaction Info | Block #41695339/Trx 283afa68e3e32cc4bceaa01de6e760fe807f9e62 |
View Raw JSON Data
{
"trx_id": "283afa68e3e32cc4bceaa01de6e760fe807f9e62",
"block": 41695339,
"trx_in_block": 18,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2020-03-16T06:58:54",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "btc",
"author": "steemitboard",
"permlink": "steemitboard-notify-yeshulin-20200316t065854000z",
"title": "",
"body": "Congratulations @yeshulin! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@yeshulin/birthday2.png</td><td>Happy Steem 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/@yeshulin) and compare to others on the [Steem Ranking](https://steemitboard.com/ranking/index.php?name=yeshulin)_</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\"]}"
}
]
}2019/06/17 07:12:21
2019/06/17 07:12:21
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 9568.181835 VESTS |
| Transaction Info | Block #33872049/Trx f96ff5c296281d35c6566f2861b45289ca5f6c70 |
View Raw JSON Data
{
"trx_id": "f96ff5c296281d35c6566f2861b45289ca5f6c70",
"block": 33872049,
"trx_in_block": 6,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2019-06-17T07:12:21",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "9568.181835 VESTS"
}
]
}2019/03/16 02:04:48
2019/03/16 02:04:48
| parent author | yeshulin |
| parent permlink | btc |
| author | steemitboard |
| permlink | steemitboard-notify-yeshulin-20190316t020447000z |
| title | |
| body | Congratulations @yeshulin! You received a personal award! <table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@yeshulin/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table> <sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@yeshulin) and compare to others on the [Steem Ranking](http://steemitboard.com/ranking/index.php?name=yeshulin)_</sub> **Do not miss the last post from @steemitboard:** <table><tr><td><a href="https://steemit.com/drugwars/@steemitboard/drugwars-early-adopter"><img src="https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmYGN7R653u4hDFyq1hM7iuhr2bdAP1v2ApACDNtecJAZ5/image.png"></a></td><td><a href="https://steemit.com/drugwars/@steemitboard/drugwars-early-adopter">Are you a DrugWars early adopter? Benvenuto in famiglia!</a></td></tr></table> ###### [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"]} |
| Transaction Info | Block #31191026/Trx 0756b58d55bccfa8879c47ed9993dd1ab22c5803 |
View Raw JSON Data
{
"trx_id": "0756b58d55bccfa8879c47ed9993dd1ab22c5803",
"block": 31191026,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2019-03-16T02:04:48",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "btc",
"author": "steemitboard",
"permlink": "steemitboard-notify-yeshulin-20190316t020447000z",
"title": "",
"body": "Congratulations @yeshulin! You received a personal award!\n\n<table><tr><td>https://steemitimages.com/70x70/http://steemitboard.com/@yeshulin/birthday1.png</td><td>Happy Birthday! - You are on the Steem blockchain for 1 year!</td></tr></table>\n\n<sub>_You can view [your badges on your Steem Board](https://steemitboard.com/@yeshulin) and compare to others on the [Steem Ranking](http://steemitboard.com/ranking/index.php?name=yeshulin)_</sub>\n\n\n**Do not miss the last post from @steemitboard:**\n<table><tr><td><a href=\"https://steemit.com/drugwars/@steemitboard/drugwars-early-adopter\"><img src=\"https://steemitimages.com/64x128/https://cdn.steemitimages.com/DQmYGN7R653u4hDFyq1hM7iuhr2bdAP1v2ApACDNtecJAZ5/image.png\"></a></td><td><a href=\"https://steemit.com/drugwars/@steemitboard/drugwars-early-adopter\">Are you a DrugWars early adopter? Benvenuto in famiglia!</a></td></tr></table>\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\"]}"
}
]
}2018/06/25 13:54:27
2018/06/25 13:54:27
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 9767.353821 VESTS |
| Transaction Info | Block #23632974/Trx 835ea6d2e2c3b66739d107a6510e0be8d2b69dda |
View Raw JSON Data
{
"trx_id": "835ea6d2e2c3b66739d107a6510e0be8d2b69dda",
"block": 23632974,
"trx_in_block": 24,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-06-25T13:54:27",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "9767.353821 VESTS"
}
]
}2018/03/30 19:21:42
2018/03/30 19:21:42
| delegator | steem |
| delegatee | yeshulin |
| vesting shares | 30211.870229 VESTS |
| Transaction Info | Block #21136020/Trx 36cb82e5c9e47c286aaf8af5872e45a98901900e |
View Raw JSON Data
{
"trx_id": "36cb82e5c9e47c286aaf8af5872e45a98901900e",
"block": 21136020,
"trx_in_block": 56,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-30T19:21:42",
"op": [
"delegate_vesting_shares",
{
"delegator": "steem",
"delegatee": "yeshulin",
"vesting_shares": "30211.870229 VESTS"
}
]
}yeshulinclaimed reward balance: 0.002 STEEM, 0.171 SBD, 0.112 SP2018/03/26 12:14:30
yeshulinclaimed reward balance: 0.002 STEEM, 0.171 SBD, 0.112 SP
2018/03/26 12:14:30
| account | yeshulin |
| reward steem | 0.002 STEEM |
| reward sbd | 0.171 SBD |
| reward vests | 181.596665 VESTS |
| Transaction Info | Block #21012319/Trx acd4b3fb5e6054a9ab6b081f27f27d140c1cab12 |
View Raw JSON Data
{
"trx_id": "acd4b3fb5e6054a9ab6b081f27f27d140c1cab12",
"block": 21012319,
"trx_in_block": 68,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-26T12:14:30",
"op": [
"claim_reward_balance",
{
"account": "yeshulin",
"reward_steem": "0.002 STEEM",
"reward_sbd": "0.171 SBD",
"reward_vests": "181.596665 VESTS"
}
]
}yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-3-persistence-and-cli2018/03/26 12:13:27
yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-3-persistence-and-cli
2018/03/26 12:13:27
| voter | yeshulin |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-3-persistence-and-cli |
| weight | 10000 (100.00%) |
| Transaction Info | Block #21012298/Trx 10ca06e48e471a09548aaa8b724f0460f7accfd4 |
View Raw JSON Data
{
"trx_id": "10ca06e48e471a09548aaa8b724f0460f7accfd4",
"block": 21012298,
"trx_in_block": 36,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-26T12:13:27",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-3-persistence-and-cli",
"weight": 10000
}
]
}yeshulinreceived 0.002 STEEM, 0.171 SBD, 0.112 SP author reward for @yeshulin / building-blockchain-in-go-part-1-basic-prototype2018/03/25 14:12:30
yeshulinreceived 0.002 STEEM, 0.171 SBD, 0.112 SP author reward for @yeshulin / building-blockchain-in-go-part-1-basic-prototype
2018/03/25 14:12:30
| author | yeshulin |
| permlink | building-blockchain-in-go-part-1-basic-prototype |
| sbd payout | 0.171 SBD |
| steem payout | 0.002 STEEM |
| vesting payout | 181.596665 VESTS |
| Transaction Info | Block #20985893/Virtual Operation #10 |
View Raw JSON Data
{
"trx_id": "0000000000000000000000000000000000000000",
"block": 20985893,
"trx_in_block": 4294967295,
"op_in_trx": 0,
"virtual_op": 10,
"timestamp": "2018-03-25T14:12:30",
"op": [
"author_reward",
{
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-1-basic-prototype",
"sbd_payout": "0.171 SBD",
"steem_payout": "0.002 STEEM",
"vesting_payout": "181.596665 VESTS"
}
]
}kingak47upvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/24 10:12:45
kingak47upvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/24 10:12:45
| voter | kingak47 |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20952305/Trx 8b349e5f5b7b6c35d5afdbea00e9d4487af16914 |
View Raw JSON Data
{
"trx_id": "8b349e5f5b7b6c35d5afdbea00e9d4487af16914",
"block": 20952305,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T10:12:45",
"op": [
"vote",
{
"voter": "kingak47",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 10000
}
]
}2018/03/24 09:32:21
2018/03/24 09:32:21
| parent author | yeshulin |
| parent permlink | btc |
| author | steemitboard |
| permlink | steemitboard-notify-yeshulin-20180324t093223000z |
| title | |
| body | Congratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) : [](http://steemitboard.com/@yeshulin) Award for the number of comments Click on any badge to view your own Board of Honor on SteemitBoard. For more information about SteemitBoard, click [here](https://steemit.com/@steemitboard) If you no longer want to receive notifications, reply to this comment with the word `STOP` > Upvote this notification to help all Steemit users. Learn why [here](https://steemit.com/steemitboard/@steemitboard/http-i-cubeupload-com-7ciqeo-png)! |
| json metadata | {"image":["https://steemitboard.com/img/notifications.png"]} |
| Transaction Info | Block #20951497/Trx d759c1378e3e95b8c7eea1518fecf9a0557a213c |
View Raw JSON Data
{
"trx_id": "d759c1378e3e95b8c7eea1518fecf9a0557a213c",
"block": 20951497,
"trx_in_block": 55,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T09:32:21",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "btc",
"author": "steemitboard",
"permlink": "steemitboard-notify-yeshulin-20180324t093223000z",
"title": "",
"body": "Congratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) :\n\n[](http://steemitboard.com/@yeshulin) Award for the number of comments\n\nClick on any badge to view your own Board of Honor on SteemitBoard.\nFor more information about SteemitBoard, click [here](https://steemit.com/@steemitboard)\n\nIf you no longer want to receive notifications, reply to this comment with the word `STOP`\n\n> Upvote this notification to help all Steemit users. Learn why [here](https://steemit.com/steemitboard/@steemitboard/http-i-cubeupload-com-7ciqeo-png)!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notifications.png\"]}"
}
]
}2018/03/24 05:11:33
2018/03/24 05:11:33
| parent author | kingak47 |
| parent permlink | sexual-honey-coat-beauty |
| author | yeshulin |
| permlink | re-kingak47-sexual-honey-coat-beauty-20180324t051132484z |
| title | |
| body | good,I like beatiful girl... |
| json metadata | {"tags":["cn"],"app":"steemit/0.1"} |
| Transaction Info | Block #20946282/Trx 339595784b0f50f6a5878cf9d31ef92f2ba5318b |
View Raw JSON Data
{
"trx_id": "339595784b0f50f6a5878cf9d31ef92f2ba5318b",
"block": 20946282,
"trx_in_block": 44,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T05:11:33",
"op": [
"comment",
{
"parent_author": "kingak47",
"parent_permlink": "sexual-honey-coat-beauty",
"author": "yeshulin",
"permlink": "re-kingak47-sexual-honey-coat-beauty-20180324t051132484z",
"title": "",
"body": "good,I like beatiful girl...",
"json_metadata": "{\"tags\":[\"cn\"],\"app\":\"steemit/0.1\"}"
}
]
}yeshulinupvoted (100.00%) @kingak47 / sexual-honey-coat-beauty2018/03/24 05:09:24
yeshulinupvoted (100.00%) @kingak47 / sexual-honey-coat-beauty
2018/03/24 05:09:24
| voter | yeshulin |
| author | kingak47 |
| permlink | sexual-honey-coat-beauty |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20946239/Trx af6689c34e037ce0cc1baa1e0c9607bc1d7a9907 |
View Raw JSON Data
{
"trx_id": "af6689c34e037ce0cc1baa1e0c9607bc1d7a9907",
"block": 20946239,
"trx_in_block": 30,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T05:09:24",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "kingak47",
"permlink": "sexual-honey-coat-beauty",
"weight": 10000
}
]
}2018/03/24 05:07:57
2018/03/24 05:07:57
| parent author | mmmyyyzzz |
| parent permlink | if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100 |
| author | yeshulin |
| permlink | re-mmmyyyzzz-if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100-20180324t050754527z |
| title | |
| body | if you have 100 million btc, haha,friends... |
| json metadata | {"tags":["cn"],"app":"steemit/0.1"} |
| Transaction Info | Block #20946210/Trx 939fdd3fee8474118fcee84d5c36c92c42a35e6d |
View Raw JSON Data
{
"trx_id": "939fdd3fee8474118fcee84d5c36c92c42a35e6d",
"block": 20946210,
"trx_in_block": 5,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T05:07:57",
"op": [
"comment",
{
"parent_author": "mmmyyyzzz",
"parent_permlink": "if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100-20180324t050754527z",
"title": "",
"body": "if you have 100 million btc, haha,friends...",
"json_metadata": "{\"tags\":[\"cn\"],\"app\":\"steemit/0.1\"}"
}
]
}2018/03/24 05:06:36
2018/03/24 05:06:36
| voter | yeshulin |
| author | mmmyyyzzz |
| permlink | if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100 |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20946183/Trx 244800a99872af9fabbee12bb2b5925daea58fbf |
View Raw JSON Data
{
"trx_id": "244800a99872af9fabbee12bb2b5925daea58fbf",
"block": 20946183,
"trx_in_block": 40,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-24T05:06:36",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "mmmyyyzzz",
"permlink": "if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100",
"weight": 10000
}
]
}2018/03/21 05:18:27
2018/03/21 05:18:27
| voter | yeshulin |
| author | yeshulin |
| permlink | btc |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20860809/Trx c2f3e33841252ae2df08b33a4fbf3af07be6d3bb |
View Raw JSON Data
{
"trx_id": "c2f3e33841252ae2df08b33a4fbf3af07be6d3bb",
"block": 20860809,
"trx_in_block": 16,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-21T05:18:27",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "btc",
"weight": 10000
}
]
}2018/03/21 03:51:39
2018/03/21 03:51:39
| parent author | |
| parent permlink | btc |
| author | yeshulin |
| permlink | btc |
| title | 关于BTC价格走势 |
| body | 从最近的趋势看,BTC有出现反转的迹象,大家可以选择低位建仓,估计在9500usdt左右会向下回踩一波,继续向上。山寨因为前期都普遍腰斩,有大涨趋势,可以选择未涨山寨建仓。 |
| json metadata | {"tags":["btc","bitcoin"],"app":"steemit/0.1","format":"markdown"} |
| Transaction Info | Block #20859073/Trx 44797510c60978fd0a6b55c702ad9087d7a0dc10 |
View Raw JSON Data
{
"trx_id": "44797510c60978fd0a6b55c702ad9087d7a0dc10",
"block": 20859073,
"trx_in_block": 19,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-21T03:51:39",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "btc",
"author": "yeshulin",
"permlink": "btc",
"title": "关于BTC价格走势",
"body": "从最近的趋势看,BTC有出现反转的迹象,大家可以选择低位建仓,估计在9500usdt左右会向下回踩一波,继续向上。山寨因为前期都普遍腰斩,有大涨趋势,可以选择未涨山寨建仓。",
"json_metadata": "{\"tags\":[\"btc\",\"bitcoin\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}"
}
]
}2018/03/20 15:17:45
2018/03/20 15:17:45
| voter | yeshulin |
| author | cryptoissweet |
| permlink | re-yeshulin-the-new-ways-to-save-crypto-from-a-post-quantum-world-1521345706444tf723fbd7-494a-4c52-916d-5ad83bb72395uid |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20844004/Trx 559517725e4d2a30627b8d22bbf9c4337474e0ce |
View Raw JSON Data
{
"trx_id": "559517725e4d2a30627b8d22bbf9c4337474e0ce",
"block": 20844004,
"trx_in_block": 26,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T15:17:45",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "cryptoissweet",
"permlink": "re-yeshulin-the-new-ways-to-save-crypto-from-a-post-quantum-world-1521345706444tf723fbd7-494a-4c52-916d-5ad83bb72395uid",
"weight": 10000
}
]
}yeshulinupvoted (100.00%) @yeshulin / the-new-ways-to-save-crypto-from-a-post-quantum-world2018/03/20 15:17:33
yeshulinupvoted (100.00%) @yeshulin / the-new-ways-to-save-crypto-from-a-post-quantum-world
2018/03/20 15:17:33
| voter | yeshulin |
| author | yeshulin |
| permlink | the-new-ways-to-save-crypto-from-a-post-quantum-world |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20844000/Trx 35a0e754175862a64e66196b1ce4e67d162aed5e |
View Raw JSON Data
{
"trx_id": "35a0e754175862a64e66196b1ce4e67d162aed5e",
"block": 20844000,
"trx_in_block": 52,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T15:17:33",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "the-new-ways-to-save-crypto-from-a-post-quantum-world",
"weight": 10000
}
]
}2018/03/20 14:51:42
2018/03/20 14:51:42
| parent author | liuzg |
| parent permlink | re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z |
| author | yeshulin |
| permlink | re-liuzg-re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t145140133z |
| title | |
| body | 哈哈,关于区块链一定要多学习的. |
| json metadata | {"tags":["building"],"app":"steemit/0.1"} |
| Transaction Info | Block #20843485/Trx c46b6905d4d8aaa33e610bb7cd8d2b152e95caa1 |
View Raw JSON Data
{
"trx_id": "c46b6905d4d8aaa33e610bb7cd8d2b152e95caa1",
"block": 20843485,
"trx_in_block": 28,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T14:51:42",
"op": [
"comment",
{
"parent_author": "liuzg",
"parent_permlink": "re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z",
"author": "yeshulin",
"permlink": "re-liuzg-re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t145140133z",
"title": "",
"body": "哈哈,关于区块链一定要多学习的.",
"json_metadata": "{\"tags\":[\"building\"],\"app\":\"steemit/0.1\"}"
}
]
}myluckyupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/20 14:23:00
myluckyupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/20 14:23:00
| voter | mylucky |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20842915/Trx 4ebf927e2dc3cbd9a2a643b19bcfcc3fda71809f |
View Raw JSON Data
{
"trx_id": "4ebf927e2dc3cbd9a2a643b19bcfcc3fda71809f",
"block": 20842915,
"trx_in_block": 30,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T14:23:00",
"op": [
"vote",
{
"voter": "mylucky",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 10000
}
]
}2018/03/20 06:34:54
2018/03/20 06:34:54
| parent author | yeshulin |
| parent permlink | building-blockchain-in-go-part-4-transactions |
| author | liuzg |
| permlink | re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z |
| title | |
| body | 这应该是一部分程式吧? 我外行,看不懂!😌 |
| json metadata | {"tags":["building"],"app":"steemit/0.1"} |
| Transaction Info | Block #20833572/Trx 0e4ee7532d6f96b284a8f65717d826c9bcfacb93 |
View Raw JSON Data
{
"trx_id": "0e4ee7532d6f96b284a8f65717d826c9bcfacb93",
"block": 20833572,
"trx_in_block": 40,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T06:34:54",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "building-blockchain-in-go-part-4-transactions",
"author": "liuzg",
"permlink": "re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z",
"title": "",
"body": "这应该是一部分程式吧?\n我外行,看不懂!😌",
"json_metadata": "{\"tags\":[\"building\"],\"app\":\"steemit/0.1\"}"
}
]
}liuzgupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/20 06:33:21
liuzgupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/20 06:33:21
| voter | liuzg |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20833541/Trx 1e2e5a96df4cecf86aba51fa09b368844d8b8e0e |
View Raw JSON Data
{
"trx_id": "1e2e5a96df4cecf86aba51fa09b368844d8b8e0e",
"block": 20833541,
"trx_in_block": 1,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T06:33:21",
"op": [
"vote",
{
"voter": "liuzg",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 10000
}
]
}2018/03/20 05:59:51
2018/03/20 05:59:51
| parent author | yeshulin |
| parent permlink | re-liuzg-8-or-or-20180320t042153215z |
| author | liuzg |
| permlink | re-yeshulin-re-liuzg-8-or-or-20180320t055946204z |
| title | |
| body | 这个是我个人的理解,欢迎指正! |
| json metadata | {"tags":["busy"],"app":"steemit/0.1"} |
| Transaction Info | Block #20832873/Trx e883cc9d25bf2131b34f08364a36371860b1f1a7 |
View Raw JSON Data
{
"trx_id": "e883cc9d25bf2131b34f08364a36371860b1f1a7",
"block": 20832873,
"trx_in_block": 16,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T05:59:51",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "re-liuzg-8-or-or-20180320t042153215z",
"author": "liuzg",
"permlink": "re-yeshulin-re-liuzg-8-or-or-20180320t055946204z",
"title": "",
"body": "这个是我个人的理解,欢迎指正!",
"json_metadata": "{\"tags\":[\"busy\"],\"app\":\"steemit/0.1\"}"
}
]
}yeshulinreplied to @liuzg / re-liuzg-8-or-or-20180320t042153215z2018/03/20 04:21:54
yeshulinreplied to @liuzg / re-liuzg-8-or-or-20180320t042153215z
2018/03/20 04:21:54
| parent author | liuzg |
| parent permlink | 8-or-or |
| author | yeshulin |
| permlink | re-liuzg-8-or-or-20180320t042153215z |
| title | |
| body | 这个通俗易懂,厉害。。。愿大家都能够照顾好自己的身体。healthy for everybody. |
| json metadata | {"tags":["busy"],"app":"steemit/0.1"} |
| Transaction Info | Block #20830918/Trx 449ae6d904ae5549806372bef6828f27a5a03720 |
View Raw JSON Data
{
"trx_id": "449ae6d904ae5549806372bef6828f27a5a03720",
"block": 20830918,
"trx_in_block": 18,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T04:21:54",
"op": [
"comment",
{
"parent_author": "liuzg",
"parent_permlink": "8-or-or",
"author": "yeshulin",
"permlink": "re-liuzg-8-or-or-20180320t042153215z",
"title": "",
"body": "这个通俗易懂,厉害。。。愿大家都能够照顾好自己的身体。healthy for everybody.",
"json_metadata": "{\"tags\":[\"busy\"],\"app\":\"steemit/0.1\"}"
}
]
}2018/03/20 04:20:15
2018/03/20 04:20:15
| voter | yeshulin |
| author | liuzg |
| permlink | 8-or-or |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20830886/Trx 643aab49bed1d39a6648e624f55ed83adb359762 |
View Raw JSON Data
{
"trx_id": "643aab49bed1d39a6648e624f55ed83adb359762",
"block": 20830886,
"trx_in_block": 28,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T04:20:15",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "liuzg",
"permlink": "8-or-or",
"weight": 10000
}
]
}2018/03/20 04:20:09
2018/03/20 04:20:09
| required auths | [] |
| required posting auths | ["yeshulin"] |
| id | follow |
| json | ["follow",{"follower":"yeshulin","following":"liuzg","what":["blog"]}] |
| Transaction Info | Block #20830884/Trx a2d05e1cdf8b095b05590ce3a24c108209acfaeb |
View Raw JSON Data
{
"trx_id": "a2d05e1cdf8b095b05590ce3a24c108209acfaeb",
"block": 20830884,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T04:20:09",
"op": [
"custom_json",
{
"required_auths": [],
"required_posting_auths": [
"yeshulin"
],
"id": "follow",
"json": "[\"follow\",{\"follower\":\"yeshulin\",\"following\":\"liuzg\",\"what\":[\"blog\"]}]"
}
]
}yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/20 01:35:24
yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/20 01:35:24
| voter | yeshulin |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20827589/Trx 3b8993b24b5c941933e0be2dd6c5302dad7a8d94 |
View Raw JSON Data
{
"trx_id": "3b8993b24b5c941933e0be2dd6c5302dad7a8d94",
"block": 20827589,
"trx_in_block": 8,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:35:24",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 10000
}
]
}2018/03/20 01:22:54
2018/03/20 01:22:54
| voter | yeshulin |
| author | yeshulin |
| permlink | 4imgnt |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20827340/Trx 5ea61b7dec19e4812fe977e008326a683325aad6 |
View Raw JSON Data
{
"trx_id": "5ea61b7dec19e4812fe977e008326a683325aad6",
"block": 20827340,
"trx_in_block": 41,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:22:54",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "4imgnt",
"weight": 10000
}
]
}yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-1-basic-prototype2018/03/20 01:22:36
yeshulinupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-1-basic-prototype
2018/03/20 01:22:36
| voter | yeshulin |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-1-basic-prototype |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20827334/Trx 5e1df100a97c98ac95108139b643105ece690898 |
View Raw JSON Data
{
"trx_id": "5e1df100a97c98ac95108139b643105ece690898",
"block": 20827334,
"trx_in_block": 50,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:22:36",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-1-basic-prototype",
"weight": 10000
}
]
}2018/03/20 01:19:57
2018/03/20 01:19:57
| voter | yeshulin |
| author | yeshulin |
| permlink | re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20827281/Trx 1bee35b1a0fc97519b1cf7cebb817be28c49afa5 |
View Raw JSON Data
{
"trx_id": "1bee35b1a0fc97519b1cf7cebb817be28c49afa5",
"block": 20827281,
"trx_in_block": 17,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:19:57",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z",
"weight": 10000
}
]
}yeshulinremoved vote from (0.00%) @yeshulin / re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z2018/03/20 01:19:45
yeshulinremoved vote from (0.00%) @yeshulin / re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
2018/03/20 01:19:45
| voter | yeshulin |
| author | yeshulin |
| permlink | re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z |
| weight | 0 (0.00%) |
| Transaction Info | Block #20827277/Trx 97b38546f7250a991bfbbc449bafb70123d65c23 |
View Raw JSON Data
{
"trx_id": "97b38546f7250a991bfbbc449bafb70123d65c23",
"block": 20827277,
"trx_in_block": 23,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:19:45",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z",
"weight": 0
}
]
}yeshulinupvoted (100.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write2018/03/20 01:19:30
yeshulinupvoted (100.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write
2018/03/20 01:19:30
| voter | yeshulin |
| author | mmmyyyzzz |
| permlink | why-is-the-word-win-so-hard-to-write |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20827272/Trx 49fb5189511365ee79be2cf8a0f43908b6ddba20 |
View Raw JSON Data
{
"trx_id": "49fb5189511365ee79be2cf8a0f43908b6ddba20",
"block": 20827272,
"trx_in_block": 6,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:19:30",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "mmmyyyzzz",
"permlink": "why-is-the-word-win-so-hard-to-write",
"weight": 10000
}
]
}yeshulinremoved vote from (0.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write2018/03/20 01:19:18
yeshulinremoved vote from (0.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write
2018/03/20 01:19:18
| voter | yeshulin |
| author | mmmyyyzzz |
| permlink | why-is-the-word-win-so-hard-to-write |
| weight | 0 (0.00%) |
| Transaction Info | Block #20827268/Trx 85a509c3efabaadeda2d626ed84b0649bcf12529 |
View Raw JSON Data
{
"trx_id": "85a509c3efabaadeda2d626ed84b0649bcf12529",
"block": 20827268,
"trx_in_block": 42,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:19:18",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "mmmyyyzzz",
"permlink": "why-is-the-word-win-so-hard-to-write",
"weight": 0
}
]
}2018/03/20 01:14:45
2018/03/20 01:14:45
| required auths | [] |
| required posting auths | ["yeshulin"] |
| id | follow |
| json | ["follow",{"follower":"yeshulin","following":"oflyhigh","what":["blog"]}] |
| Transaction Info | Block #20827177/Trx 1b110a8cb86704d162653d1698f03b81aa020501 |
View Raw JSON Data
{
"trx_id": "1b110a8cb86704d162653d1698f03b81aa020501",
"block": 20827177,
"trx_in_block": 35,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:14:45",
"op": [
"custom_json",
{
"required_auths": [],
"required_posting_auths": [
"yeshulin"
],
"id": "follow",
"json": "[\"follow\",{\"follower\":\"yeshulin\",\"following\":\"oflyhigh\",\"what\":[\"blog\"]}]"
}
]
}yeshulinupvoted (100.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write2018/03/20 01:03:39
yeshulinupvoted (100.00%) @mmmyyyzzz / why-is-the-word-win-so-hard-to-write
2018/03/20 01:03:39
| voter | yeshulin |
| author | mmmyyyzzz |
| permlink | why-is-the-word-win-so-hard-to-write |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20826955/Trx b022a06313b04aa19baa40ef1d713671da1961fa |
View Raw JSON Data
{
"trx_id": "b022a06313b04aa19baa40ef1d713671da1961fa",
"block": 20826955,
"trx_in_block": 8,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:03:39",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "mmmyyyzzz",
"permlink": "why-is-the-word-win-so-hard-to-write",
"weight": 10000
}
]
}yeshulinflagged (-100.00%) @yeshulin / re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z2018/03/20 01:03:15
yeshulinflagged (-100.00%) @yeshulin / re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
2018/03/20 01:03:15
| voter | yeshulin |
| author | yeshulin |
| permlink | re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z |
| weight | -10000 (-100.00%) |
| Transaction Info | Block #20826947/Trx 0f433151a952e7de4243bc00caefb9a5c00a1ac7 |
View Raw JSON Data
{
"trx_id": "0f433151a952e7de4243bc00caefb9a5c00a1ac7",
"block": 20826947,
"trx_in_block": 62,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:03:15",
"op": [
"vote",
{
"voter": "yeshulin",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z",
"weight": -10000
}
]
}yeshulinfollowed @mmmyyyzzz2018/03/20 01:02:18
yeshulinfollowed @mmmyyyzzz
2018/03/20 01:02:18
| required auths | [] |
| required posting auths | ["yeshulin"] |
| id | follow |
| json | ["follow",{"follower":"yeshulin","following":"mmmyyyzzz","what":["blog"]}] |
| Transaction Info | Block #20826928/Trx f0f91434219059c05a32a55d821c91f62068528b |
View Raw JSON Data
{
"trx_id": "f0f91434219059c05a32a55d821c91f62068528b",
"block": 20826928,
"trx_in_block": 37,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:02:18",
"op": [
"custom_json",
{
"required_auths": [],
"required_posting_auths": [
"yeshulin"
],
"id": "follow",
"json": "[\"follow\",{\"follower\":\"yeshulin\",\"following\":\"mmmyyyzzz\",\"what\":[\"blog\"]}]"
}
]
}2018/03/20 01:02:18
2018/03/20 01:02:18
| parent author | mmmyyyzzz |
| parent permlink | re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z |
| author | yeshulin |
| permlink | re-mmmyyyzzz-re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t010156496z |
| title | |
| body | 区块链原理的讲解。The principle of blockchain.bitcoin,eth,EOS... |
| json metadata | {"tags":["building"],"app":"steemit/0.1"} |
| Transaction Info | Block #20826928/Trx 998de650288b400a27e30e0b003ae186c3c0a181 |
View Raw JSON Data
{
"trx_id": "998de650288b400a27e30e0b003ae186c3c0a181",
"block": 20826928,
"trx_in_block": 29,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T01:02:18",
"op": [
"comment",
{
"parent_author": "mmmyyyzzz",
"parent_permlink": "re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t010156496z",
"title": "",
"body": "区块链原理的讲解。The principle of blockchain.bitcoin,eth,EOS...",
"json_metadata": "{\"tags\":[\"building\"],\"app\":\"steemit/0.1\"}"
}
]
}2018/03/20 00:56:06
2018/03/20 00:56:06
| parent author | mmmyyyzzz |
| parent permlink | why-is-the-word-win-so-hard-to-write |
| author | yeshulin |
| permlink | re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z |
| title | |
| body | Chinese culture is very profound,china has a long history. |
| json metadata | {"tags":["travel"],"app":"steemit/0.1"} |
| Transaction Info | Block #20826804/Trx a82f51848b05ff2adfdcfde7474cfd4d1d107bc1 |
View Raw JSON Data
{
"trx_id": "a82f51848b05ff2adfdcfde7474cfd4d1d107bc1",
"block": 20826804,
"trx_in_block": 35,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T00:56:06",
"op": [
"comment",
{
"parent_author": "mmmyyyzzz",
"parent_permlink": "why-is-the-word-win-so-hard-to-write",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z",
"title": "",
"body": "Chinese culture is very profound,china has a long history.",
"json_metadata": "{\"tags\":[\"travel\"],\"app\":\"steemit/0.1\"}"
}
]
}2018/03/20 00:54:21
2018/03/20 00:54:21
| parent author | mmmyyyzzz |
| parent permlink | why-is-the-word-win-so-hard-to-write |
| author | yeshulin |
| permlink | re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005411627z |
| title | |
| body | ok,I will keep you. |
| json metadata | {"tags":["travel"],"app":"steemit/0.1"} |
| Transaction Info | Block #20826769/Trx aad0787d035a930d0d9ad522045e2f3eecc5f017 |
View Raw JSON Data
{
"trx_id": "aad0787d035a930d0d9ad522045e2f3eecc5f017",
"block": 20826769,
"trx_in_block": 33,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T00:54:21",
"op": [
"comment",
{
"parent_author": "mmmyyyzzz",
"parent_permlink": "why-is-the-word-win-so-hard-to-write",
"author": "yeshulin",
"permlink": "re-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005411627z",
"title": "",
"body": "ok,I will keep you.",
"json_metadata": "{\"tags\":[\"travel\"],\"app\":\"steemit/0.1\"}"
}
]
}2018/03/20 00:52:42
2018/03/20 00:52:42
| parent author | yeshulin |
| parent permlink | building-blockchain-in-go-part-4-transactions |
| author | mmmyyyzzz |
| permlink | re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z |
| title | |
| body | 这是什么 |
| json metadata | {"tags":["building"],"app":"steemit/0.1"} |
| Transaction Info | Block #20826736/Trx 5443816844ddda86df4ec8402ba83c0edfdee461 |
View Raw JSON Data
{
"trx_id": "5443816844ddda86df4ec8402ba83c0edfdee461",
"block": 20826736,
"trx_in_block": 22,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T00:52:42",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "building-blockchain-in-go-part-4-transactions",
"author": "mmmyyyzzz",
"permlink": "re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z",
"title": "",
"body": "这是什么",
"json_metadata": "{\"tags\":[\"building\"],\"app\":\"steemit/0.1\"}"
}
]
}mmmyyyzzzupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/20 00:52:03
mmmyyyzzzupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/20 00:52:03
| voter | mmmyyyzzz |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20826723/Trx 90601c7057e503c639a934967296e20069d38117 |
View Raw JSON Data
{
"trx_id": "90601c7057e503c639a934967296e20069d38117",
"block": 20826723,
"trx_in_block": 20,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-20T00:52:03",
"op": [
"vote",
{
"voter": "mmmyyyzzz",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 10000
}
]
}2018/03/19 14:49:12
2018/03/19 14:49:12
| parent author | yeshulin |
| parent permlink | building-blockchain-in-go-part-4-transactions |
| author | cheetah |
| permlink | cheetah-re-yeshulinbuilding-blockchain-in-go-part-4-transactions |
| title | |
| body | Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in: https://jeiwan.cc/posts/building-blockchain-in-go-part-4/ |
| json metadata | |
| Transaction Info | Block #20814671/Trx 5ea0d5dc80aa825d7a83eb95926e67778149c359 |
View Raw JSON Data
{
"trx_id": "5ea0d5dc80aa825d7a83eb95926e67778149c359",
"block": 20814671,
"trx_in_block": 33,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T14:49:12",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "building-blockchain-in-go-part-4-transactions",
"author": "cheetah",
"permlink": "cheetah-re-yeshulinbuilding-blockchain-in-go-part-4-transactions",
"title": "",
"body": "Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:\nhttps://jeiwan.cc/posts/building-blockchain-in-go-part-4/",
"json_metadata": ""
}
]
}cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-4-transactions2018/03/19 14:49:09
cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-4-transactions
2018/03/19 14:49:09
| voter | cheetah |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| weight | 8 (0.08%) |
| Transaction Info | Block #20814670/Trx 5cb34b48dba39f01496f9b3461be7daab55a0bcd |
View Raw JSON Data
{
"trx_id": "5cb34b48dba39f01496f9b3461be7daab55a0bcd",
"block": 20814670,
"trx_in_block": 4,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T14:49:09",
"op": [
"vote",
{
"voter": "cheetah",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"weight": 8
}
]
}yeshulinpublished a new post: building-blockchain-in-go-part-4-transactions2018/03/19 14:48:48
yeshulinpublished a new post: building-blockchain-in-go-part-4-transactions
2018/03/19 14:48:48
| parent author | |
| parent permlink | building |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-4-transactions |
| title | Building Blockchain in Go. Part 4: Transactions |
| body | func (bc *Blockchain) FindUTXO(address string) []TXOutput { var UTXOs []TXOutput unspentTransactions := bc.FindUnspentTransactions(address) for _, tx := range unspentTransactions { for _, out := range tx.Vout { if out.CanBeUnlockedWith(address) { UTXOs = append(UTXOs, out) } } } return UTXOs } That’s it! Now we can implement getbalance command: func (cli *CLI) getBalance(address string) { bc := NewBlockchain(address) defer bc.db.Close() balance := 0 UTXOs := bc.FindUTXO(address) for _, out := range UTXOs { balance += out.Value } fmt.Printf("Balance of '%s': %d\n", address, balance) } The account balance is the sum of values of all unspent transaction outputs locked by the account address. Let’s check our balance after mining the genesis block: $ blockchain_go getbalance -address Ivan Balance of 'Ivan': 10 This is our first money! Sending Coins Now, we want to send some coins to someone else. For this, we need to create a new transaction, put it in a block, and mine the block. So far, we implemented only the coinbase transaction (which is a special type of transactions), now we need a general transaction: func NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction { var inputs []TXInput var outputs []TXOutput acc, validOutputs := bc.FindSpendableOutputs(from, amount) if acc < amount { log.Panic("ERROR: Not enough funds") } // Build a list of inputs for txid, outs := range validOutputs { txID, err := hex.DecodeString(txid) for _, out := range outs { input := TXInput{txID, out, from} inputs = append(inputs, input) } } // Build a list of outputs outputs = append(outputs, TXOutput{amount, to}) if acc > amount { outputs = append(outputs, TXOutput{acc - amount, from}) // a change } tx := Transaction{nil, inputs, outputs} tx.SetID() return &tx } Before creating new outputs, we first have to find all unspent outputs and ensure that they store enough value. This is what FindSpendableOutputs method does. After that, for each found output an input referencing it is created. Next, we create two outputs: One that’s locked with the receiver address. This is the actual transferring of coins to other address. One that’s locked with the sender address. This is a change. It’s only created when unspent outputs hold more value than required for the new transaction. Remember: outputs are indivisible. FindSpendableOutputs method is based on the FindUnspentTransactions method we defined earlier: func (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) { unspentOutputs := make(map[string][]int) unspentTXs := bc.FindUnspentTransactions(address) accumulated := 0 Work: for _, tx := range unspentTXs { txID := hex.EncodeToString(tx.ID) for outIdx, out := range tx.Vout { if out.CanBeUnlockedWith(address) && accumulated < amount { accumulated += out.Value unspentOutputs[txID] = append(unspentOutputs[txID], outIdx) if accumulated >= amount { break Work } } } } return accumulated, unspentOutputs } The method iterates over all unspent transactions and accumulates their values. When the accumulated value is more or equals to the amount we want to transfer, it stops and returns the accumulated value and output indices grouped by transaction IDs. We don’t want to take more than we’re going to spend. Now we can modify the Blockchain.MineBlock method: func (bc *Blockchain) MineBlock(transactions []*Transaction) { ... newBlock := NewBlock(transactions, lastHash) ... } Finally, let’s implement send command: func (cli *CLI) send(from, to string, amount int) { bc := NewBlockchain(from) defer bc.db.Close() tx := NewUTXOTransaction(from, to, amount, bc) bc.MineBlock([]*Transaction{tx}) fmt.Println("Success!") } Sending coins means creating a transaction and adding it to the blockchain via mining a block. But Bitcoin doesn’t do this immediately (as we do). Instead, it puts all new transactions into memory pool (or mempool), and when a miner is ready to mine a block, it takes all transactions from the mempool and creates a candidate block. Transactions become confirmed only when a block containing them is mined and added to the blockchain. Let’s check that sending coins works: $ blockchain_go send -from Ivan -to Pedro -amount 6 00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37 Success! $ blockchain_go getbalance -address Ivan Balance of 'Ivan': 4 $ blockchain_go getbalance -address Pedro Balance of 'Pedro': 6 Nice! Now, let’s create more transactions and ensure that sending from multiple outputs works fine: $ blockchain_go send -from Pedro -to Helen -amount 2 00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf Success! $ blockchain_go send -from Ivan -to Helen -amount 2 000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa Success! Now, Helen’s coins are locked in two outputs: one from Pedro and one from Ivan. Let’s send them to someone else: $ blockchain_go send -from Helen -to Rachel -amount 3 000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0 Success! $ blockchain_go getbalance -address Ivan Balance of 'Ivan': 2 $ blockchain_go getbalance -address Pedro Balance of 'Pedro': 4 $ blockchain_go getbalance -address Helen Balance of 'Helen': 1 $ blockchain_go getbalance -address Rachel Balance of 'Rachel': 3 Looks fine! Now let’s test a failure: $ blockchain_go send -from Pedro -to Ivan -amount 5 panic: ERROR: Not enough funds $ blockchain_go getbalance -address Pedro Balance of 'Pedro': 4 $ blockchain_go getbalance -address Ivan Balance of 'Ivan': 2 Conclusion Phew! It wasn’t easy, but we have transactions now! Although, some key features of a Bitcoin-like cryptocurrency are missing: Addresses. We don’t have real, private key based addresses yet. Rewards. Mining blocks is absolutely not profitable! UTXO set. Getting balance requires scanning the whole blockchain, which can take very long time when there are many and many blocks. Also, it can take a lot of time if we want to validate later transactions. UTXO set is intended to solve these problems and make operations with transactions fast. Mempool. This is where transactions are stored before being packed in a block. In our current implementation, a block contains only one transaction, and this is quite inefficient. |
| json metadata | {"tags":["building","blockchain","btc","transactions"],"app":"steemit/0.1","format":"markdown"} |
| Transaction Info | Block #20814663/Trx 183832bfbc7ae34f7730dfceb694b431bcfcca39 |
View Raw JSON Data
{
"trx_id": "183832bfbc7ae34f7730dfceb694b431bcfcca39",
"block": 20814663,
"trx_in_block": 4,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T14:48:48",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "building",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-4-transactions",
"title": "Building Blockchain in Go. Part 4: Transactions",
"body": "func (bc *Blockchain) FindUTXO(address string) []TXOutput {\n var UTXOs []TXOutput\n unspentTransactions := bc.FindUnspentTransactions(address)\n\n for _, tx := range unspentTransactions {\n for _, out := range tx.Vout {\n if out.CanBeUnlockedWith(address) {\n UTXOs = append(UTXOs, out)\n }\n }\n }\n\n return UTXOs\n}\nThat’s it! Now we can implement getbalance command:\n\nfunc (cli *CLI) getBalance(address string) {\n\tbc := NewBlockchain(address)\n\tdefer bc.db.Close()\n\n\tbalance := 0\n\tUTXOs := bc.FindUTXO(address)\n\n\tfor _, out := range UTXOs {\n\t\tbalance += out.Value\n\t}\n\n\tfmt.Printf(\"Balance of '%s': %d\\n\", address, balance)\n}\n\nThe account balance is the sum of values of all unspent transaction outputs locked by the account address.\n\nLet’s check our balance after mining the genesis block:\n\n$ blockchain_go getbalance -address Ivan\nBalance of 'Ivan': 10\nThis is our first money!\n\nSending Coins\nNow, we want to send some coins to someone else. For this, we need to create a new transaction, put it in a block, and mine the block. So far, we implemented only the coinbase transaction (which is a special type of transactions), now we need a general transaction:\n\nfunc NewUTXOTransaction(from, to string, amount int, bc *Blockchain) *Transaction {\n\tvar inputs []TXInput\n\tvar outputs []TXOutput\n\n\tacc, validOutputs := bc.FindSpendableOutputs(from, amount)\n\n\tif acc < amount {\n\t\tlog.Panic(\"ERROR: Not enough funds\")\n\t}\n\n\t// Build a list of inputs\n\tfor txid, outs := range validOutputs {\n\t\ttxID, err := hex.DecodeString(txid)\n\n\t\tfor _, out := range outs {\n\t\t\tinput := TXInput{txID, out, from}\n\t\t\tinputs = append(inputs, input)\n\t\t}\n\t}\n\n\t// Build a list of outputs\n\toutputs = append(outputs, TXOutput{amount, to})\n\tif acc > amount {\n\t\toutputs = append(outputs, TXOutput{acc - amount, from}) // a change\n\t}\n\n\ttx := Transaction{nil, inputs, outputs}\n\ttx.SetID()\n\n\treturn &tx\n}\nBefore creating new outputs, we first have to find all unspent outputs and ensure that they store enough value. This is what FindSpendableOutputs method does. After that, for each found output an input referencing it is created. Next, we create two outputs:\n\nOne that’s locked with the receiver address. This is the actual transferring of coins to other address.\nOne that’s locked with the sender address. This is a change. It’s only created when unspent outputs hold more value than required for the new transaction. Remember: outputs are indivisible.\nFindSpendableOutputs method is based on the FindUnspentTransactions method we defined earlier:\n\nfunc (bc *Blockchain) FindSpendableOutputs(address string, amount int) (int, map[string][]int) {\n\tunspentOutputs := make(map[string][]int)\n\tunspentTXs := bc.FindUnspentTransactions(address)\n\taccumulated := 0\n\nWork:\n\tfor _, tx := range unspentTXs {\n\t\ttxID := hex.EncodeToString(tx.ID)\n\n\t\tfor outIdx, out := range tx.Vout {\n\t\t\tif out.CanBeUnlockedWith(address) && accumulated < amount {\n\t\t\t\taccumulated += out.Value\n\t\t\t\tunspentOutputs[txID] = append(unspentOutputs[txID], outIdx)\n\n\t\t\t\tif accumulated >= amount {\n\t\t\t\t\tbreak Work\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn accumulated, unspentOutputs\n}\n\nThe method iterates over all unspent transactions and accumulates their values. When the accumulated value is more or equals to the amount we want to transfer, it stops and returns the accumulated value and output indices grouped by transaction IDs. We don’t want to take more than we’re going to spend.\n\nNow we can modify the Blockchain.MineBlock method:\n\nfunc (bc *Blockchain) MineBlock(transactions []*Transaction) {\n\t...\n\tnewBlock := NewBlock(transactions, lastHash)\n\t...\n}\nFinally, let’s implement send command:\n\nfunc (cli *CLI) send(from, to string, amount int) {\n\tbc := NewBlockchain(from)\n\tdefer bc.db.Close()\n\n\ttx := NewUTXOTransaction(from, to, amount, bc)\n\tbc.MineBlock([]*Transaction{tx})\n\tfmt.Println(\"Success!\")\n}\nSending coins means creating a transaction and adding it to the blockchain via mining a block. But Bitcoin doesn’t do this immediately (as we do). Instead, it puts all new transactions into memory pool (or mempool), and when a miner is ready to mine a block, it takes all transactions from the mempool and creates a candidate block. Transactions become confirmed only when a block containing them is mined and added to the blockchain.\n\nLet’s check that sending coins works:\n\n$ blockchain_go send -from Ivan -to Pedro -amount 6\n00000001b56d60f86f72ab2a59fadb197d767b97d4873732be505e0a65cc1e37\n\nSuccess!\n\n$ blockchain_go getbalance -address Ivan\nBalance of 'Ivan': 4\n\n$ blockchain_go getbalance -address Pedro\nBalance of 'Pedro': 6\nNice! Now, let’s create more transactions and ensure that sending from multiple outputs works fine:\n\n$ blockchain_go send -from Pedro -to Helen -amount 2\n00000099938725eb2c7730844b3cd40209d46bce2c2af9d87c2b7611fe9d5bdf\n\nSuccess!\n\n$ blockchain_go send -from Ivan -to Helen -amount 2\n000000a2edf94334b1d94f98d22d7e4c973261660397dc7340464f7959a7a9aa\n\nSuccess!\nNow, Helen’s coins are locked in two outputs: one from Pedro and one from Ivan. Let’s send them to someone else:\n\n$ blockchain_go send -from Helen -to Rachel -amount 3\n000000c58136cffa669e767b8f881d16e2ede3974d71df43058baaf8c069f1a0\n\nSuccess!\n\n$ blockchain_go getbalance -address Ivan\nBalance of 'Ivan': 2\n\n$ blockchain_go getbalance -address Pedro\nBalance of 'Pedro': 4\n\n$ blockchain_go getbalance -address Helen\nBalance of 'Helen': 1\n\n$ blockchain_go getbalance -address Rachel\nBalance of 'Rachel': 3\nLooks fine! Now let’s test a failure:\n\n$ blockchain_go send -from Pedro -to Ivan -amount 5\npanic: ERROR: Not enough funds\n\n$ blockchain_go getbalance -address Pedro\nBalance of 'Pedro': 4\n\n$ blockchain_go getbalance -address Ivan\nBalance of 'Ivan': 2\nConclusion\nPhew! It wasn’t easy, but we have transactions now! Although, some key features of a Bitcoin-like cryptocurrency are missing:\n\nAddresses. We don’t have real, private key based addresses yet.\nRewards. Mining blocks is absolutely not profitable!\nUTXO set. Getting balance requires scanning the whole blockchain, which can take very long time when there are many and many blocks. Also, it can take a lot of time if we want to validate later transactions. UTXO set is intended to solve these problems and make operations with transactions fast.\nMempool. This is where transactions are stored before being packed in a block. In our current implementation, a block contains only one transaction, and this is quite inefficient.",
"json_metadata": "{\"tags\":[\"building\",\"blockchain\",\"btc\",\"transactions\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}"
}
]
}steemitboardupvoted (1.00%) @yeshulin / 2jovhl2018/03/19 14:29:06
steemitboardupvoted (1.00%) @yeshulin / 2jovhl
2018/03/19 14:29:06
| voter | steemitboard |
| author | yeshulin |
| permlink | 2jovhl |
| weight | 100 (1.00%) |
| Transaction Info | Block #20814269/Trx cff69ce30c9cb775bf63c8f349b57876401dfecc |
View Raw JSON Data
{
"trx_id": "cff69ce30c9cb775bf63c8f349b57876401dfecc",
"block": 20814269,
"trx_in_block": 18,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T14:29:06",
"op": [
"vote",
{
"voter": "steemitboard",
"author": "yeshulin",
"permlink": "2jovhl",
"weight": 100
}
]
}2018/03/19 14:29:03
2018/03/19 14:29:03
| parent author | yeshulin |
| parent permlink | 2jovhl |
| author | steemitboard |
| permlink | steemitboard-notify-yeshulin-20180319t142905000z |
| title | |
| body | Congratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) : [](http://steemitboard.com/@yeshulin) Award for the number of posts published [](http://steemitboard.com/@yeshulin) Award for the number of upvotes received [](http://steemitboard.com/@yeshulin) You published 4 posts in one day Click on any badge to view your own Board of Honor on SteemitBoard. To support your work, I also upvoted your post! For more information about SteemitBoard, click [here](https://steemit.com/@steemitboard) If you no longer want to receive notifications, reply to this comment with the word `STOP` > Upvote this notification to help all Steemit users. Learn why [here](https://steemit.com/steemitboard/@steemitboard/http-i-cubeupload-com-7ciqeo-png)! |
| json metadata | {"image":["https://steemitboard.com/img/notifications.png"]} |
| Transaction Info | Block #20814268/Trx f71fb0a65e0fc798d55879926a683c59eef1bedc |
View Raw JSON Data
{
"trx_id": "f71fb0a65e0fc798d55879926a683c59eef1bedc",
"block": 20814268,
"trx_in_block": 55,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T14:29:03",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "2jovhl",
"author": "steemitboard",
"permlink": "steemitboard-notify-yeshulin-20180319t142905000z",
"title": "",
"body": "Congratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) :\n\n[](http://steemitboard.com/@yeshulin) Award for the number of posts published\n[](http://steemitboard.com/@yeshulin) Award for the number of upvotes received\n[](http://steemitboard.com/@yeshulin) You published 4 posts in one day\n\nClick on any badge to view your own Board of Honor on SteemitBoard.\n\nTo support your work, I also upvoted your post!\nFor more information about SteemitBoard, click [here](https://steemit.com/@steemitboard)\n\nIf you no longer want to receive notifications, reply to this comment with the word `STOP`\n\n> Upvote this notification to help all Steemit users. Learn why [here](https://steemit.com/steemitboard/@steemitboard/http-i-cubeupload-com-7ciqeo-png)!",
"json_metadata": "{\"image\":[\"https://steemitboard.com/img/notifications.png\"]}"
}
]
}yeshulinfollowed @sensation2018/03/19 10:47:24
yeshulinfollowed @sensation
2018/03/19 10:47:24
| required auths | [] |
| required posting auths | ["yeshulin"] |
| id | follow |
| json | ["follow",{"follower":"yeshulin","following":"sensation","what":["blog"]}] |
| Transaction Info | Block #20809835/Trx 5d8937007688906a5a23d0286f720f3bc312cb8c |
View Raw JSON Data
{
"trx_id": "5d8937007688906a5a23d0286f720f3bc312cb8c",
"block": 20809835,
"trx_in_block": 46,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T10:47:24",
"op": [
"custom_json",
{
"required_auths": [],
"required_posting_auths": [
"yeshulin"
],
"id": "follow",
"json": "[\"follow\",{\"follower\":\"yeshulin\",\"following\":\"sensation\",\"what\":[\"blog\"]}]"
}
]
}yeshulinfollowed @darkvaders2018/03/19 10:47:15
yeshulinfollowed @darkvaders
2018/03/19 10:47:15
| required auths | [] |
| required posting auths | ["yeshulin"] |
| id | follow |
| json | ["follow",{"follower":"yeshulin","following":"darkvaders","what":["blog"]}] |
| Transaction Info | Block #20809832/Trx acebe4ded0c3aa04176427b4ef88246f0a61f7a1 |
View Raw JSON Data
{
"trx_id": "acebe4ded0c3aa04176427b4ef88246f0a61f7a1",
"block": 20809832,
"trx_in_block": 55,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T10:47:15",
"op": [
"custom_json",
{
"required_auths": [],
"required_posting_auths": [
"yeshulin"
],
"id": "follow",
"json": "[\"follow\",{\"follower\":\"yeshulin\",\"following\":\"darkvaders\",\"what\":[\"blog\"]}]"
}
]
}darkvadersupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work2018/03/19 00:51:51
darkvadersupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work
2018/03/19 00:51:51
| voter | darkvaders |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-2-proof-of-work |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20797926/Trx 329736c8710268c3a9cfe1f1c4500ea650a4ea52 |
View Raw JSON Data
{
"trx_id": "329736c8710268c3a9cfe1f1c4500ea650a4ea52",
"block": 20797926,
"trx_in_block": 6,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-19T00:51:51",
"op": [
"vote",
{
"voter": "darkvaders",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-2-proof-of-work",
"weight": 10000
}
]
}sensationupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work2018/03/18 15:54:18
sensationupvoted (100.00%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work
2018/03/18 15:54:18
| voter | sensation |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-2-proof-of-work |
| weight | 10000 (100.00%) |
| Transaction Info | Block #20787175/Trx e366dfb1d24ef9aef30e08998213ab744334d925 |
View Raw JSON Data
{
"trx_id": "e366dfb1d24ef9aef30e08998213ab744334d925",
"block": 20787175,
"trx_in_block": 35,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T15:54:18",
"op": [
"vote",
{
"voter": "sensation",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-2-proof-of-work",
"weight": 10000
}
]
}2018/03/18 15:04:27
2018/03/18 15:04:27
| parent author | |
| parent permlink | chengdu |
| author | yeshulin |
| permlink | 2jovhl |
| title | 成都哪些美食值得一吃呢? |
| body | @@ -215,16 +215,105 @@ %E7%B2%BE%E7%AD%89%EF%BC%8C%E6%9E%81%E5%85%B6%E7%AE%80%E5%8D%95%E3%80%82 +%0A!%5B%5D(https://steemitimages.com/DQmNtk3oxQLwB2rYQt6DqFjPiGATnu5sacy5MxfLu9vuXFH/image.png) %0A%0A%E2%80%83%E2%80%83NO.2 @@ -449,16 +449,105 @@ %E5%A4%AB%E5%A6%BB%E8%82%BA%E7%89%87%E2%80%9D%E3%80%82%0A%0A +!%5B%5D(https://steemitimages.com/DQma8zVKWxpB9DndEk9trdzM7Bc2ebrSjxcWSaPqpSEL322/image.png)%0A %E2%80%83%E2%80%83NO.3%E9%BE%99%E6%8A%84 @@ -655,16 +655,106 @@ %E5%85%B4%E2%80%9C%E9%9A%86%E2%80%9D%E4%B9%8B%E6%84%8F%E3%80%82%0A +!%5B%5D(https://steemitimages.com/DQmWKjfKosfRK64M89nNQtauM7qLjUXhr5xxy4UQgB2M3mR/image.png)%0A%0A %0A%E2%80%83%E2%80%83NO.4%E8%B5%96 @@ -1710,8 +1710,97 @@ %E5%AE%B6%E5%90%83%E6%9C%80%E5%A5%BD%E7%9A%84%E5%B7%9D%E8%8F%9C%EF%BC%8E +%0A!%5B%5D(https://steemitimages.com/DQmXodaEe9pu3qdxiLKHYYqvtz9MNBd7qCo4dZgjuXztvDZ/image.png) |
| json metadata | {"tags":["chengdu","fine","foods"],"app":"steemit/0.1","format":"markdown","image":["https://steemitimages.com/DQmNtk3oxQLwB2rYQt6DqFjPiGATnu5sacy5MxfLu9vuXFH/image.png","https://steemitimages.com/DQma8zVKWxpB9DndEk9trdzM7Bc2ebrSjxcWSaPqpSEL322/image.png","https://steemitimages.com/DQmWKjfKosfRK64M89nNQtauM7qLjUXhr5xxy4UQgB2M3mR/image.png","https://steemitimages.com/DQmXodaEe9pu3qdxiLKHYYqvtz9MNBd7qCo4dZgjuXztvDZ/image.png"]} |
| Transaction Info | Block #20786178/Trx deafa64a266bfba780daa12121934cd8a4b4a4ff |
View Raw JSON Data
{
"trx_id": "deafa64a266bfba780daa12121934cd8a4b4a4ff",
"block": 20786178,
"trx_in_block": 83,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T15:04:27",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "chengdu",
"author": "yeshulin",
"permlink": "2jovhl",
"title": "成都哪些美食值得一吃呢?",
"body": "@@ -215,16 +215,105 @@\n %E7%B2%BE%E7%AD%89%EF%BC%8C%E6%9E%81%E5%85%B6%E7%AE%80%E5%8D%95%E3%80%82\n+%0A!%5B%5D(https://steemitimages.com/DQmNtk3oxQLwB2rYQt6DqFjPiGATnu5sacy5MxfLu9vuXFH/image.png)\n %0A%0A%E2%80%83%E2%80%83NO.2\n@@ -449,16 +449,105 @@\n %E5%A4%AB%E5%A6%BB%E8%82%BA%E7%89%87%E2%80%9D%E3%80%82%0A%0A\n+!%5B%5D(https://steemitimages.com/DQma8zVKWxpB9DndEk9trdzM7Bc2ebrSjxcWSaPqpSEL322/image.png)%0A\n %E2%80%83%E2%80%83NO.3%E9%BE%99%E6%8A%84\n@@ -655,16 +655,106 @@\n %E5%85%B4%E2%80%9C%E9%9A%86%E2%80%9D%E4%B9%8B%E6%84%8F%E3%80%82%0A\n+!%5B%5D(https://steemitimages.com/DQmWKjfKosfRK64M89nNQtauM7qLjUXhr5xxy4UQgB2M3mR/image.png)%0A%0A\n %0A%E2%80%83%E2%80%83NO.4%E8%B5%96\n@@ -1710,8 +1710,97 @@\n %E5%AE%B6%E5%90%83%E6%9C%80%E5%A5%BD%E7%9A%84%E5%B7%9D%E8%8F%9C%EF%BC%8E\n+%0A!%5B%5D(https://steemitimages.com/DQmXodaEe9pu3qdxiLKHYYqvtz9MNBd7qCo4dZgjuXztvDZ/image.png)\n",
"json_metadata": "{\"tags\":[\"chengdu\",\"fine\",\"foods\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\",\"image\":[\"https://steemitimages.com/DQmNtk3oxQLwB2rYQt6DqFjPiGATnu5sacy5MxfLu9vuXFH/image.png\",\"https://steemitimages.com/DQma8zVKWxpB9DndEk9trdzM7Bc2ebrSjxcWSaPqpSEL322/image.png\",\"https://steemitimages.com/DQmWKjfKosfRK64M89nNQtauM7qLjUXhr5xxy4UQgB2M3mR/image.png\",\"https://steemitimages.com/DQmXodaEe9pu3qdxiLKHYYqvtz9MNBd7qCo4dZgjuXztvDZ/image.png\"]}"
}
]
}2018/03/18 14:58:42
2018/03/18 14:58:42
| parent author | |
| parent permlink | chengdu |
| author | yeshulin |
| permlink | 2jovhl |
| title | 成都哪些美食值得一吃呢? |
| body | NO.1火锅 到成都,火锅是必不可少的体验。成都火锅注重麻辣鲜香,多数用猪油或牛油作底料,辅以大量的麻椒和花椒,口感麻辣。食材多用四川人喜欢的黄喉、毛肚、鹅肠、鳝丝等。 喜吃火锅的四川人都有一种用“干碟”的吃法(“碟”即调料)。四川人往往是用一个香油碟再加一个干碟,根据自己的口味,有些食物蘸香油碟(比如羊肉、 藕片、鱼等),有些食物蘸干碟(比如毛肚、腰片、鸭肠、黄喉等)。所谓“干碟”,其实是一小碟干的辣椒粉,加上盐,味精等,极其简单。 NO.2夫妻肺片 成都地区人人皆知的一款风味名菜。相传在30年代,成都少城附近,有一男子名郭朝华,与其妻一道以制售凉拌牛肺片为业,他们夫妻俩亲自操作,走街串巷,提篮叫卖。由于他们经营的凉拌肺片制作精细,风味独特,深受人们喜爱。为区别一般肺片摊店,人们称他们为“夫妻肺片”。 NO.3龙抄手 龙抄手皮薄馅嫩,爽滑鲜香,汤浓色白,为蓉城小吃的佼佼者。龙抄手的得名与钟水饺不一样,并非老板姓龙,而是当初三个伙计在“浓花茶园”商议开抄手店,取“浓”的谐音“龙”为名,也寓有“龙腾虎跃”、生意兴“隆”之意。 NO.4赖汤圆 赖汤圆迄今已有百年历史。老板赖源鑫从1894年起就在成都沿街煮卖汤圆,他制作的汤圆煮时不烂皮、不露馅、不浑汤,吃时不粘筷、不粘牙、不腻口,滋润香甜,爽滑软糯,成为成都最负盛名的小吃。现在的赖汤圆,保持了老字号名优小吃的质量,其色滑洁白,皮粑绵糯,甜香油重,营养丰富。 NO.5豆花 白白的豆花上面撒上葱花、花生末、大头菜粒或者萝卜干,还有炒得嘎嘣脆的黄豆,再根据口味加酱油、醋、辣椒油。豆花嫩滑绵软,嫩得入口即滑,却又绵得用筷子挑一团起来,不碎不裂。古早时期,沿街叫卖的豆花没有那么多花头,现在就不一样了,各种风味各种浇头任君挑选,香辣、麻辣、酸辣,还有牛肉豆花、鳝鱼豆花、肥肠豆花等等。 NO.6串串香 在成都的大街小巷,随处可见串串香铺子。红漆的矮方桌、小凳子和热气腾腾的一锅红汤以及大把的竹签就构成了成都特别的一景。海带、土豆、肉片、花菜、莴笋、毛肚、香肠、鱿鱼、冬瓜、黄腊丁、贡菜、海白菜、魔芋、黄花、藕、空心菜、排骨等各种食材往竹签上一串,客人随意选取喜欢的串串,一边涮一边吃,和火锅的吃法差不多,因而也有人称之为“小火锅”。吃完之后叫老板“数签签”结账走人,好不潇洒! NO.7钟水饺 钟水饺与北方水饺的主要区别是全用猪肉馅,不加其它鲜菜,上桌时淋上特制的红油,微甜带咸,兼有辛辣,风味独特。钟水饺具有皮薄(10个水饺才50克)、料精(上等面粉、剔筋去皮的精选猪肉)、馅嫩(全靠加工时掌握好温度、水分,肉馅细嫩化渣)、味鲜(全靠辅料和红油、原汤)的特色。 钟水饺是全肉的,馅里没有菜,味道是辣中带甜,很好吃。 NO.10钵钵鸡 钵钵鸡是成都非常流行的特色小吃,钵钵鸡是成都非常流行的特色小吃,一听名字就觉得很新奇,“钵钵”其实就是瓦罐,钵外面是画着红黄相间的瓷质龙纹,钵内盛放配以麻辣为主的佐料,菜品在特珠殊加工后用签串制,晾冷浸于各种口味的佐料中,食用时自取自食,除味道悠长外更添情趣盎然。 No.11 各种川菜 川菜最近几年被火锅,串串等压制,另外加上各种酒店为了适应越来越多的外地人,正宗的川菜却越来越少.但是川菜菜品太多,回锅肉、麻婆豆腐、酸菜鱼、芋儿鸡,太多太多的吃不过来. 欢迎大家来到成都,我一定带大家吃最好的川菜. |
| json metadata | {"tags":["chengdu","fine","foods"],"app":"steemit/0.1","format":"markdown"} |
| Transaction Info | Block #20786063/Trx d6a8ec89bae0dd1ef9cf73b657e1abb49357a9e8 |
View Raw JSON Data
{
"trx_id": "d6a8ec89bae0dd1ef9cf73b657e1abb49357a9e8",
"block": 20786063,
"trx_in_block": 43,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:58:42",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "chengdu",
"author": "yeshulin",
"permlink": "2jovhl",
"title": "成都哪些美食值得一吃呢?",
"body": "NO.1火锅\n\n 到成都,火锅是必不可少的体验。成都火锅注重麻辣鲜香,多数用猪油或牛油作底料,辅以大量的麻椒和花椒,口感麻辣。食材多用四川人喜欢的黄喉、毛肚、鹅肠、鳝丝等。 喜吃火锅的四川人都有一种用“干碟”的吃法(“碟”即调料)。四川人往往是用一个香油碟再加一个干碟,根据自己的口味,有些食物蘸香油碟(比如羊肉、 藕片、鱼等),有些食物蘸干碟(比如毛肚、腰片、鸭肠、黄喉等)。所谓“干碟”,其实是一小碟干的辣椒粉,加上盐,味精等,极其简单。\n\n NO.2夫妻肺片\n\n 成都地区人人皆知的一款风味名菜。相传在30年代,成都少城附近,有一男子名郭朝华,与其妻一道以制售凉拌牛肺片为业,他们夫妻俩亲自操作,走街串巷,提篮叫卖。由于他们经营的凉拌肺片制作精细,风味独特,深受人们喜爱。为区别一般肺片摊店,人们称他们为“夫妻肺片”。\n\n NO.3龙抄手\n\n 龙抄手皮薄馅嫩,爽滑鲜香,汤浓色白,为蓉城小吃的佼佼者。龙抄手的得名与钟水饺不一样,并非老板姓龙,而是当初三个伙计在“浓花茶园”商议开抄手店,取“浓”的谐音“龙”为名,也寓有“龙腾虎跃”、生意兴“隆”之意。\n\n NO.4赖汤圆\n\n 赖汤圆迄今已有百年历史。老板赖源鑫从1894年起就在成都沿街煮卖汤圆,他制作的汤圆煮时不烂皮、不露馅、不浑汤,吃时不粘筷、不粘牙、不腻口,滋润香甜,爽滑软糯,成为成都最负盛名的小吃。现在的赖汤圆,保持了老字号名优小吃的质量,其色滑洁白,皮粑绵糯,甜香油重,营养丰富。\n\n NO.5豆花\n\n 白白的豆花上面撒上葱花、花生末、大头菜粒或者萝卜干,还有炒得嘎嘣脆的黄豆,再根据口味加酱油、醋、辣椒油。豆花嫩滑绵软,嫩得入口即滑,却又绵得用筷子挑一团起来,不碎不裂。古早时期,沿街叫卖的豆花没有那么多花头,现在就不一样了,各种风味各种浇头任君挑选,香辣、麻辣、酸辣,还有牛肉豆花、鳝鱼豆花、肥肠豆花等等。\n\n NO.6串串香\n\n 在成都的大街小巷,随处可见串串香铺子。红漆的矮方桌、小凳子和热气腾腾的一锅红汤以及大把的竹签就构成了成都特别的一景。海带、土豆、肉片、花菜、莴笋、毛肚、香肠、鱿鱼、冬瓜、黄腊丁、贡菜、海白菜、魔芋、黄花、藕、空心菜、排骨等各种食材往竹签上一串,客人随意选取喜欢的串串,一边涮一边吃,和火锅的吃法差不多,因而也有人称之为“小火锅”。吃完之后叫老板“数签签”结账走人,好不潇洒!\n\n NO.7钟水饺\n\n 钟水饺与北方水饺的主要区别是全用猪肉馅,不加其它鲜菜,上桌时淋上特制的红油,微甜带咸,兼有辛辣,风味独特。钟水饺具有皮薄(10个水饺才50克)、料精(上等面粉、剔筋去皮的精选猪肉)、馅嫩(全靠加工时掌握好温度、水分,肉馅细嫩化渣)、味鲜(全靠辅料和红油、原汤)的特色。 钟水饺是全肉的,馅里没有菜,味道是辣中带甜,很好吃。\n\n NO.10钵钵鸡\n\n 钵钵鸡是成都非常流行的特色小吃,钵钵鸡是成都非常流行的特色小吃,一听名字就觉得很新奇,“钵钵”其实就是瓦罐,钵外面是画着红黄相间的瓷质龙纹,钵内盛放配以麻辣为主的佐料,菜品在特珠殊加工后用签串制,晾冷浸于各种口味的佐料中,食用时自取自食,除味道悠长外更添情趣盎然。\n No.11 各种川菜\n 川菜最近几年被火锅,串串等压制,另外加上各种酒店为了适应越来越多的外地人,正宗的川菜却越来越少.但是川菜菜品太多,回锅肉、麻婆豆腐、酸菜鱼、芋儿鸡,太多太多的吃不过来.\n 欢迎大家来到成都,我一定带大家吃最好的川菜.",
"json_metadata": "{\"tags\":[\"chengdu\",\"fine\",\"foods\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}"
}
]
}2018/03/18 14:41:51
2018/03/18 14:41:51
| parent author | yeshulin |
| parent permlink | building-blockchain-in-go-part-3-persistence-and-cli |
| author | cheetah |
| permlink | cheetah-re-yeshulinbuilding-blockchain-in-go-part-3-persistence-and-cli |
| title | |
| body | Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in: https://medium.com/@Jeiwan/building-blockchain-in-go-part-3-persistence-and-cli-e0c4df50327a |
| json metadata | |
| Transaction Info | Block #20785726/Trx 97e321b6e0fcb43c7c209fc7c3190337dc478359 |
View Raw JSON Data
{
"trx_id": "97e321b6e0fcb43c7c209fc7c3190337dc478359",
"block": 20785726,
"trx_in_block": 58,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:41:51",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "building-blockchain-in-go-part-3-persistence-and-cli",
"author": "cheetah",
"permlink": "cheetah-re-yeshulinbuilding-blockchain-in-go-part-3-persistence-and-cli",
"title": "",
"body": "Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:\nhttps://medium.com/@Jeiwan/building-blockchain-in-go-part-3-persistence-and-cli-e0c4df50327a",
"json_metadata": ""
}
]
}cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-3-persistence-and-cli2018/03/18 14:41:48
cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-3-persistence-and-cli
2018/03/18 14:41:48
| voter | cheetah |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-3-persistence-and-cli |
| weight | 8 (0.08%) |
| Transaction Info | Block #20785725/Trx a93e0dedc40cc8fb507299b37c8e3c5855c517d3 |
View Raw JSON Data
{
"trx_id": "a93e0dedc40cc8fb507299b37c8e3c5855c517d3",
"block": 20785725,
"trx_in_block": 2,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:41:48",
"op": [
"vote",
{
"voter": "cheetah",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-3-persistence-and-cli",
"weight": 8
}
]
}yeshulinpublished a new post: building-blockchain-in-go-part-3-persistence-and-cli2018/03/18 14:41:03
yeshulinpublished a new post: building-blockchain-in-go-part-3-persistence-and-cli
2018/03/18 14:41:03
| parent author | |
| parent permlink | blockchain |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-3-persistence-and-cli |
| title | Building Blockchain in Go. Part 3: Persistence and CLI |
| body | Introduction So far, we’ve built a blockchain with a proof-of-work system, which makes mining possible. Our implementation is getting closer to a fully functional blockchain, but it still lacks some important features. Today will start storing a blockchain in a database, and after that we’ll make a simple command-line interface to perform operations with the blockchain. In its essence, blockchain is a distributed database. We’re going to omit the “distributed” part for now and focus on the “database” part. Database Choice Currently, there’s no database in our implementation; instead, we create blocks every time we run the program and store them in memory. We cannot reuse a blockchain, we cannot share it with others, thus we need to store it on the disk. Which database do we need? Actually, any of them. In the original Bitcoin paper, nothing is said about using a certain database, so it’s up to a developer what DB to use. Bitcoin Core, which was initially published by Satoshi Nakamoto and which is currently a reference implementation of Bitcoin, uses LevelDB (although it was introduced to the client only in 2012). And we’ll use… BoltDB Because: It’s simple and minimalistic. It’s implemented in Go. It doesn’t require to run a server. It allows to build the data structure we want. From the BoltDB’s README on Github: Bolt is a pure Go key/value store inspired by Howard Chu’s LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL. Since Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it. Sounds perfect for our needs! Let’s spend a minute reviewing it. BoltDB is a key/value storage, which means there’re no tables like in SQL RDBMS (MySQL, PostgreSQL, etc.), no rows, no columns. Instead, data is stored as key-value pairs (like in Golang maps). Key-value pairs are stored in buckets, which are intended to group similar pairs (this is similar to tables in RDBMS). Thus, in order to get a value, you need to know a bucket and a key. One important thing about BoltDB is that there are no data types: keys and values are byte arrays. Since we’ll store Go structs (Block, in particular) in it, we’ll need to serialize them, i.e. implement a mechanism of converting a Go struct into a byte array and restoring it back from a byte array. We’ll use encoding/gob for this, but JSON, XML, Protocol Buffers, etc. can be used as well. We’re using encoding/gob because it’s simple and is a part of the standard Go library. Database Structure Before starting implementing persistence logic, we first need to decide how we’ll store data in the DB. And for this, we’ll refer to the way Bitcoin Core does that. In simple words, Bitcoin Core uses two “buckets” to store data: blocks stores metadata describing all the blocks in a chain. chainstate stores the state of a chain, which is all currently unspent transaction outputs and some metadata. Also, blocks are stored as separate files on the disk. This is done for a performance purpose: reading a single block won’t require loading all (or some) of them into memory. We won’t implement this. In blocks, the key -> value pairs are: 'b' + 32-byte block hash -> block index record 'f' + 4-byte file number -> file information record 'l' -> 4-byte file number: the last block file number used 'R' -> 1-byte boolean: whether we're in the process of reindexing 'F' + 1-byte flag name length + flag name string -> 1 byte boolean: various flags that can be on or off 't' + 32-byte transaction hash -> transaction index record In chainstate, the key -> value pairs are: 'c' + 32-byte transaction hash -> unspent transaction output record for that transaction 'B' -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs (Detailed explanation can be found here) Since we don’t have transactions yet, we’re going to have only blocks bucket. Also, as said above, we will store the whole DB as a single file, without storing blocks in separate files. So we won’t need anything related to file numbers. So these are key -> value pairs we’ll use: 32-byte block-hash -> Block structure (serialized) 'l' -> the hash of the last block in a chain That’s all we need to know to start implementing the persistence mechanism. Serialization As said before, in BoltDB values can be only of []byte type, and we want to store Block structs in the DB. We’ll use encoding/gob to serialize the structs. Let’s implement Serialize method of Block (errors processing is omitted for brevity): func (b *Block) Serialize() []byte { var result bytes.Buffer encoder := gob.NewEncoder(&result) err := encoder.Encode(b) return result.Bytes() } The piece is straightforward: at first, we declare a buffer that will store serialized data; then we initialize a gob encoder and encode the block; the result is returned as a byte array. Next, we need a deserializing function that will receive a byte array as input and return a Block. This won’t be a method but an independent function: func DeserializeBlock(d []byte) *Block { var block Block decoder := gob.NewDecoder(bytes.NewReader(d)) err := decoder.Decode(&block) return &block } And that’s it for the serialization! Persistence Let’s start with the NewBlockchain function. Currently, it creates a new instance of Blockchain and adds the genesis block to it. What we want it to do is to: Open a DB file. Check if there’s a blockchain stored in it. If there’s a blockchain: Create a new Blockchain instance. Set the tip of the Blockchain instance to the last block hash stored in the DB. If there’s no existing blockchain: Create the genesis block. Store in the DB. Save the genesis block’s hash as the last block hash. Create a new Blockchain instance with its tip pointing at the genesis block. In code, it looks like this: func NewBlockchain() *Blockchain { var tip []byte db, err := bolt.Open(dbFile, 0600, nil) err = db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) if b == nil { genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) err = b.Put([]byte("l"), genesis.Hash) tip = genesis.Hash } else { tip = b.Get([]byte("l")) } return nil }) bc := Blockchain{tip, db} return &bc } Let’s review this piece by piece. db, err := bolt.Open(dbFile, 0600, nil) This is a standard way of opening a BoltDB file. Notice that it won’t return an error if there’s no such file. err = db.Update(func(tx *bolt.Tx) error { ... }) In BoltDB, operations with a database are run within a transaction. And there are two types of transactions: read-only and read-write. Here, we open a read-write transaction (db.Update(...)), because we expect to put the genesis block in the DB. b := tx.Bucket([]byte(blocksBucket)) if b == nil { genesis := NewGenesisBlock() b, err := tx.CreateBucket([]byte(blocksBucket)) err = b.Put(genesis.Hash, genesis.Serialize()) err = b.Put([]byte("l"), genesis.Hash) tip = genesis.Hash } else { tip = b.Get([]byte("l")) } This is the core of the function. Here, we obtain the bucket storing our blocks: if it exists, we read the l key from it; if it doesn’t exist, we generate the genesis block, create the bucket, save the block into it, and update the l key storing the last block hash of the chain. Also, notice the new way of creating a Blockchain: bc := Blockchain{tip, db} We don’t store all the blocks in it anymore, instead only the tip of the chain is stored. Also, we store a DB connection, because we want to open it once and keep it open while the program is running. Thus, the Blockchain structure now looks like this: type Blockchain struct { tip []byte db *bolt.DB } Next thing we want to update is the AddBlock method: adding blocks to a chain now is not as easy as adding an element to an array. From now on we’ll store blocks in the DB: func (bc *Blockchain) AddBlock(data string) { var lastHash []byte err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil }) newBlock := NewBlock(data, lastHash) err = bc.db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash return nil }) } Let’s review this piece by piece: err := bc.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) lastHash = b.Get([]byte("l")) return nil }) This is the other (read-only) type of BoltDB transactions. Here we get the last block hash from the DB to use it to mine a new block hash. newBlock := NewBlock(data, lastHash) b := tx.Bucket([]byte(blocksBucket)) err := b.Put(newBlock.Hash, newBlock.Serialize()) err = b.Put([]byte("l"), newBlock.Hash) bc.tip = newBlock.Hash After mining a new block, we save its serialized representation into the DB and update the l key, which now stores the new block’s hash. Done! It wasn’t hard, was it? Inspecting Blockchain All new blocks are now saved in a database, so we can reopen a blockchain and add a new block to it. But after implementing this, we lost a nice feature: we cannot print out blockchain blocks anymore because we don’t store blocks in an array any longer. Let’s fix this flaw! BoltDB allows to iterate over all the keys in a bucket, but the keys are stored in byte-sorted order, and we want blocks to be printed in the order they take in a blockchain. Also, because we don’t want to load all the blocks into memory (our blockchain DB could be huge!.. or let’s just pretend it could), we’ll read them one by one. For this purpose, we’ll need a blockchain iterator: type BlockchainIterator struct { currentHash []byte db *bolt.DB } An iterator will be created each time we want to iterate over blocks in a blockchain and it’ll store the block hash of the current iteration and a connection to a DB. Because of the latter, an iterator is logically attached to a blockchain (it’s a Blockchain instance that stores a DB connection) and, thus, is created in a Blockchain method: func (bc *Blockchain) Iterator() *BlockchainIterator { bci := &BlockchainIterator{bc.tip, bc.db} return bci } Notice that an iterator initially points at the tip of a blockchain, thus blocks will be obtained from top to bottom, from newest to oldest. In fact, choosing a tip means “voting” for a blockchain. A blockchain can have multiple branches, and it’s the longest of them that’s considered main. After getting a tip (it can be any block in the blockchain) we can reconstruct the whole blockchain and find its length and the work required to build it. This fact also means that a tip is a kind of an identifier of a blockchain. BlockchainIterator will do only one thing: it’ll return the next block from a blockchain. func (i *BlockchainIterator) Next() *Block { var block *Block err := i.db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(blocksBucket)) encodedBlock := b.Get(i.currentHash) block = DeserializeBlock(encodedBlock) return nil }) i.currentHash = block.PrevBlockHash return block } That’s it for the DB part! CLI Until now our implementation hasn’t provided any interface to interact with the program: we’ve simply executed NewBlockchain, bc.AddBlock in the main function. Time to improve this! We want to have these commands: blockchain_go addblock "Pay 0.031337 for a coffee" blockchain_go printchain All command-line related operations will be processed by the CLI struct: type CLI struct { bc *Blockchain } Its “entrypoint” is the Run function: func (cli *CLI) Run() { cli.validateArgs() addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data") switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) case "printchain": err := printChainCmd.Parse(os.Args[2:]) default: cli.printUsage() os.Exit(1) } if addBlockCmd.Parsed() { if *addBlockData == "" { addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChainCmd.Parsed() { cli.printChain() } } We’re using the standard flag package to parse command-line arguments. addBlockCmd := flag.NewFlagSet("addblock", flag.ExitOnError) printChainCmd := flag.NewFlagSet("printchain", flag.ExitOnError) addBlockData := addBlockCmd.String("data", "", "Block data") First, we create two subcommands, addblock and printchain, then we add -data flag to the former. printchain won’t have any flags. switch os.Args[1] { case "addblock": err := addBlockCmd.Parse(os.Args[2:]) case "printchain": err := printChainCmd.Parse(os.Args[2:]) default: cli.printUsage() os.Exit(1) } Next we check the command provided by user and parse related flag subcommand. if addBlockCmd.Parsed() { if *addBlockData == "" { addBlockCmd.Usage() os.Exit(1) } cli.addBlock(*addBlockData) } if printChainCmd.Parsed() { cli.printChain() } Next we check which of the subcommands were parsed and run related functions. func (cli *CLI) addBlock(data string) { cli.bc.AddBlock(data) fmt.Println("Success!") } func (cli *CLI) printChain() { bci := cli.bc.Iterator() for { block := bci.Next() fmt.Printf("Prev. hash: %x\n", block.PrevBlockHash) fmt.Printf("Data: %s\n", block.Data) fmt.Printf("Hash: %x\n", block.Hash) pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() if len(block.PrevBlockHash) == 0 { break } } } This piece is very similar to the one we had before. The only difference is that we’re now using a BlockchainIterator to iterate over blocks in a blockchain. Also let’s not forget to modify the main function accordingly: func main() { bc := NewBlockchain() defer bc.db.Close() cli := CLI{bc} cli.Run() } Note that a new Blockchain is created no matter what command-line arguments are provided. And that’s it! Let’s check that everything works as expected: $ blockchain_go printchain No existing blockchain found. Creating a new one... Mining the block containing "Genesis Block" 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b Prev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true $ blockchain_go addblock -data "Send 1 BTC to Ivan" Mining the block containing "Send 1 BTC to Ivan" 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 Success! $ blockchain_go addblock -data "Pay 0.31337 BTC for a coffee" Mining the block containing "Pay 0.31337 BTC for a coffee" 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 Success! $ blockchain_go printchain Prev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 Data: Pay 0.31337 BTC for a coffee Hash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148 PoW: true Prev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b Data: Send 1 BTC to Ivan Hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13 PoW: true Prev. hash: Data: Genesis Block Hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b PoW: true (sound of a beer can opening) Conclusion Next time we’ll implement addresses, wallets, and (probably) transactions. So stay tuned! |
| json metadata | {"tags":["blockchain","database","cli","api"],"app":"steemit/0.1","format":"markdown"} |
| Transaction Info | Block #20785710/Trx fb08a0457442fc620d617e3c4f49ddcc275edeae |
View Raw JSON Data
{
"trx_id": "fb08a0457442fc620d617e3c4f49ddcc275edeae",
"block": 20785710,
"trx_in_block": 29,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:41:03",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "blockchain",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-3-persistence-and-cli",
"title": "Building Blockchain in Go. Part 3: Persistence and CLI",
"body": "Introduction\nSo far, we’ve built a blockchain with a proof-of-work system, which makes mining possible. Our implementation is getting closer to a fully functional blockchain, but it still lacks some important features. Today will start storing a blockchain in a database, and after that we’ll make a simple command-line interface to perform operations with the blockchain. In its essence, blockchain is a distributed database. We’re going to omit the “distributed” part for now and focus on the “database” part.\n\nDatabase Choice\nCurrently, there’s no database in our implementation; instead, we create blocks every time we run the program and store them in memory. We cannot reuse a blockchain, we cannot share it with others, thus we need to store it on the disk.\n\nWhich database do we need? Actually, any of them. In the original Bitcoin paper, nothing is said about using a certain database, so it’s up to a developer what DB to use. Bitcoin Core, which was initially published by Satoshi Nakamoto and which is currently a reference implementation of Bitcoin, uses LevelDB (although it was introduced to the client only in 2012). And we’ll use…\n\nBoltDB\nBecause:\n\nIt’s simple and minimalistic.\nIt’s implemented in Go.\nIt doesn’t require to run a server.\nIt allows to build the data structure we want.\nFrom the BoltDB’s README on Github:\n\nBolt is a pure Go key/value store inspired by Howard Chu’s LMDB project. The goal of the project is to provide a simple, fast, and reliable database for projects that don’t require a full database server such as Postgres or MySQL.\n\nSince Bolt is meant to be used as such a low-level piece of functionality, simplicity is key. The API will be small and only focus on getting values and setting values. That’s it.\n\nSounds perfect for our needs! Let’s spend a minute reviewing it.\n\nBoltDB is a key/value storage, which means there’re no tables like in SQL RDBMS (MySQL, PostgreSQL, etc.), no rows, no columns. Instead, data is stored as key-value pairs (like in Golang maps). Key-value pairs are stored in buckets, which are intended to group similar pairs (this is similar to tables in RDBMS). Thus, in order to get a value, you need to know a bucket and a key.\n\nOne important thing about BoltDB is that there are no data types: keys and values are byte arrays. Since we’ll store Go structs (Block, in particular) in it, we’ll need to serialize them, i.e. implement a mechanism of converting a Go struct into a byte array and restoring it back from a byte array. We’ll use encoding/gob for this, but JSON, XML, Protocol Buffers, etc. can be used as well. We’re using encoding/gob because it’s simple and is a part of the standard Go library.\n\nDatabase Structure\nBefore starting implementing persistence logic, we first need to decide how we’ll store data in the DB. And for this, we’ll refer to the way Bitcoin Core does that.\n\nIn simple words, Bitcoin Core uses two “buckets” to store data:\n\nblocks stores metadata describing all the blocks in a chain.\nchainstate stores the state of a chain, which is all currently unspent transaction outputs and some metadata.\nAlso, blocks are stored as separate files on the disk. This is done for a performance purpose: reading a single block won’t require loading all (or some) of them into memory. We won’t implement this.\n\nIn blocks, the key -> value pairs are:\n\n'b' + 32-byte block hash -> block index record\n'f' + 4-byte file number -> file information record\n'l' -> 4-byte file number: the last block file number used\n'R' -> 1-byte boolean: whether we're in the process of reindexing\n'F' + 1-byte flag name length + flag name string -> 1 byte boolean: various flags that can be on or off\n't' + 32-byte transaction hash -> transaction index record\nIn chainstate, the key -> value pairs are:\n\n'c' + 32-byte transaction hash -> unspent transaction output record for that transaction\n'B' -> 32-byte block hash: the block hash up to which the database represents the unspent transaction outputs\n(Detailed explanation can be found here)\n\nSince we don’t have transactions yet, we’re going to have only blocks bucket. Also, as said above, we will store the whole DB as a single file, without storing blocks in separate files. So we won’t need anything related to file numbers. So these are key -> value pairs we’ll use:\n\n32-byte block-hash -> Block structure (serialized)\n'l' -> the hash of the last block in a chain\nThat’s all we need to know to start implementing the persistence mechanism.\n\nSerialization\nAs said before, in BoltDB values can be only of []byte type, and we want to store Block structs in the DB. We’ll use encoding/gob to serialize the structs.\n\nLet’s implement Serialize method of Block (errors processing is omitted for brevity):\n\nfunc (b *Block) Serialize() []byte {\n\tvar result bytes.Buffer\n\tencoder := gob.NewEncoder(&result)\n\n\terr := encoder.Encode(b)\n\n\treturn result.Bytes()\n}\nThe piece is straightforward: at first, we declare a buffer that will store serialized data; then we initialize a gob encoder and encode the block; the result is returned as a byte array.\n\nNext, we need a deserializing function that will receive a byte array as input and return a Block. This won’t be a method but an independent function:\n\nfunc DeserializeBlock(d []byte) *Block {\n\tvar block Block\n\n\tdecoder := gob.NewDecoder(bytes.NewReader(d))\n\terr := decoder.Decode(&block)\n\n\treturn &block\n}\nAnd that’s it for the serialization!\n\nPersistence\nLet’s start with the NewBlockchain function. Currently, it creates a new instance of Blockchain and adds the genesis block to it. What we want it to do is to:\n\nOpen a DB file.\nCheck if there’s a blockchain stored in it.\nIf there’s a blockchain:\nCreate a new Blockchain instance.\nSet the tip of the Blockchain instance to the last block hash stored in the DB.\nIf there’s no existing blockchain:\nCreate the genesis block.\nStore in the DB.\nSave the genesis block’s hash as the last block hash.\nCreate a new Blockchain instance with its tip pointing at the genesis block.\nIn code, it looks like this:\n\nfunc NewBlockchain() *Blockchain {\n\tvar tip []byte\n\tdb, err := bolt.Open(dbFile, 0600, nil)\n\n\terr = db.Update(func(tx *bolt.Tx) error {\n\t\tb := tx.Bucket([]byte(blocksBucket))\n\n\t\tif b == nil {\n\t\t\tgenesis := NewGenesisBlock()\n\t\t\tb, err := tx.CreateBucket([]byte(blocksBucket))\n\t\t\terr = b.Put(genesis.Hash, genesis.Serialize())\n\t\t\terr = b.Put([]byte(\"l\"), genesis.Hash)\n\t\t\ttip = genesis.Hash\n\t\t} else {\n\t\t\ttip = b.Get([]byte(\"l\"))\n\t\t}\n\n\t\treturn nil\n\t})\n\n\tbc := Blockchain{tip, db}\n\n\treturn &bc\n}\nLet’s review this piece by piece.\n\ndb, err := bolt.Open(dbFile, 0600, nil)\nThis is a standard way of opening a BoltDB file. Notice that it won’t return an error if there’s no such file.\n\nerr = db.Update(func(tx *bolt.Tx) error {\n...\n})\nIn BoltDB, operations with a database are run within a transaction. And there are two types of transactions: read-only and read-write. Here, we open a read-write transaction (db.Update(...)), because we expect to put the genesis block in the DB.\n\nb := tx.Bucket([]byte(blocksBucket))\n\nif b == nil {\n\tgenesis := NewGenesisBlock()\n\tb, err := tx.CreateBucket([]byte(blocksBucket))\n\terr = b.Put(genesis.Hash, genesis.Serialize())\n\terr = b.Put([]byte(\"l\"), genesis.Hash)\n\ttip = genesis.Hash\n} else {\n\ttip = b.Get([]byte(\"l\"))\n}\nThis is the core of the function. Here, we obtain the bucket storing our blocks: if it exists, we read the l key from it; if it doesn’t exist, we generate the genesis block, create the bucket, save the block into it, and update the l key storing the last block hash of the chain.\n\nAlso, notice the new way of creating a Blockchain:\n\nbc := Blockchain{tip, db}\nWe don’t store all the blocks in it anymore, instead only the tip of the chain is stored. Also, we store a DB connection, because we want to open it once and keep it open while the program is running. Thus, the Blockchain structure now looks like this:\n\ntype Blockchain struct {\n\ttip []byte\n\tdb *bolt.DB\n}\nNext thing we want to update is the AddBlock method: adding blocks to a chain now is not as easy as adding an element to an array. From now on we’ll store blocks in the DB:\n\nfunc (bc *Blockchain) AddBlock(data string) {\n\tvar lastHash []byte\n\n\terr := bc.db.View(func(tx *bolt.Tx) error {\n\t\tb := tx.Bucket([]byte(blocksBucket))\n\t\tlastHash = b.Get([]byte(\"l\"))\n\n\t\treturn nil\n\t})\n\n\tnewBlock := NewBlock(data, lastHash)\n\n\terr = bc.db.Update(func(tx *bolt.Tx) error {\n\t\tb := tx.Bucket([]byte(blocksBucket))\n\t\terr := b.Put(newBlock.Hash, newBlock.Serialize())\n\t\terr = b.Put([]byte(\"l\"), newBlock.Hash)\n\t\tbc.tip = newBlock.Hash\n\n\t\treturn nil\n\t})\n}\nLet’s review this piece by piece:\n\nerr := bc.db.View(func(tx *bolt.Tx) error {\n\tb := tx.Bucket([]byte(blocksBucket))\n\tlastHash = b.Get([]byte(\"l\"))\n\n\treturn nil\n})\nThis is the other (read-only) type of BoltDB transactions. Here we get the last block hash from the DB to use it to mine a new block hash.\n\nnewBlock := NewBlock(data, lastHash)\nb := tx.Bucket([]byte(blocksBucket))\nerr := b.Put(newBlock.Hash, newBlock.Serialize())\nerr = b.Put([]byte(\"l\"), newBlock.Hash)\nbc.tip = newBlock.Hash\nAfter mining a new block, we save its serialized representation into the DB and update the l key, which now stores the new block’s hash.\n\nDone! It wasn’t hard, was it?\n\nInspecting Blockchain\nAll new blocks are now saved in a database, so we can reopen a blockchain and add a new block to it. But after implementing this, we lost a nice feature: we cannot print out blockchain blocks anymore because we don’t store blocks in an array any longer. Let’s fix this flaw!\n\nBoltDB allows to iterate over all the keys in a bucket, but the keys are stored in byte-sorted order, and we want blocks to be printed in the order they take in a blockchain. Also, because we don’t want to load all the blocks into memory (our blockchain DB could be huge!.. or let’s just pretend it could), we’ll read them one by one. For this purpose, we’ll need a blockchain iterator:\n\ntype BlockchainIterator struct {\n\tcurrentHash []byte\n\tdb *bolt.DB\n}\nAn iterator will be created each time we want to iterate over blocks in a blockchain and it’ll store the block hash of the current iteration and a connection to a DB. Because of the latter, an iterator is logically attached to a blockchain (it’s a Blockchain instance that stores a DB connection) and, thus, is created in a Blockchain method:\n\nfunc (bc *Blockchain) Iterator() *BlockchainIterator {\n\tbci := &BlockchainIterator{bc.tip, bc.db}\n\n\treturn bci\n}\nNotice that an iterator initially points at the tip of a blockchain, thus blocks will be obtained from top to bottom, from newest to oldest. In fact, choosing a tip means “voting” for a blockchain. A blockchain can have multiple branches, and it’s the longest of them that’s considered main. After getting a tip (it can be any block in the blockchain) we can reconstruct the whole blockchain and find its length and the work required to build it. This fact also means that a tip is a kind of an identifier of a blockchain.\n\nBlockchainIterator will do only one thing: it’ll return the next block from a blockchain.\n\nfunc (i *BlockchainIterator) Next() *Block {\n\tvar block *Block\n\n\terr := i.db.View(func(tx *bolt.Tx) error {\n\t\tb := tx.Bucket([]byte(blocksBucket))\n\t\tencodedBlock := b.Get(i.currentHash)\n\t\tblock = DeserializeBlock(encodedBlock)\n\n\t\treturn nil\n\t})\n\n\ti.currentHash = block.PrevBlockHash\n\n\treturn block\n}\nThat’s it for the DB part!\n\nCLI\nUntil now our implementation hasn’t provided any interface to interact with the program: we’ve simply executed NewBlockchain, bc.AddBlock in the main function. Time to improve this! We want to have these commands:\n\nblockchain_go addblock \"Pay 0.031337 for a coffee\"\nblockchain_go printchain\nAll command-line related operations will be processed by the CLI struct:\n\ntype CLI struct {\n\tbc *Blockchain\n}\nIts “entrypoint” is the Run function:\n\nfunc (cli *CLI) Run() {\n\tcli.validateArgs()\n\n\taddBlockCmd := flag.NewFlagSet(\"addblock\", flag.ExitOnError)\n\tprintChainCmd := flag.NewFlagSet(\"printchain\", flag.ExitOnError)\n\n\taddBlockData := addBlockCmd.String(\"data\", \"\", \"Block data\")\n\n\tswitch os.Args[1] {\n\tcase \"addblock\":\n\t\terr := addBlockCmd.Parse(os.Args[2:])\n\tcase \"printchain\":\n\t\terr := printChainCmd.Parse(os.Args[2:])\n\tdefault:\n\t\tcli.printUsage()\n\t\tos.Exit(1)\n\t}\n\n\tif addBlockCmd.Parsed() {\n\t\tif *addBlockData == \"\" {\n\t\t\taddBlockCmd.Usage()\n\t\t\tos.Exit(1)\n\t\t}\n\t\tcli.addBlock(*addBlockData)\n\t}\n\n\tif printChainCmd.Parsed() {\n\t\tcli.printChain()\n\t}\n}\nWe’re using the standard flag package to parse command-line arguments.\n\naddBlockCmd := flag.NewFlagSet(\"addblock\", flag.ExitOnError)\nprintChainCmd := flag.NewFlagSet(\"printchain\", flag.ExitOnError)\naddBlockData := addBlockCmd.String(\"data\", \"\", \"Block data\")\nFirst, we create two subcommands, addblock and printchain, then we add -data flag to the former. printchain won’t have any flags.\n\nswitch os.Args[1] {\ncase \"addblock\":\n\terr := addBlockCmd.Parse(os.Args[2:])\ncase \"printchain\":\n\terr := printChainCmd.Parse(os.Args[2:])\ndefault:\n\tcli.printUsage()\n\tos.Exit(1)\n}\nNext we check the command provided by user and parse related flag subcommand.\n\nif addBlockCmd.Parsed() {\n\tif *addBlockData == \"\" {\n\t\taddBlockCmd.Usage()\n\t\tos.Exit(1)\n\t}\n\tcli.addBlock(*addBlockData)\n}\n\nif printChainCmd.Parsed() {\n\tcli.printChain()\n}\nNext we check which of the subcommands were parsed and run related functions.\n\nfunc (cli *CLI) addBlock(data string) {\n\tcli.bc.AddBlock(data)\n\tfmt.Println(\"Success!\")\n}\n\nfunc (cli *CLI) printChain() {\n\tbci := cli.bc.Iterator()\n\n\tfor {\n\t\tblock := bci.Next()\n\n\t\tfmt.Printf(\"Prev. hash: %x\\n\", block.PrevBlockHash)\n\t\tfmt.Printf(\"Data: %s\\n\", block.Data)\n\t\tfmt.Printf(\"Hash: %x\\n\", block.Hash)\n\t\tpow := NewProofOfWork(block)\n\t\tfmt.Printf(\"PoW: %s\\n\", strconv.FormatBool(pow.Validate()))\n\t\tfmt.Println()\n\n\t\tif len(block.PrevBlockHash) == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n}\nThis piece is very similar to the one we had before. The only difference is that we’re now using a BlockchainIterator to iterate over blocks in a blockchain.\n\nAlso let’s not forget to modify the main function accordingly:\n\nfunc main() {\n\tbc := NewBlockchain()\n\tdefer bc.db.Close()\n\n\tcli := CLI{bc}\n\tcli.Run()\n}\nNote that a new Blockchain is created no matter what command-line arguments are provided.\n\nAnd that’s it! Let’s check that everything works as expected:\n\n$ blockchain_go printchain\nNo existing blockchain found. Creating a new one...\nMining the block containing \"Genesis Block\"\n000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b\n\nPrev. hash:\nData: Genesis Block\nHash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b\nPoW: true\n\n$ blockchain_go addblock -data \"Send 1 BTC to Ivan\"\nMining the block containing \"Send 1 BTC to Ivan\"\n000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13\n\nSuccess!\n\n$ blockchain_go addblock -data \"Pay 0.31337 BTC for a coffee\"\nMining the block containing \"Pay 0.31337 BTC for a coffee\"\n000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148\n\nSuccess!\n\n$ blockchain_go printchain\nPrev. hash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13\nData: Pay 0.31337 BTC for a coffee\nHash: 000000aa0748da7367dec6b9de5027f4fae0963df89ff39d8f20fd7299307148\nPoW: true\n\nPrev. hash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b\nData: Send 1 BTC to Ivan\nHash: 000000d7b0c76e1001cdc1fc866b95a481d23f3027d86901eaeb77ae6d002b13\nPoW: true\n\nPrev. hash:\nData: Genesis Block\nHash: 000000edc4a82659cebf087adee1ea353bd57fcd59927662cd5ff1c4f618109b\nPoW: true\n(sound of a beer can opening)\n\nConclusion\nNext time we’ll implement addresses, wallets, and (probably) transactions. So stay tuned!",
"json_metadata": "{\"tags\":[\"blockchain\",\"database\",\"cli\",\"api\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}"
}
]
}2018/03/18 14:19:39
2018/03/18 14:19:39
| parent author | yeshulin |
| parent permlink | building-blockchain-in-go-part-2-proof-of-work |
| author | cheetah |
| permlink | cheetah-re-yeshulinbuilding-blockchain-in-go-part-2-proof-of-work |
| title | |
| body | Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in: https://jeiwan.cc/posts/building-blockchain-in-go-part-2/ |
| json metadata | |
| Transaction Info | Block #20785282/Trx aeba2e1733be12cbbae7e25045f53dec63c7fa9b |
View Raw JSON Data
{
"trx_id": "aeba2e1733be12cbbae7e25045f53dec63c7fa9b",
"block": 20785282,
"trx_in_block": 45,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:19:39",
"op": [
"comment",
{
"parent_author": "yeshulin",
"parent_permlink": "building-blockchain-in-go-part-2-proof-of-work",
"author": "cheetah",
"permlink": "cheetah-re-yeshulinbuilding-blockchain-in-go-part-2-proof-of-work",
"title": "",
"body": "Hi! I am a robot. I just upvoted you! I found similar content that readers might be interested in:\nhttps://jeiwan.cc/posts/building-blockchain-in-go-part-2/",
"json_metadata": ""
}
]
}cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work2018/03/18 14:19:36
cheetahupvoted (0.08%) @yeshulin / building-blockchain-in-go-part-2-proof-of-work
2018/03/18 14:19:36
| voter | cheetah |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-2-proof-of-work |
| weight | 8 (0.08%) |
| Transaction Info | Block #20785281/Trx 2efa28e8eb87a83e6ae532402255241f6693d3ef |
View Raw JSON Data
{
"trx_id": "2efa28e8eb87a83e6ae532402255241f6693d3ef",
"block": 20785281,
"trx_in_block": 22,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:19:36",
"op": [
"vote",
{
"voter": "cheetah",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-2-proof-of-work",
"weight": 8
}
]
}yeshulinpublished a new post: building-blockchain-in-go-part-2-proof-of-work2018/03/18 14:19:21
yeshulinpublished a new post: building-blockchain-in-go-part-2-proof-of-work
2018/03/18 14:19:21
| parent author | |
| parent permlink | blockchain |
| author | yeshulin |
| permlink | building-blockchain-in-go-part-2-proof-of-work |
| title | Building Blockchain in Go. Part 2: Proof-of-Work |
| body | Introduction In the previous article we built a very simple data structure, which is the essence of blockchain database. And we made it possible to add blocks to it with the chain-like relation between them: each block is linked to the previous one. Alas, our blockchain implementation has one significant flaw: adding blocks to the chain is easy and cheap. One of the keystones of blockchain and Bitcoin is that adding new blocks is a hard work. Today we’re going to fix this flaw. Proof-of-Work A key idea of blockchain is that one has to perform some hard work to put data in it. It is this hard work that makes blockchain secure and consistent. Also, a reward is paid for this hard work (this is how people get coins for mining). This mechanism is very similar to the one from real life: one has to work hard to get a reward and to sustain their life. In blockchain, some participants (miners) of the network work to sustain the network, to add new blocks to it, and get a reward for their work. As a result of their work, a block is incorporated into the blockchain in a secure way, which maintains the stability of the whole blockchain database. It’s worth noting that, the one who finished the work has to prove this. This whole “do hard work and prove” mechanism is called proof-of-work. It’s hard because it requires a lot of computational power: even high performance computers cannot do it quickly. Moreover, the difficulty of this work increases from time to time to keep new blocks rate at about 6 blocks per hour. In Bitcoin, the goal of such work is to find a hash for a block, that meets some requirements. And it’s this hash that serves as a proof. Thus, finding a proof is the actual work. One last thing to note. Proof-of-Work algorithms must meet a requirement: doing the work is hard, but verifying the proof is easy. A proof is usually handed to someone else, so for them, it shouldn’t take much time to verify it. Hashing In this paragraph, we’ll discuss hashing. If you’re familiar with the concept, you can skip this part. Hashing is a process of obtaining a hash for specified data. A hash is a unique representation of the data it was calculated on. A hash function is a function that takes data of arbitrary size and produces a fixed size hash. Here are some key features of hashing: Original data cannot be restored from a hash. Thus, hashing is not encryption. Certain data can have only one hash and the hash is unique. Changing even one byte in the input data will result in a completely different hash. Hashing example Hashing functions are widely used to check the consistency of data. Some software providers publish checksums in addition to a software package. After downloading a file you can feed it to a hashing function and compare produced hash with the one provided by the software developer. In blockchain, hashing is used to guarantee the consistency of a block. The input data for a hashing algorithm contains the hash of the previous block, thus making it impossible (or, at least, quite difficult) to modify a block in the chain: one has to recalculate its hash and hashes of all the blocks after it. Hashcash Bitcoin uses Hashcash, a Proof-of-Work algorithm that was initially developed to prevent email spam. It can be split into the following steps: Take some publicly known data (in case of email, it’s receiver’s email address; in case of Bitcoin, it’s block headers). Add a counter to it. The counter starts at 0. Get a hash of the data + counter combination. Check that the hash meets certain requirements. If it does, you’re done. If it doesn’t, increase the counter and repeat the steps 3 and 4. Thus, this is a brute force algorithm: you change the counter, calculate a new hash, check it, increment the counter, calculate a hash, etc. That’s why it’s computationally expensive. Now let’s look closer at the requirements a hash has to meet. In the original Hashcash implementation, the requirement sounds like “first 20 bits of a hash must be zeros”. In Bitcoin, the requirement is adjusted from time to time, because, by design, a block must be generated every 10 minutes, despite computation power increasing with time and more and more miners joining the network. To demonstrate this algorithm, I took the data from the previous example (“I like donuts”) and found a hash that starts with 3 zero-bytes: Hashcash example ca07ca is the hexadecimal value of the counter, which is 13240266 in the decimal system. Implementation Ok, we’re done with the theory, let’s write code! First, let’s define the difficulty of mining: const targetBits = 24 In Bitcoin, “target bits” is the block header storing the difficulty at which the block was mined. We won’t implement a target adjusting algorithm, for now, so we can just define the difficulty as a global constant. 24 is an arbitrary number, our goal is to have a target that takes less than 256 bits in memory. And we want the difference to be significant enough, but not too big, because the bigger the difference the more difficult it’s to find a proper hash. type ProofOfWork struct { block *Block target *big.Int } func NewProofOfWork(b *Block) *ProofOfWork { target := big.NewInt(1) target.Lsh(target, uint(256-targetBits)) pow := &ProofOfWork{b, target} return pow } Here create ProofOfWork structure that holds a pointer to a block and a pointer to a target. “target” is another name for the requirement described in the previous paragraph. We use a big integer because of the way we’ll compare a hash to the target: we’ll convert a hash to a big integer and check if it’s less than the target. In the NewProofOfWork function, we initialize a big.Int with the value of 1 and shift it left by 256 - targetBits bits. 256 is the length of a SHA-256 hash in bits, and it’s SHA-256 hashing algorithm that we’re going to use. The hexadecimal representation of target is: 0x10000000000000000000000000000000000000000000000000000000000 And it occupies 29 bytes in memory. And here’s its visual comparison with the hashes from the previous examples: 0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3 0000010000000000000000000000000000000000000000000000000000000000 0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca The first hash (calculated on “I like donuts”) is bigger than the target, thus it’s not a valid proof of work. The second hash (calculated on “I like donutsca07ca”) is smaller than the target, thus it’s a valid proof. You can think of a target as the upper boundary of a range: if a number (a hash) is lower than the boundary, it’s valid, and vice versa. Lowering the boundary will result in fewer valid numbers, and thus, more difficult work required to find a valid one. Now, we need the data to hash. Let’s prepare it: func (pow *ProofOfWork) prepareData(nonce int) []byte { data := bytes.Join( [][]byte{ pow.block.PrevBlockHash, pow.block.Data, IntToHex(pow.block.Timestamp), IntToHex(int64(targetBits)), IntToHex(int64(nonce)), }, []byte{}, ) return data } This piece is straightforward: we just merge block fields with the target and nonce. nonce here is the counter from the Hashcash description above, this is a cryptographic term. Ok, all preparations are done, let’s implement the core of the PoW algorithm: func (pow *ProofOfWork) Run() (int, []byte) { var hashInt big.Int var hash [32]byte nonce := 0 fmt.Printf("Mining the block containing \"%s\"\n", pow.block.Data) for nonce < maxNonce { data := pow.prepareData(nonce) hash = sha256.Sum256(data) fmt.Printf("\r%x", hash) hashInt.SetBytes(hash[:]) if hashInt.Cmp(pow.target) == -1 { break } else { nonce++ } } fmt.Print("\n\n") return nonce, hash[:] } First, we initialize variables: hashInt is the integer representation of hash; nonce is the counter. Next, we run an “infinite” loop: it’s limited by maxNonce, which equals to math.MaxInt64; this is done to avoid a possible overflow of nonce. Although the difficulty of our PoW implementation is too low for the counter to overflow, it’s still better to have this check, just in case. In the loop we: Prepare data. Hash it with SHA-256. Convert the hash to a big integer. Compare the integer with the target. As easy as it was explained earlier. Now we can remove the SetHash method of Block and modify the NewBlock function: func NewBlock(data string, prevBlockHash []byte) *Block { block := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0} pow := NewProofOfWork(block) nonce, hash := pow.Run() block.Hash = hash[:] block.Nonce = nonce return block } Here you can see that nonce is saved as a Block property. This is necessary because nonce is required to verify a proof. The Block structure now looks so: type Block struct { Timestamp int64 Data []byte PrevBlockHash []byte Hash []byte Nonce int } Alright! Let’s run the program to see if everything works fine: Mining the block containing "Genesis Block" 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Mining the block containing "Send 1 BTC to Ivan" 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Mining the block containing "Send 2 more BTC to Ivan" 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe Prev. hash: Data: Genesis Block Hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Prev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1 Data: Send 1 BTC to Ivan Hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Prev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804 Data: Send 2 more BTC to Ivan Hash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe Yay! You can see that every hash now starts with three zero bytes, and it takes some time to get these hashes. There’s one more thing left to do: let’s make it possible to validate proof of works. func (pow *ProofOfWork) Validate() bool { var hashInt big.Int data := pow.prepareData(pow.block.Nonce) hash := sha256.Sum256(data) hashInt.SetBytes(hash[:]) isValid := hashInt.Cmp(pow.target) == -1 return isValid } And this is where we need the saved nonce. Let’s check one more time that everything’s ok: func main() { ... for _, block := range bc.blocks { ... pow := NewProofOfWork(block) fmt.Printf("PoW: %s\n", strconv.FormatBool(pow.Validate())) fmt.Println() } } Output: ... Prev. hash: Data: Genesis Block Hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 PoW: true Prev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038 Data: Send 1 BTC to Ivan Hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b PoW: true Prev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b Data: Send 2 more BTC to Ivan Hash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a PoW: true Conclusion Our blockchain is a step closer to its actual architecture: adding blocks now requires hard work, thus mining is possible. But it still lacks some crucial features: the blockchain database is not persistent, there are no wallets, addresses, transactions, and there’s no consensus mechanism. All these things we’ll implement in future articles, and for now, happy mining! |
| json metadata | {"tags":["blockchain","proof","of","work"],"app":"steemit/0.1","format":"markdown"} |
| Transaction Info | Block #20785276/Trx 7e3ba7963553d31cfac169c51f12c97a94a7f5b6 |
View Raw JSON Data
{
"trx_id": "7e3ba7963553d31cfac169c51f12c97a94a7f5b6",
"block": 20785276,
"trx_in_block": 0,
"op_in_trx": 0,
"virtual_op": 0,
"timestamp": "2018-03-18T14:19:21",
"op": [
"comment",
{
"parent_author": "",
"parent_permlink": "blockchain",
"author": "yeshulin",
"permlink": "building-blockchain-in-go-part-2-proof-of-work",
"title": "Building Blockchain in Go. Part 2: Proof-of-Work",
"body": "Introduction\nIn the previous article we built a very simple data structure, which is the essence of blockchain database. And we made it possible to add blocks to it with the chain-like relation between them: each block is linked to the previous one. Alas, our blockchain implementation has one significant flaw: adding blocks to the chain is easy and cheap. One of the keystones of blockchain and Bitcoin is that adding new blocks is a hard work. Today we’re going to fix this flaw.\n\nProof-of-Work\nA key idea of blockchain is that one has to perform some hard work to put data in it. It is this hard work that makes blockchain secure and consistent. Also, a reward is paid for this hard work (this is how people get coins for mining).\n\nThis mechanism is very similar to the one from real life: one has to work hard to get a reward and to sustain their life. In blockchain, some participants (miners) of the network work to sustain the network, to add new blocks to it, and get a reward for their work. As a result of their work, a block is incorporated into the blockchain in a secure way, which maintains the stability of the whole blockchain database. It’s worth noting that, the one who finished the work has to prove this.\n\nThis whole “do hard work and prove” mechanism is called proof-of-work. It’s hard because it requires a lot of computational power: even high performance computers cannot do it quickly. Moreover, the difficulty of this work increases from time to time to keep new blocks rate at about 6 blocks per hour. In Bitcoin, the goal of such work is to find a hash for a block, that meets some requirements. And it’s this hash that serves as a proof. Thus, finding a proof is the actual work.\n\nOne last thing to note. Proof-of-Work algorithms must meet a requirement: doing the work is hard, but verifying the proof is easy. A proof is usually handed to someone else, so for them, it shouldn’t take much time to verify it.\n\nHashing\nIn this paragraph, we’ll discuss hashing. If you’re familiar with the concept, you can skip this part.\n\nHashing is a process of obtaining a hash for specified data. A hash is a unique representation of the data it was calculated on. A hash function is a function that takes data of arbitrary size and produces a fixed size hash. Here are some key features of hashing:\n\nOriginal data cannot be restored from a hash. Thus, hashing is not encryption.\nCertain data can have only one hash and the hash is unique.\nChanging even one byte in the input data will result in a completely different hash.\nHashing example\n\nHashing functions are widely used to check the consistency of data. Some software providers publish checksums in addition to a software package. After downloading a file you can feed it to a hashing function and compare produced hash with the one provided by the software developer.\n\nIn blockchain, hashing is used to guarantee the consistency of a block. The input data for a hashing algorithm contains the hash of the previous block, thus making it impossible (or, at least, quite difficult) to modify a block in the chain: one has to recalculate its hash and hashes of all the blocks after it.\n\nHashcash\nBitcoin uses Hashcash, a Proof-of-Work algorithm that was initially developed to prevent email spam. It can be split into the following steps:\n\nTake some publicly known data (in case of email, it’s receiver’s email address; in case of Bitcoin, it’s block headers).\nAdd a counter to it. The counter starts at 0.\nGet a hash of the data + counter combination.\nCheck that the hash meets certain requirements.\nIf it does, you’re done.\nIf it doesn’t, increase the counter and repeat the steps 3 and 4.\nThus, this is a brute force algorithm: you change the counter, calculate a new hash, check it, increment the counter, calculate a hash, etc. That’s why it’s computationally expensive.\n\nNow let’s look closer at the requirements a hash has to meet. In the original Hashcash implementation, the requirement sounds like “first 20 bits of a hash must be zeros”. In Bitcoin, the requirement is adjusted from time to time, because, by design, a block must be generated every 10 minutes, despite computation power increasing with time and more and more miners joining the network.\n\nTo demonstrate this algorithm, I took the data from the previous example (“I like donuts”) and found a hash that starts with 3 zero-bytes:\n\nHashcash example\n\nca07ca is the hexadecimal value of the counter, which is 13240266 in the decimal system.\n\nImplementation\nOk, we’re done with the theory, let’s write code! First, let’s define the difficulty of mining:\n\nconst targetBits = 24\nIn Bitcoin, “target bits” is the block header storing the difficulty at which the block was mined. We won’t implement a target adjusting algorithm, for now, so we can just define the difficulty as a global constant.\n\n24 is an arbitrary number, our goal is to have a target that takes less than 256 bits in memory. And we want the difference to be significant enough, but not too big, because the bigger the difference the more difficult it’s to find a proper hash.\n\ntype ProofOfWork struct {\n\tblock *Block\n\ttarget *big.Int\n}\n\nfunc NewProofOfWork(b *Block) *ProofOfWork {\n\ttarget := big.NewInt(1)\n\ttarget.Lsh(target, uint(256-targetBits))\n\n\tpow := &ProofOfWork{b, target}\n\n\treturn pow\n}\nHere create ProofOfWork structure that holds a pointer to a block and a pointer to a target. “target” is another name for the requirement described in the previous paragraph. We use a big integer because of the way we’ll compare a hash to the target: we’ll convert a hash to a big integer and check if it’s less than the target.\n\nIn the NewProofOfWork function, we initialize a big.Int with the value of 1 and shift it left by 256 - targetBits bits. 256 is the length of a SHA-256 hash in bits, and it’s SHA-256 hashing algorithm that we’re going to use. The hexadecimal representation of target is:\n\n0x10000000000000000000000000000000000000000000000000000000000\nAnd it occupies 29 bytes in memory. And here’s its visual comparison with the hashes from the previous examples:\n\n0fac49161af82ed938add1d8725835cc123a1a87b1b196488360e58d4bfb51e3\n0000010000000000000000000000000000000000000000000000000000000000\n0000008b0f41ec78bab747864db66bcb9fb89920ee75f43fdaaeb5544f7f76ca\nThe first hash (calculated on “I like donuts”) is bigger than the target, thus it’s not a valid proof of work. The second hash (calculated on “I like donutsca07ca”) is smaller than the target, thus it’s a valid proof.\n\nYou can think of a target as the upper boundary of a range: if a number (a hash) is lower than the boundary, it’s valid, and vice versa. Lowering the boundary will result in fewer valid numbers, and thus, more difficult work required to find a valid one.\n\nNow, we need the data to hash. Let’s prepare it:\n\nfunc (pow *ProofOfWork) prepareData(nonce int) []byte {\n\tdata := bytes.Join(\n\t\t[][]byte{\n\t\t\tpow.block.PrevBlockHash,\n\t\t\tpow.block.Data,\n\t\t\tIntToHex(pow.block.Timestamp),\n\t\t\tIntToHex(int64(targetBits)),\n\t\t\tIntToHex(int64(nonce)),\n\t\t},\n\t\t[]byte{},\n\t)\n\n\treturn data\n}\nThis piece is straightforward: we just merge block fields with the target and nonce. nonce here is the counter from the Hashcash description above, this is a cryptographic term.\n\nOk, all preparations are done, let’s implement the core of the PoW algorithm:\n\nfunc (pow *ProofOfWork) Run() (int, []byte) {\n\tvar hashInt big.Int\n\tvar hash [32]byte\n\tnonce := 0\n\n\tfmt.Printf(\"Mining the block containing \\\"%s\\\"\\n\", pow.block.Data)\n\tfor nonce < maxNonce {\n\t\tdata := pow.prepareData(nonce)\n\t\thash = sha256.Sum256(data)\n\t\tfmt.Printf(\"\\r%x\", hash)\n\t\thashInt.SetBytes(hash[:])\n\n\t\tif hashInt.Cmp(pow.target) == -1 {\n\t\t\tbreak\n\t\t} else {\n\t\t\tnonce++\n\t\t}\n\t}\n\tfmt.Print(\"\\n\\n\")\n\n\treturn nonce, hash[:]\n}\nFirst, we initialize variables: hashInt is the integer representation of hash; nonce is the counter. Next, we run an “infinite” loop: it’s limited by maxNonce, which equals to math.MaxInt64; this is done to avoid a possible overflow of nonce. Although the difficulty of our PoW implementation is too low for the counter to overflow, it’s still better to have this check, just in case.\n\nIn the loop we:\n\nPrepare data.\nHash it with SHA-256.\nConvert the hash to a big integer.\nCompare the integer with the target.\nAs easy as it was explained earlier. Now we can remove the SetHash method of Block and modify the NewBlock function:\n\nfunc NewBlock(data string, prevBlockHash []byte) *Block {\n\tblock := &Block{time.Now().Unix(), []byte(data), prevBlockHash, []byte{}, 0}\n\tpow := NewProofOfWork(block)\n\tnonce, hash := pow.Run()\n\n\tblock.Hash = hash[:]\n\tblock.Nonce = nonce\n\n\treturn block\n}\nHere you can see that nonce is saved as a Block property. This is necessary because nonce is required to verify a proof. The Block structure now looks so:\n\ntype Block struct {\n\tTimestamp int64\n\tData []byte\n\tPrevBlockHash []byte\n\tHash []byte\n\tNonce int\n}\nAlright! Let’s run the program to see if everything works fine:\n\nMining the block containing \"Genesis Block\"\n00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1\n\nMining the block containing \"Send 1 BTC to Ivan\"\n00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804\n\nMining the block containing \"Send 2 more BTC to Ivan\"\n000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe\n\nPrev. hash:\nData: Genesis Block\nHash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1\n\nPrev. hash: 00000041662c5fc2883535dc19ba8a33ac993b535da9899e593ff98e1eda56a1\nData: Send 1 BTC to Ivan\nHash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804\n\nPrev. hash: 00000077a856e697c69833d9effb6bdad54c730a98d674f73c0b30020cc82804\nData: Send 2 more BTC to Ivan\nHash: 000000b33185e927c9a989cc7d5aaaed739c56dad9fd9361dea558b9bfaf5fbe\nYay! You can see that every hash now starts with three zero bytes, and it takes some time to get these hashes.\n\nThere’s one more thing left to do: let’s make it possible to validate proof of works.\n\nfunc (pow *ProofOfWork) Validate() bool {\n\tvar hashInt big.Int\n\n\tdata := pow.prepareData(pow.block.Nonce)\n\thash := sha256.Sum256(data)\n\thashInt.SetBytes(hash[:])\n\n\tisValid := hashInt.Cmp(pow.target) == -1\n\n\treturn isValid\n}\nAnd this is where we need the saved nonce.\n\nLet’s check one more time that everything’s ok:\n\nfunc main() {\n\t...\n\n\tfor _, block := range bc.blocks {\n\t\t...\n\t\tpow := NewProofOfWork(block)\n\t\tfmt.Printf(\"PoW: %s\\n\", strconv.FormatBool(pow.Validate()))\n\t\tfmt.Println()\n\t}\n}\nOutput:\n\n...\n\nPrev. hash:\nData: Genesis Block\nHash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038\nPoW: true\n\nPrev. hash: 00000093253acb814afb942e652a84a8f245069a67b5eaa709df8ac612075038\nData: Send 1 BTC to Ivan\nHash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b\nPoW: true\n\nPrev. hash: 0000003eeb3743ee42020e4a15262fd110a72823d804ce8e49643b5fd9d1062b\nData: Send 2 more BTC to Ivan\nHash: 000000e42afddf57a3daa11b43b2e0923f23e894f96d1f24bfd9b8d2d494c57a\nPoW: true\nConclusion\nOur blockchain is a step closer to its actual architecture: adding blocks now requires hard work, thus mining is possible. But it still lacks some crucial features: the blockchain database is not persistent, there are no wallets, addresses, transactions, and there’s no consensus mechanism. All these things we’ll implement in future articles, and for now, happy mining!",
"json_metadata": "{\"tags\":[\"blockchain\",\"proof\",\"of\",\"work\"],\"app\":\"steemit/0.1\",\"format\":\"markdown\"}"
}
]
}Manabar
Voting Power100.00%
Downvote Power100.00%
Resource Credits100.00%
Reputation Progress77.41%
{
"voting_manabar": {
"current_mana": "8143659806",
"last_update_time": 1779092841
},
"downvote_manabar": {
"current_mana": 2035914951,
"last_update_time": 1779092841
},
"rc_account": {
"account": "yeshulin",
"rc_manabar": {
"current_mana": "10164408779",
"last_update_time": 1779092841
},
"max_rc_creation_adjustment": {
"amount": "2020748973",
"precision": 6,
"nai": "@@000000037"
},
"max_rc": "10164408779"
}
}Account Metadata
| POSTING JSON METADATA | |
| None | |
| JSON METADATA | |
| None |
{
"posting_json_metadata": {},
"json_metadata": {}
}Auth Keys
Owner
Single Signature
Public Keys
STM7rSAVPXTrmQbzfugk6gWDK193Da2gUXAqiVi1nFgG4dVRexj221/1
Active
Single Signature
Public Keys
STM76teMDP8hsztZUYTt5VdgSibWss8Zkz9oZmmxo8P8MwVC9zFwb1/1
Posting
Single Signature
Public Keys
STM7CqxpAcgFgjvGZrejU98rNX9uLHuibQvXLsK15Wp8xhwt3QKrj1/1
Memo
STM67Tnv2sf3KryMnUHHxQX4KWgMYp4vihyQvGB9vJwZpDEsoHAFq
{
"owner": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM7rSAVPXTrmQbzfugk6gWDK193Da2gUXAqiVi1nFgG4dVRexj22",
1
]
]
},
"active": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM76teMDP8hsztZUYTt5VdgSibWss8Zkz9oZmmxo8P8MwVC9zFwb",
1
]
]
},
"posting": {
"weight_threshold": 1,
"account_auths": [],
"key_auths": [
[
"STM7CqxpAcgFgjvGZrejU98rNX9uLHuibQvXLsK15Wp8xhwt3QKrj",
1
]
]
},
"memo": "STM67Tnv2sf3KryMnUHHxQX4KWgMYp4vihyQvGB9vJwZpDEsoHAFq"
}Witness Votes
0 / 30
No active witness votes.
[]