Ecoer Logo
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 Deleg
+4.764SP

Detailed Balance

STEEM
balance
0.002STEEM
market_balance
0.000STEEM
savings_balance
0.000STEEM
reward_steem_balance
0.000STEEM
STEEM POWER
Own SP
0.237SP
Delegated Out
0.000SP
Delegation In
4.764SP
Effective Power
5.001SP
Reward SP (pending)
0.000SP
SBD
sbd_balance
0.171SBD
sbd_conversions
0.000SBD
sbd_market_balance
0.000SBD
savings_sbd_balance
0.000SBD
reward_sbd_balance
0.000SBD
{
  "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

nameyeshulin
id863270
rank440,588
reputation2033465974
created2018-03-16T01:45:42
recovery_accountsteem
proxyNone
post_count23
comment_count0
lifetime_vote_count0
witnesses_voted_for0
last_post2018-03-24T05:11:33
last_root_post2018-03-21T03:51:39
last_vote_time2018-03-24T05:09:24
proxied_vsf_votes0, 0, 0, 0
can_vote1
voting_power0
delayed_votes0
balance0.002 STEEM
savings_balance0.000 STEEM
sbd_balance0.171 SBD
savings_sbd_balance0.000 SBD
vesting_shares385.738774 VESTS
delegated_vesting_shares0.000000 VESTS
received_vesting_shares7757.921032 VESTS
reward_vesting_balance0.000000 VESTS
vesting_balance0.000 STEEM
vesting_withdraw_rate0.000000 VESTS
next_vesting_withdrawal1969-12-31T23:59:59
withdrawn0
to_withdraw0
withdraw_routes0
savings_withdraw_requests0
last_account_recovery1970-01-01T00:00:00
reset_accountnull
last_owner_update1970-01-01T00:00:00
last_account_update1970-01-01T00:00:00
minedNo
sbd_seconds0
sbd_last_interest_payment1970-01-01T00:00:00
savings_sbd_last_interest_payment1970-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

IncomingOutgoing
Empty
Empty
{
  "incoming": [],
  "outgoing": []
}
From Date
To Date
steemdelegated 4.764 SP to @yeshulin
2026/05/18 08:27:21
delegatorsteem
delegateeyeshulin
vesting shares7757.921032 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 3.099 SP to @yeshulin
2026/05/13 13:10:54
delegatorsteem
delegateeyeshulin
vesting shares5045.710627 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 4.772 SP to @yeshulin
2026/04/26 07:36:00
delegatorsteem
delegateeyeshulin
vesting shares7770.436788 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 3.124 SP to @yeshulin
2026/01/24 05:51:57
delegatorsteem
delegateeyeshulin
vesting shares5087.257446 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 3.225 SP to @yeshulin
2024/12/18 01:00:45
delegatorsteem
delegateeyeshulin
vesting shares5251.476643 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 3.329 SP to @yeshulin
2023/11/14 16:40:00
delegatorsteem
delegateeyeshulin
vesting shares5420.610175 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.132 SP to @yeshulin
2023/09/22 12:57:48
delegatorsteem
delegateeyeshulin
vesting shares8357.518961 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.269 SP to @yeshulin
2022/11/03 20:05:18
delegatorsteem
delegateeyeshulin
vesting shares8579.570399 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.404 SP to @yeshulin
2022/01/18 01:04:33
delegatorsteem
delegateeyeshulin
vesting shares8799.678000 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.517 SP to @yeshulin
2021/06/14 08:10:12
delegatorsteem
delegateeyeshulin
vesting shares8983.872288 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.632 SP to @yeshulin
2020/12/11 18:20:15
delegatorsteem
delegateeyeshulin
vesting shares9171.294262 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 1.174 SP to @yeshulin
2020/12/06 11:55:09
delegatorsteem
delegateeyeshulin
vesting shares1912.543513 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.636 SP to @yeshulin
2020/12/05 21:57:54
delegatorsteem
delegateeyeshulin
vesting shares9177.502116 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 1.179 SP to @yeshulin
2020/11/03 06:34:51
delegatorsteem
delegateeyeshulin
vesting shares1920.017158 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 5.760 SP to @yeshulin
2020/05/09 13:00:12
delegatorsteem
delegateeyeshulin
vesting shares9380.307475 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 1.200 SP to @yeshulin
2020/05/08 17:42:03
delegatorsteem
delegateeyeshulin
vesting shares1953.311140 VESTS
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbtc
authorsteemitboard
permlinksteemitboard-notify-yeshulin-20200316t065854000z
title
bodyCongratulations @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 InfoBlock #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\"]}"
    }
  ]
}
steemdelegated 5.876 SP to @yeshulin
2019/06/17 07:12:21
delegatorsteem
delegateeyeshulin
vesting shares9568.181835 VESTS
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbtc
authorsteemitboard
permlinksteemitboard-notify-yeshulin-20190316t020447000z
title
bodyCongratulations @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 InfoBlock #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\"]}"
    }
  ]
}
steemdelegated 5.998 SP to @yeshulin
2018/06/25 13:54:27
delegatorsteem
delegateeyeshulin
vesting shares9767.353821 VESTS
Transaction InfoBlock #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"
    }
  ]
}
steemdelegated 18.553 SP to @yeshulin
2018/03/30 19:21:42
delegatorsteem
delegateeyeshulin
vesting shares30211.870229 VESTS
Transaction InfoBlock #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 SP
2018/03/26 12:14:30
accountyeshulin
reward steem0.002 STEEM
reward sbd0.171 SBD
reward vests181.596665 VESTS
Transaction InfoBlock #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"
    }
  ]
}
2018/03/26 12:13:27
voteryeshulin
authoryeshulin
permlinkbuilding-blockchain-in-go-part-3-persistence-and-cli
weight10000 (100.00%)
Transaction InfoBlock #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-prototype
2018/03/25 14:12:30
authoryeshulin
permlinkbuilding-blockchain-in-go-part-1-basic-prototype
sbd payout0.171 SBD
steem payout0.002 STEEM
vesting payout181.596665 VESTS
Transaction InfoBlock #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"
    }
  ]
}
2018/03/24 10:12:45
voterkingak47
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight10000 (100.00%)
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbtc
authorsteemitboard
permlinksteemitboard-notify-yeshulin-20180324t093223000z
title
bodyCongratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) : [![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/comments.png)](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 InfoBlock #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[![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/comments.png)](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
parent authorkingak47
parent permlinksexual-honey-coat-beauty
authoryeshulin
permlinkre-kingak47-sexual-honey-coat-beauty-20180324t051132484z
title
bodygood,I like beatiful girl...
json metadata{"tags":["cn"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
2018/03/24 05:09:24
voteryeshulin
authorkingak47
permlinksexual-honey-coat-beauty
weight10000 (100.00%)
Transaction InfoBlock #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
parent authormmmyyyzzz
parent permlinkif-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100
authoryeshulin
permlinkre-mmmyyyzzz-if-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100-20180324t050754527z
title
bodyif you have 100 million btc, haha,friends...
json metadata{"tags":["cn"],"app":"steemit/0.1"}
Transaction InfoBlock #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
voteryeshulin
authormmmyyyzzz
permlinkif-i-have-1-million-gold-coins-and-decided-sold-overnight-what-happens-100
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
yeshulinupvoted (100.00%) @yeshulin / btc
2018/03/21 05:18:27
voteryeshulin
authoryeshulin
permlinkbtc
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
yeshulinpublished a new post: btc
2018/03/21 03:51:39
parent author
parent permlinkbtc
authoryeshulin
permlinkbtc
title关于BTC价格走势
body从最近的趋势看,BTC有出现反转的迹象,大家可以选择低位建仓,估计在9500usdt左右会向下回踩一波,继续向上。山寨因为前期都普遍腰斩,有大涨趋势,可以选择未涨山寨建仓。
json metadata{"tags":["btc","bitcoin"],"app":"steemit/0.1","format":"markdown"}
Transaction InfoBlock #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
voteryeshulin
authorcryptoissweet
permlinkre-yeshulin-the-new-ways-to-save-crypto-from-a-post-quantum-world-1521345706444tf723fbd7-494a-4c52-916d-5ad83bb72395uid
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 15:17:33
voteryeshulin
authoryeshulin
permlinkthe-new-ways-to-save-crypto-from-a-post-quantum-world
weight10000 (100.00%)
Transaction InfoBlock #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
parent authorliuzg
parent permlinkre-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z
authoryeshulin
permlinkre-liuzg-re-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t145140133z
title
body哈哈,关于区块链一定要多学习的.
json metadata{"tags":["building"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
2018/03/20 14:23:00
votermylucky
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight10000 (100.00%)
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbuilding-blockchain-in-go-part-4-transactions
authorliuzg
permlinkre-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t063453837z
title
body这应该是一部分程式吧? 我外行,看不懂!😌
json metadata{"tags":["building"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
2018/03/20 06:33:21
voterliuzg
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight10000 (100.00%)
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkre-liuzg-8-or-or-20180320t042153215z
authorliuzg
permlinkre-yeshulin-re-liuzg-8-or-or-20180320t055946204z
title
body这个是我个人的理解,欢迎指正!
json metadata{"tags":["busy"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
2018/03/20 04:21:54
parent authorliuzg
parent permlink8-or-or
authoryeshulin
permlinkre-liuzg-8-or-or-20180320t042153215z
title
body这个通俗易懂,厉害。。。愿大家都能够照顾好自己的身体。healthy for everybody.
json metadata{"tags":["busy"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
yeshulinupvoted (100.00%) @liuzg / 8-or-or
2018/03/20 04:20:15
voteryeshulin
authorliuzg
permlink8-or-or
weight10000 (100.00%)
Transaction InfoBlock #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
required auths[]
required posting auths["yeshulin"]
idfollow
json["follow",{"follower":"yeshulin","following":"liuzg","what":["blog"]}]
Transaction InfoBlock #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\"]}]"
    }
  ]
}
2018/03/20 01:35:24
voteryeshulin
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
yeshulinupvoted (100.00%) @yeshulin / 4imgnt
2018/03/20 01:22:54
voteryeshulin
authoryeshulin
permlink4imgnt
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:22:36
voteryeshulin
authoryeshulin
permlinkbuilding-blockchain-in-go-part-1-basic-prototype
weight10000 (100.00%)
Transaction InfoBlock #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
voteryeshulin
authoryeshulin
permlinkre-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:19:45
voteryeshulin
authoryeshulin
permlinkre-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
weight0 (0.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:19:30
voteryeshulin
authormmmyyyzzz
permlinkwhy-is-the-word-win-so-hard-to-write
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:19:18
voteryeshulin
authormmmyyyzzz
permlinkwhy-is-the-word-win-so-hard-to-write
weight0 (0.00%)
Transaction InfoBlock #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
required auths[]
required posting auths["yeshulin"]
idfollow
json["follow",{"follower":"yeshulin","following":"oflyhigh","what":["blog"]}]
Transaction InfoBlock #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\"]}]"
    }
  ]
}
2018/03/20 01:03:39
voteryeshulin
authormmmyyyzzz
permlinkwhy-is-the-word-win-so-hard-to-write
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:03:15
voteryeshulin
authoryeshulin
permlinkre-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
weight-10000 (-100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/20 01:02:18
required auths[]
required posting auths["yeshulin"]
idfollow
json["follow",{"follower":"yeshulin","following":"mmmyyyzzz","what":["blog"]}]
Transaction InfoBlock #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
parent authormmmyyyzzz
parent permlinkre-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z
authoryeshulin
permlinkre-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 InfoBlock #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
parent authormmmyyyzzz
parent permlinkwhy-is-the-word-win-so-hard-to-write
authoryeshulin
permlinkre-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005556455z
title
bodyChinese culture is very profound,china has a long history.
json metadata{"tags":["travel"],"app":"steemit/0.1"}
Transaction InfoBlock #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
parent authormmmyyyzzz
parent permlinkwhy-is-the-word-win-so-hard-to-write
authoryeshulin
permlinkre-mmmyyyzzz-why-is-the-word-win-so-hard-to-write-20180320t005411627z
title
bodyok,I will keep you.
json metadata{"tags":["travel"],"app":"steemit/0.1"}
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbuilding-blockchain-in-go-part-4-transactions
authormmmyyyzzz
permlinkre-yeshulin-building-blockchain-in-go-part-4-transactions-20180320t005238854z
title
body这是什么
json metadata{"tags":["building"],"app":"steemit/0.1"}
Transaction InfoBlock #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\"}"
    }
  ]
}
2018/03/20 00:52:03
votermmmyyyzzz
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight10000 (100.00%)
Transaction InfoBlock #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
parent authoryeshulin
parent permlinkbuilding-blockchain-in-go-part-4-transactions
authorcheetah
permlinkcheetah-re-yeshulinbuilding-blockchain-in-go-part-4-transactions
title
bodyHi! 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 InfoBlock #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": ""
    }
  ]
}
2018/03/19 14:49:09
votercheetah
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
weight8 (0.08%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/19 14:48:48
parent author
parent permlinkbuilding
authoryeshulin
permlinkbuilding-blockchain-in-go-part-4-transactions
titleBuilding Blockchain in Go. Part 4: Transactions
bodyfunc (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 InfoBlock #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 / 2jovhl
2018/03/19 14:29:06
votersteemitboard
authoryeshulin
permlink2jovhl
weight100 (1.00%)
Transaction InfoBlock #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
parent authoryeshulin
parent permlink2jovhl
authorsteemitboard
permlinksteemitboard-notify-yeshulin-20180319t142905000z
title
bodyCongratulations @yeshulin! You have completed some achievement on Steemit and have been rewarded with new badge(s) : [![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/posts.png)](http://steemitboard.com/@yeshulin) Award for the number of posts published [![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/voted.png)](http://steemitboard.com/@yeshulin) Award for the number of upvotes received [![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/post4day.png)](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 InfoBlock #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[![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/posts.png)](http://steemitboard.com/@yeshulin) Award for the number of posts published\n[![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/voted.png)](http://steemitboard.com/@yeshulin) Award for the number of upvotes received\n[![](https://steemitimages.com/70x80/http://steemitboard.com/notifications/post4day.png)](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\"]}"
    }
  ]
}
2018/03/19 10:47:24
required auths[]
required posting auths["yeshulin"]
idfollow
json["follow",{"follower":"yeshulin","following":"sensation","what":["blog"]}]
Transaction InfoBlock #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\"]}]"
    }
  ]
}
2018/03/19 10:47:15
required auths[]
required posting auths["yeshulin"]
idfollow
json["follow",{"follower":"yeshulin","following":"darkvaders","what":["blog"]}]
Transaction InfoBlock #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\"]}]"
    }
  ]
}
2018/03/19 00:51:51
voterdarkvaders
authoryeshulin
permlinkbuilding-blockchain-in-go-part-2-proof-of-work
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/18 15:54:18
votersensation
authoryeshulin
permlinkbuilding-blockchain-in-go-part-2-proof-of-work
weight10000 (100.00%)
Transaction InfoBlock #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
    }
  ]
}
yeshulinpublished a new post: 2jovhl
2018/03/18 15:04:27
parent author
parent permlinkchengdu
authoryeshulin
permlink2jovhl
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 InfoBlock #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\"]}"
    }
  ]
}
yeshulinpublished a new post: 2jovhl
2018/03/18 14:58:42
parent author
parent permlinkchengdu
authoryeshulin
permlink2jovhl
title成都哪些美食值得一吃呢?
bodyNO.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 InfoBlock #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
parent authoryeshulin
parent permlinkbuilding-blockchain-in-go-part-3-persistence-and-cli
authorcheetah
permlinkcheetah-re-yeshulinbuilding-blockchain-in-go-part-3-persistence-and-cli
title
bodyHi! 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 InfoBlock #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": ""
    }
  ]
}
2018/03/18 14:41:48
votercheetah
authoryeshulin
permlinkbuilding-blockchain-in-go-part-3-persistence-and-cli
weight8 (0.08%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/18 14:41:03
parent author
parent permlinkblockchain
authoryeshulin
permlinkbuilding-blockchain-in-go-part-3-persistence-and-cli
titleBuilding Blockchain in Go. Part 3: Persistence and CLI
bodyIntroduction 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 InfoBlock #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
parent authoryeshulin
parent permlinkbuilding-blockchain-in-go-part-2-proof-of-work
authorcheetah
permlinkcheetah-re-yeshulinbuilding-blockchain-in-go-part-2-proof-of-work
title
bodyHi! 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 InfoBlock #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": ""
    }
  ]
}
2018/03/18 14:19:36
votercheetah
authoryeshulin
permlinkbuilding-blockchain-in-go-part-2-proof-of-work
weight8 (0.08%)
Transaction InfoBlock #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
    }
  ]
}
2018/03/18 14:19:21
parent author
parent permlinkblockchain
authoryeshulin
permlinkbuilding-blockchain-in-go-part-2-proof-of-work
titleBuilding Blockchain in Go. Part 2: Proof-of-Work
bodyIntroduction 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 InfoBlock #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\"}"
    }
  ]
}

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.
[]