├── .github ├── actions │ └── github-release │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── action.yml │ │ ├── main.js │ │ └── package.json ├── tools │ └── build-tarballs.sh └── workflows │ └── ci.yml ├── .gitignore ├── .golangci.yaml ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── blockchain ├── chain.go ├── check_block.go ├── check_health.go ├── pow_blocks.go ├── pow_chain.go └── pow_tx.go ├── cmd ├── coreminer │ ├── app.go │ ├── client.go │ ├── defaults.go │ ├── job.go │ ├── main.go │ └── miner.go ├── genesistool │ ├── defaults.go │ ├── main.go │ └── utils.go └── ngcore │ ├── app.go │ ├── cli_tools.go │ ├── defaults.go │ ├── key_tools.go │ └── main.go ├── consensus ├── doc.go ├── generate_tx_builder.go ├── pow.go ├── pow_test.go ├── report.go ├── sync.go ├── sync_bootstrap.go ├── sync_converge.go ├── sync_fast.go ├── sync_get.go ├── sync_loop.go ├── sync_normal.go ├── sync_remote_record.go └── sync_snapshot.go ├── go.mod ├── go.sum ├── jsonrpc ├── chain.go ├── doc.go ├── main.go ├── main_test.go ├── mining.go ├── p2p.go ├── regiser_handlers.go ├── state_sheet.go ├── state_tx.go ├── state_vm.go ├── utils.go └── workpool │ ├── expirable_map.go │ └── pool.go ├── keytools ├── defaults.go ├── p2p.go ├── tools.go └── tools_test.go ├── ngblocks ├── check.go ├── doc.go ├── get.go ├── init.go ├── put.go ├── put_force.go └── store.go ├── ngp2p ├── bootstrap_nodes.go ├── broadcast │ ├── broadcast.go │ ├── broadcast_block.go │ └── broadcast_tx.go ├── defaults │ └── defaults.go ├── dht.go ├── doc.go ├── main.go └── wired │ ├── message.go │ ├── message_payload.go │ ├── message_recv.go │ ├── message_send.go │ ├── message_verify.go │ ├── wired.go │ ├── wired_chain.go │ ├── wired_getchain.go │ ├── wired_getsheet.go │ ├── wired_ping.go │ ├── wired_pong.go │ ├── wired_reject.go │ └── wired_sheet.go ├── ngpool ├── doc.go ├── pool.go ├── pool_get.go ├── pool_put.go └── pool_test.go ├── ngstate ├── doc.go ├── errors.go ├── events.go ├── hooks.go ├── imports.go ├── imports_account.go ├── imports_tx.go ├── state.go ├── state_check.go ├── state_external.go ├── state_handle.go ├── state_helper.go ├── state_snapshot.go ├── test │ ├── README.md │ └── contract.wasm ├── utils.go ├── vm_func.go ├── wasm.go └── wasm_test.go ├── ngtypes ├── abstract.go ├── account.go ├── account_context.go ├── account_json.go ├── account_num.go ├── account_test.go ├── address.go ├── address_balance.go ├── block.go ├── block_genesis.go ├── block_header.go ├── block_header_trie.go ├── block_json.go ├── block_light.go ├── block_test.go ├── defaults.go ├── difficulty.go ├── difficulty_test.go ├── doc.go ├── network.go ├── reward_curve.go ├── reward_curve_test.go ├── sheet.go ├── sheet_genesis.go ├── tx.go ├── tx_error.go ├── tx_extra.go ├── tx_genesis.go ├── tx_json.go ├── tx_test.go └── tx_trie.go ├── resources ├── NG.svg ├── NG_with_bg.png ├── NG_with_bg.svg ├── QA.md ├── ng_16x16.png └── ng_64x64.png ├── storage ├── db.go ├── db_test.go ├── err.go └── init.go └── utils ├── aes256gcm.go ├── aes256gcm_test.go ├── comparison.go ├── doc.go ├── format.go ├── format_test.go ├── hash.go ├── helper.go ├── json.go ├── key.go ├── key_test.go ├── locker.go ├── rand.go ├── serialize.go └── serialize_test.go /.github/actions/github-release/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:slim 2 | 3 | COPY . /action 4 | WORKDIR /action 5 | 6 | RUN npm install --production 7 | 8 | ENTRYPOINT ["node", "/action/main.js"] 9 | -------------------------------------------------------------------------------- /.github/actions/github-release/README.md: -------------------------------------------------------------------------------- 1 | # github-release 2 | 3 | An action used to publish GitHub releases. 4 | 5 | As of the time of this writing there's a few actions floating around which perform github releases but they all tend to 6 | have their set of drawbacks. Additionally nothing handles deleting releases which we need for our rolling 7 | `dev` release. 8 | 9 | To handle all this this action rolls-its-own implementation using the actions/toolkit repository and packages published 10 | there. These run in a Docker container and take various inputs to orchestrate the release from the build. 11 | 12 | More comments can be found in `main.js`. 13 | 14 | Testing this is really hard. If you want to try though run `npm install` and then `node main.js`. You'll have to 15 | configure a bunch of env vars though to get anything reasonably working. 16 | -------------------------------------------------------------------------------- /.github/actions/github-release/action.yml: -------------------------------------------------------------------------------- 1 | name: 'github releases' 2 | description: 'github releases' 3 | inputs: 4 | token: 5 | description: '' 6 | required: true 7 | name: 8 | description: '' 9 | required: true 10 | files: 11 | description: '' 12 | required: true 13 | runs: 14 | using: 'docker' 15 | image: 'Dockerfile' 16 | -------------------------------------------------------------------------------- /.github/actions/github-release/main.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const path = require("path"); 3 | const fs = require("fs"); 4 | const github = require('@actions/github'); 5 | const glob = require('glob'); 6 | 7 | function sleep(milliseconds) { 8 | return new Promise(resolve => setTimeout(resolve, milliseconds)) 9 | } 10 | 11 | async function runOnce() { 12 | // Load all our inputs and env vars. Note that `getInput` reads from `INPUT_*` 13 | const files = core.getInput('files'); 14 | const name = core.getInput('name'); 15 | const token = core.getInput('token'); 16 | const slug = process.env.GITHUB_REPOSITORY; 17 | const owner = slug.split('/')[0]; 18 | const repo = slug.split('/')[1]; 19 | const sha = process.env.GITHUB_SHA; 20 | 21 | core.info(`files: ${files}`); 22 | core.info(`name: ${name}`); 23 | core.info(`token: ${token}`); 24 | 25 | const octokit = new github.GitHub(token); 26 | 27 | // Delete the previous release since we can't overwrite one. This may happen 28 | // due to retrying an upload or it may happen because we're doing the dev 29 | // release. 30 | const releases = await octokit.paginate("GET /repos/:owner/:repo/releases", {owner, repo}); 31 | for (const release of releases) { 32 | if (release.tag_name !== name) { 33 | continue; 34 | } 35 | const release_id = release.id; 36 | core.info(`deleting release ${release_id}`); 37 | await octokit.repos.deleteRelease({owner, repo, release_id}); 38 | } 39 | 40 | // We also need to update the `dev` tag while we're at it on the `dev` branch. 41 | if (name == 'dev') { 42 | try { 43 | core.info(`updating dev tag`); 44 | await octokit.git.updateRef({ 45 | owner, 46 | repo, 47 | ref: 'tags/dev', 48 | sha, 49 | force: true, 50 | }); 51 | } catch (e) { 52 | console.log("ERROR: ", JSON.stringify(e, null, 2)); 53 | core.info(`creating dev tag`); 54 | await octokit.git.createTag({ 55 | owner, 56 | repo, 57 | tag: 'dev', 58 | message: 'dev release', 59 | object: sha, 60 | type: 'commit', 61 | }); 62 | } 63 | } 64 | 65 | // Creates an official GitHub release for this `tag`, and if this is `dev` 66 | // then we know that from the previous block this should be a fresh release. 67 | core.info(`creating a release`); 68 | const release = await octokit.repos.createRelease({ 69 | owner, 70 | repo, 71 | tag_name: name, 72 | prerelease: name === 'dev', 73 | }); 74 | 75 | // Upload all the relevant assets for this release as just general blobs. 76 | for (const file of glob.sync(files)) { 77 | const size = fs.statSync(file).size; 78 | core.info(`upload ${file}`); 79 | await octokit.repos.uploadReleaseAsset({ 80 | data: fs.createReadStream(file), 81 | headers: {'content-length': size, 'content-type': 'application/octet-stream'}, 82 | name: path.basename(file), 83 | url: release.data.upload_url, 84 | }); 85 | } 86 | } 87 | 88 | async function run() { 89 | const retries = 10; 90 | for (let i = 0; i < retries; i++) { 91 | try { 92 | await runOnce(); 93 | break; 94 | } catch (e) { 95 | if (i === retries - 1) 96 | throw e; 97 | logError(e); 98 | console.log("RETRYING after 10s"); 99 | await sleep(10000) 100 | } 101 | } 102 | } 103 | 104 | function logError(e) { 105 | console.log("ERROR: ", e.message); 106 | try { 107 | console.log(JSON.stringify(e, null, 2)); 108 | } catch (e) { 109 | // ignore json errors for now 110 | } 111 | console.log(e.stack); 112 | } 113 | 114 | run().catch(err => { 115 | logError(err); 116 | core.setFailed(err.message); 117 | }); 118 | -------------------------------------------------------------------------------- /.github/actions/github-release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-release", 3 | "version": "0.0.0", 4 | "main": "main.js", 5 | "dependencies": { 6 | "@actions/core": "^1.0.0", 7 | "@actions/github": "^1.0.0", 8 | "glob": "^7.1.5" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/tools/build-tarballs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | platform=$1 5 | win=$2 6 | 7 | rm -rf tmp 8 | mkdir tmp 9 | mkdir -p dist 10 | 11 | mktarball() { 12 | dir=$1 13 | if [ "$win" = "" ]; then 14 | tar cJf dist/$dir.tar.xz -C tmp $dir 15 | else 16 | (cd tmp && zip -r ../dist/$dir.zip $dir) 17 | fi 18 | } 19 | 20 | # Create the main tarball of binaries 21 | bin_pkgname=ngcore-$platform 22 | mkdir tmp/$bin_pkgname 23 | 24 | mv ngcore-$platform/* tmp/$bin_pkgname 25 | mktarball $bin_pkgname 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #GO 2 | ##Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | 9 | ##Test binary, build with `go test -c` 10 | *.test 11 | 12 | ##Output of the go coverage tool, specifically when used with LiteIDE 13 | *.out 14 | 15 | ##go module 16 | vendor/ 17 | 18 | #VSCode 19 | .vscode 20 | 21 | # Local History for Visual Studio Code 22 | .history/ 23 | 24 | # debug binary 25 | __debug_bin 26 | 27 | #IDEA 28 | .idea 29 | 30 | 31 | #bazel: 32 | /bazel-* 33 | 34 | #Personal 35 | ##PrivateKey 36 | *.key 37 | 38 | #Debug 39 | env/* 40 | **/.ngdb 41 | **/ngdb 42 | **/*.db 43 | *.log 44 | 45 | #Binary 46 | /ngcore 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## v0.0.22 4 | 5 | - TODO: add quill protocol to manage the contract 6 | - TODO: using bbolt or other simple db to replace badgerdb 7 | - TODO: implement the sub-block acts as ethereum's uncle blocks 8 | 9 | ## v0.0.21 10 | 11 | - BUGFIX: fix balance bug in the genesis list 12 | - DONE: remove built-in miner 13 | - DONE: add standalone coreminer 14 | - DONE: optimize daemon running 15 | - DONE: upgrade errors to go1.13 version 16 | - DONE: introduce subs 17 | 18 | ## v0.0.20 19 | 20 | - DONE: fix bugs 21 | - DONE: implement getsheet&sheet on ngp2p 22 | - DONE: rename "fork" to "converge" 23 | - DONE: add snapshotSync and snapshotConverge 24 | - DONE: upgrade libp2p 25 | - DONE: add non-strict mode(fast sync) 26 | - DONE: add GetSheet and Sheet p2p message 27 | - DONE: enhance mining job update 28 | - DONE: add id for Block & Tx representing the hash 29 | - TODO: use subs field in Block to implement sub-blocks revenue in v0.0.21 30 | 31 | ## v0.0.19 32 | 33 | - DONE: support multi-network 34 | - DONE: add regression testnet 35 | 36 | ## v0.0.18 37 | 38 | - DONE: add storage for the state 39 | - DONE: use wasm vm & imports bindings 40 | - DONE: change wasm engine 41 | 42 | ## v0.0.17 43 | 44 | - BUGFIX: fix difficulty algorithm 45 | - DONE: add height bomb for difficulty algorithm 46 | - DONE: add keytools functions 47 | - DONE: rename gen to gentools 48 | - DONE: change default key and db path 49 | - DONE: save log into log file 50 | - DONE: use fmt to output 51 | - DONE: update genesis block time 52 | - DONE: upgrade P2P version 53 | 54 | ## v0.0.16 55 | 56 | - BUGFIX: fix jsonrpc 57 | - DONE: add kad-dht and mdns for peer discovery 58 | - REMOVE: temporarily remove bazel 59 | - DONE: use github action CI instead of circleCI 60 | - DONE: add auto-fork mechanism 61 | - BUGFIX: fix miner's job update on receiving p2p broadcasts 62 | - BUGFIX: fix some deadlocks 63 | - DONE: update genesis block time 64 | - DONE: upgrade P2P version 65 | - DONE: test new difficulty algorithm 66 | 67 | ## v0.0.15 68 | 69 | - DONE: optimize built-in miner 70 | - DONE: avoid mem leak 71 | - DONE: fix tx check 72 | - DONE: pass ngwallet basic test 73 | 74 | ## v0.0.14 75 | 76 | - DONE: change PoW algorithm from cryptonight-go to RandomNG 77 | - DONE: add submitWork and getWork 78 | - DONE: update genesis block 79 | - BUGFIX: nonce length => 8 80 | 81 | ## v0.0.13 82 | 83 | - DONE: huge changes on JSON RPC 84 | - DONE: test and fix RegisterTx 85 | - DONE: fix some bugs on tx 86 | - DONE: remove useless height 87 | - DONE: add prevBlockHash for identification 88 | - DONE: same changes to state 89 | - DONE: now we can use prevBlockHash to verify whether the tx is on the correct height in TxPool.PutTx 90 | - DONE: fix checkRegister by adding newAccountNum check 91 | - DONE: recv and bcast Tx 92 | - DONE: fix wrong regTx extra len requirement 93 | - DONE: api params 94 | - DONE: apply tx into state 95 | - DONE: speed up sync 96 | - DONE: take a tx test on ngwallet 97 | 98 | ## v0.0.12 99 | 100 | - DONE: add jsonrpc { GBT, submitBlock, getNetwork, getPeers, getLatest } 101 | - DONE: upgrade deps 102 | - DONE: optimize codes 103 | 104 | ## v0.0.11 105 | 106 | - DONE: Introducing Address to avoid potential public key collision 107 | - DONE: Finish new ngstate 108 | - TODO: Unit tests for ngstate 109 | 110 | ## v0.0.10 111 | 112 | - Initialized and getting ready for v0.0.11 113 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Currently using ubuntu for usability. 2 | # BUILDER 3 | FROM golang:latest as builder 4 | 5 | COPY . /build 6 | WORKDIR /build 7 | 8 | RUN apt install gcc -y 9 | RUN GOPROXY=$GOPROXY make build 10 | 11 | # MAIN 12 | FROM ubuntu:latest 13 | 14 | COPY --from=builder /build/ngcore /usr/local/bin/ 15 | 16 | WORKDIR /workspace 17 | 18 | EXPOSE 52520 52521 19 | ENTRYPOINT ["ngcore"] 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | 3 | COMMIT = $(shell git rev-parse HEAD) 4 | TAG = $(shell git describe --tags --abbrev=0) 5 | 6 | docker-build: 7 | docker build -t ngcore . 8 | docker-run: docker-build 9 | docker run -p 52520:52520 -p 52521:52521 -v ~/.ngdb:/.ngdb ngcore --in-mem --log-level debug 10 | docker-run-bootstrap: docker-build 11 | docker run -p 52520:52520 -p 52521:52521 -v ~/.ngdb:/.ngdb ngcore --bootstrap --in-mem --log-level debug 12 | run: 13 | go run -ldflags "-X main.Commit=${COMMIT} -X main.Tag=${TAG}" ./cmd/ngcore $(ARGS) 14 | build: 15 | go build -ldflags "-X main.Commit=${COMMIT} -X main.Tag=${TAG}" ./cmd/ngcore 16 | bootstrap: build 17 | ./ngcore --bootstrap --in-mem 18 | clean: 19 | rm ~/ngdb 20 | build-miner: 21 | go build -ldflags "-X main.Commit=${COMMIT} -X main.Tag=${TAG}" ./cmd/coreminer 22 | run-miner: 23 | go run -ldflags "-X main.Commit=${COMMIT} -X main.Tag=${TAG}" ./cmd/coreminer 24 | gazelle: 25 | bazel run //:gazelle -- -go_prefix github.com/ngchain/ngcore 26 | bazel run //:gazelle -- update-repos -from_file=go.mod 27 | -------------------------------------------------------------------------------- /blockchain/chain.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | logging "github.com/ngchain/zap-log" 6 | 7 | "github.com/ngchain/ngcore/ngblocks" 8 | "github.com/ngchain/ngcore/ngstate" 9 | "github.com/ngchain/ngcore/ngtypes" 10 | ) 11 | 12 | var log = logging.Logger("chain") 13 | 14 | type Chain struct { 15 | *bbolt.DB 16 | 17 | *ngblocks.BlockStore 18 | *ngstate.State 19 | 20 | Network ngtypes.Network 21 | } 22 | 23 | func Init(db *bbolt.DB, network ngtypes.Network, store *ngblocks.BlockStore, state *ngstate.State) *Chain { 24 | chain := &Chain{ 25 | DB: db, 26 | 27 | BlockStore: store, 28 | State: state, 29 | 30 | Network: network, 31 | } 32 | 33 | return chain 34 | } 35 | -------------------------------------------------------------------------------- /blockchain/check_block.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "bytes" 5 | "math/big" 6 | 7 | "go.etcd.io/bbolt" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/ngchain/ngcore/ngblocks" 11 | "github.com/ngchain/ngcore/ngstate" 12 | "github.com/ngchain/ngcore/ngtypes" 13 | "github.com/ngchain/ngcore/storage" 14 | ) 15 | 16 | // CheckBlock checks block before putting into chain. 17 | func (chain *Chain) CheckBlock(b ngtypes.Block) error { 18 | block := b.(*ngtypes.FullBlock) 19 | if block.IsGenesis() { 20 | return nil 21 | } 22 | 23 | // check block itself 24 | if err := block.CheckError(); err != nil { 25 | return err 26 | } 27 | 28 | err := chain.View(func(txn *bbolt.Tx) error { 29 | blockBucket := txn.Bucket(storage.BlockBucketName) 30 | 31 | originHash, err := ngblocks.GetOriginHash(blockBucket) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | if !bytes.Equal(block.GetPrevHash(), originHash) { 37 | prevBlock, err := chain.getBlockByHash(block.GetPrevHash()) 38 | if err != nil { 39 | return errors.Wrapf(err, "failed to get the prev block@%d %x", 40 | block.GetHeight()-1, block.GetPrevHash()) 41 | } 42 | 43 | if err := checkBlockTarget(block, prevBlock); err != nil { 44 | return errors.Wrapf(err, "failed on checking block target") 45 | } 46 | } 47 | 48 | return ngstate.CheckBlockTxs(txn, block) 49 | }) 50 | if err != nil { 51 | return errors.Wrap(err, "block txs are invalid") 52 | } 53 | 54 | return nil 55 | } 56 | 57 | func checkBlockTarget(block, prevBlock *ngtypes.FullBlock) error { 58 | correctDiff := ngtypes.GetNextDiff(block.GetHeight(), block.BlockHeader.Timestamp, prevBlock) 59 | blockDiff := new(big.Int).SetBytes(block.BlockHeader.Difficulty) 60 | actualDiff := block.GetActualDiff() 61 | 62 | if blockDiff.Cmp(correctDiff) != 0 { 63 | return errors.Wrapf(ngtypes.ErrBlockDiffInvalid, "wrong block diff for block@%d, diff in block: %x shall be %x", 64 | block.GetHeight(), blockDiff, correctDiff) 65 | } 66 | 67 | if actualDiff.Cmp(correctDiff) < 0 { 68 | return errors.Wrapf(ngtypes.ErrBlockDiffInvalid, "wrong block diff for block@%d, actual diff in block: %x shall be large than %x", 69 | block.GetHeight(), actualDiff, correctDiff) 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /blockchain/check_health.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | func (chain *Chain) CheckHealth(network ngtypes.Network) { 11 | log.Warn("checking chain's health") 12 | latestHeight := chain.GetLatestBlockHeight() 13 | 14 | origin := chain.GetOriginBlock() 15 | originHeight := origin.GetHeight() 16 | 17 | prevBlockHash := origin.GetHash() 18 | 19 | for h := originHeight; h < latestHeight; { 20 | h++ 21 | b, err := chain.getBlockByHeight(h) 22 | if err != nil { 23 | panic(err) 24 | } 25 | 26 | if !bytes.Equal(b.GetPrevHash(), prevBlockHash) { 27 | panic(fmt.Sprintf("prev block hash %x is incorrect, shall be %x", b.GetPrevHash(), prevBlockHash)) 28 | } 29 | 30 | prevBlockHash = b.GetHash() 31 | } 32 | 33 | log.Warn("checking is finished") 34 | } 35 | -------------------------------------------------------------------------------- /blockchain/pow_chain.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | "github.com/ngchain/ngcore/ngblocks" 6 | "github.com/ngchain/ngcore/ngtypes" 7 | "github.com/ngchain/ngcore/storage" 8 | ) 9 | 10 | // ApplyBlock checks the block and then calls blockchain's PutNewBlock, and then auto-upgrade the state. 11 | func (chain *Chain) ApplyBlock(block *ngtypes.FullBlock) error { 12 | err := chain.Update(func(txn *bbolt.Tx) error { 13 | blockBucket := txn.Bucket(storage.BlockBucketName) 14 | txBucket := txn.Bucket(storage.BlockBucketName) 15 | 16 | // check block first 17 | if err := chain.CheckBlock(block); err != nil { 18 | return err 19 | } 20 | 21 | // block is valid 22 | err := ngblocks.PutNewBlock(blockBucket, txBucket, block) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | err = chain.State.Upgrade(txn, block) // handle Block Txs inside 28 | if err != nil { 29 | return err 30 | } 31 | 32 | return nil 33 | }) 34 | if err != nil { 35 | return err 36 | } 37 | 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /blockchain/pow_tx.go: -------------------------------------------------------------------------------- 1 | package blockchain 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | "github.com/ngchain/ngcore/ngblocks" 6 | "github.com/ngchain/ngcore/ngtypes" 7 | "github.com/ngchain/ngcore/storage" 8 | ) 9 | 10 | // GetTxByHash gets the tx with hash from db, so the tx must be applied. 11 | func (chain *Chain) GetTxByHash(hash []byte) (*ngtypes.FullTx, error) { 12 | tx := &ngtypes.FullTx{} 13 | 14 | if err := chain.View(func(txn *bbolt.Tx) error { 15 | txBucket := txn.Bucket(storage.TxBucketName) 16 | 17 | var err error 18 | tx, err = ngblocks.GetTxByHash(txBucket, hash) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | return nil 24 | }); err != nil { 25 | return nil, err 26 | } 27 | 28 | return tx, nil 29 | } 30 | -------------------------------------------------------------------------------- /cmd/coreminer/app.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/ngchain/ngcore/keytools" 7 | "github.com/ngchain/ngcore/ngtypes" 8 | "github.com/urfave/cli/v2" 9 | ) 10 | 11 | var coreAddrFlag = &cli.StringFlag{ 12 | Name: "addr", 13 | Aliases: []string{"a"}, 14 | Usage: "ngcore address for JSON RPC", 15 | Value: defaultRPCHost, 16 | } 17 | 18 | var corePortFlag = &cli.IntFlag{ 19 | Name: "port", 20 | Aliases: []string{"p"}, 21 | Usage: "ngcore address for JSON RPC", 22 | Value: defaultRPCPort, 23 | } 24 | 25 | var keyFileFlag = &cli.StringFlag{ 26 | Name: "file", 27 | Aliases: []string{"f"}, 28 | Usage: "address' key file for receiving rewards", 29 | Value: keytools.GetDefaultFile(), 30 | } 31 | 32 | var keyPassFlag = &cli.StringFlag{ 33 | Name: "password", 34 | Aliases: []string{"pw"}, 35 | Usage: "key file password", 36 | Value: "", 37 | } 38 | 39 | var networkFlag = &cli.StringFlag{ 40 | Name: "network", 41 | Aliases: []string{"x"}, 42 | Usage: "daemon network", 43 | Value: "MAINNET", 44 | } 45 | 46 | var mining cli.ActionFunc = func(context *cli.Context) error { 47 | network := ngtypes.GetNetwork(context.String(networkFlag.Name)) 48 | priv := keytools.ReadLocalKey(context.String(keyFileFlag.Name), context.String(keyPassFlag.Name)) 49 | client := NewClient(context.String(coreAddrFlag.Name), context.Int(corePortFlag.Name), network, priv) 50 | 51 | foundCh := make(chan Job) 52 | 53 | threadNum := 2 // TODO 54 | 55 | du := time.Second * 10 56 | timeCh := time.NewTicker(du) 57 | allExitCh := make(chan struct{}, 1) 58 | miner := NewMiner(threadNum, foundCh, allExitCh) 59 | 60 | go func() { 61 | for { 62 | job := <-foundCh 63 | ok := client.SubmitWork(job.WorkID, job.Nonce, job.GenTx) 64 | if ok { 65 | timeCh.Reset(du) 66 | job := client.GetWork() 67 | miner.ExitJob() 68 | miner.Mining(*job) 69 | } 70 | } 71 | }() 72 | 73 | go func() { 74 | for { 75 | <-timeCh.C 76 | job := client.GetWork() 77 | miner.ExitJob() 78 | miner.Mining(*job) 79 | } 80 | }() 81 | 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /cmd/coreminer/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/hex" 5 | "io" 6 | 7 | "net" 8 | "strconv" 9 | "time" 10 | 11 | "github.com/c0mm4nd/go-jsonrpc2" 12 | "github.com/c0mm4nd/go-jsonrpc2/jsonrpc2http" 13 | "github.com/ngchain/ngcore/jsonrpc" 14 | "github.com/ngchain/ngcore/ngtypes" 15 | "github.com/ngchain/ngcore/utils" 16 | "github.com/ngchain/secp256k1" 17 | ) 18 | 19 | type Client struct { 20 | coreAddr string 21 | corePort int 22 | baseURL string 23 | 24 | Network ngtypes.Network 25 | priv *secp256k1.PrivateKey 26 | 27 | client *jsonrpc2http.Client 28 | currentJob *Job 29 | OnNewJob chan *Job 30 | } 31 | 32 | func NewClient(coreAddr string, corePort int, network ngtypes.Network, privateKey *secp256k1.PrivateKey) *Client { 33 | baseURL := "http://" + net.JoinHostPort(coreAddr, strconv.Itoa(corePort)) 34 | return &Client{ 35 | coreAddr: coreAddr, 36 | corePort: corePort, 37 | baseURL: baseURL, 38 | 39 | Network: network, 40 | priv: privateKey, 41 | 42 | client: jsonrpc2http.NewClient(), 43 | currentJob: nil, 44 | OnNewJob: make(chan *Job), 45 | } 46 | } 47 | 48 | func (c *Client) Loop() { 49 | } 50 | 51 | func (c *Client) GetWork() *Job { 52 | msg := jsonrpc2.NewJsonRpcRequest(nil, "getWork", nil) 53 | req, err := jsonrpc2http.NewClientRequest(c.baseURL, msg) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | res, err := c.client.Do(req) 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | body, err := io.ReadAll(res.Body) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | resMsg, err := jsonrpc2.UnmarshalMessage(body) 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | switch resMsg.GetType() { 74 | case jsonrpc2.TypeErrorMsg, jsonrpc2.TypeInvalidMsg: 75 | panic(resMsg.Error.Message) 76 | 77 | case jsonrpc2.TypeSuccessMsg: 78 | var result jsonrpc.GetWorkReply 79 | err := utils.JSON.Unmarshal(*resMsg.Result, &result) 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | return NewJob(c.Network, c.priv, &result) 85 | default: 86 | panic("unknown response type") 87 | } 88 | } 89 | 90 | func (c *Client) SubmitWork(workID uint64, nonce []byte, genTx string) bool { 91 | submitWork, err := utils.JSON.Marshal(jsonrpc.SubmitWorkParams{ 92 | WorkID: workID, 93 | Nonce: hex.EncodeToString(nonce), 94 | GenTx: genTx, 95 | }) 96 | if err != nil { 97 | panic(err) 98 | } 99 | 100 | msg := jsonrpc2.NewJsonRpcRequest(time.Now().UnixNano(), "submitWork", submitWork) 101 | req, err := jsonrpc2http.NewClientRequest(c.baseURL, msg) 102 | if err != nil { 103 | panic(err) 104 | } 105 | 106 | res, err := c.client.Do(req) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | body, err := io.ReadAll(res.Body) 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | resMsg, err := jsonrpc2.UnmarshalMessage(body) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | switch resMsg.GetType() { 122 | case jsonrpc2.TypeErrorMsg, jsonrpc2.TypeInvalidMsg: 123 | log.Error(resMsg.Error.Message) 124 | return false 125 | case jsonrpc2.TypeSuccessMsg: 126 | log.Warning("nonce accepted by daemon") 127 | return true 128 | default: 129 | panic("unknown response type") 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /cmd/coreminer/defaults.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | name = "coreminer" 5 | usage = "Miner for ngchain PoW protocol" 6 | description = `This is the mining software for solo/local mining, 7 | the mining share from which will all get sent to the local ngcore. 8 | For pool mining, please download from https://github.com/ngchain/ngminer 9 | ` 10 | 11 | defaultRPCHost = "127.0.0.1" 12 | defaultRPCPort = 52521 13 | ) 14 | 15 | var ( 16 | Commit string // from `git rev-parse HEAD` 17 | Tag string // from `git describe --tags --abbrev=0` 18 | Version string 19 | ) 20 | 21 | func init() { 22 | if Tag == "" && Commit == "" { 23 | panic("invalid version: tag:" + Tag + " commit: " + Commit) 24 | } 25 | if Tag != "" { 26 | Version = Tag 27 | } else { 28 | Version = "v0.0.0-" + Commit 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cmd/coreminer/job.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/ngchain/ngcore/consensus" 5 | "github.com/ngchain/ngcore/jsonrpc" 6 | "github.com/ngchain/ngcore/ngtypes" 7 | "github.com/ngchain/ngcore/utils" 8 | "github.com/ngchain/secp256k1" 9 | ) 10 | 11 | type Job struct { 12 | block *ngtypes.FullBlock 13 | WorkID uint64 14 | Nonce []byte 15 | GenTx string 16 | } 17 | 18 | func NewJob(network ngtypes.Network, priv *secp256k1.PrivateKey, reply *jsonrpc.GetWorkReply) *Job { 19 | var block ngtypes.FullBlock 20 | var txs []*ngtypes.FullTx 21 | err := utils.HexRLPDecode(reply.Block, &block) 22 | if err != nil { 23 | panic(err) 24 | } 25 | err = utils.HexRLPDecode(reply.Txs, &txs) 26 | if err != nil { 27 | panic(err) 28 | } 29 | 30 | log.Warnf("new work: block %d with %d txs", block.Height, len(txs)) 31 | 32 | extraData := []byte("coreminer") 33 | genTx := consensus.CreateGenerateTx(network, priv, block.Height, extraData) 34 | 35 | err = block.ToUnsealing(append([]*ngtypes.FullTx{genTx}, txs...)) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | log.Warnf("tx (with gen) trie hash: %x", block.TxTrieHash) 41 | 42 | return &Job{ 43 | block: &block, 44 | WorkID: reply.WorkID, 45 | Nonce: nil, 46 | GenTx: utils.HexRLPEncode(genTx), 47 | } 48 | } 49 | 50 | func (j *Job) SetNonce(nonce []byte) { 51 | j.Nonce = nonce 52 | } 53 | -------------------------------------------------------------------------------- /cmd/coreminer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | logging "github.com/ngchain/zap-log" 7 | "github.com/urfave/cli/v2" 8 | ) 9 | 10 | var log = logging.Logger("miner") 11 | 12 | func main() { 13 | app := cli.NewApp() 14 | 15 | app.Name = name 16 | app.Usage = usage 17 | app.Description = description 18 | app.Version = Version 19 | app.Action = mining 20 | app.Flags = []cli.Flag{ 21 | coreAddrFlag, corePortFlag, 22 | keyFileFlag, keyPassFlag, 23 | networkFlag, 24 | } 25 | 26 | app.Commands = []*cli.Command{} 27 | 28 | err := app.Run(os.Args) 29 | if err != nil { 30 | panic(err) 31 | } 32 | 33 | select {} 34 | // os.Exit(0) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/coreminer/miner.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "math/big" 6 | "sync" 7 | "time" 8 | 9 | "github.com/ngchain/astrobwt" 10 | 11 | "github.com/ngchain/ngcore/ngtypes" 12 | "go.uber.org/atomic" 13 | ) 14 | 15 | type Miner struct { 16 | running *atomic.Bool 17 | threadNum int 18 | 19 | hashes *atomic.Int64 20 | quitSignal *atomic.Bool 21 | foundCh chan Job 22 | AllExitCh chan struct{} 23 | } 24 | 25 | func NewMiner(threadNum int, foundCh chan Job, allExitCh chan struct{}) *Miner { 26 | if threadNum <= 0 { 27 | panic("thread number is incorrect") 28 | } 29 | 30 | log.Warningf("start mining with %d thread(s)", threadNum) 31 | 32 | quitSignal := atomic.NewBool(false) 33 | 34 | m := &Miner{ 35 | running: atomic.NewBool(false), 36 | threadNum: threadNum, 37 | hashes: atomic.NewInt64(0), 38 | foundCh: foundCh, 39 | quitSignal: quitSignal, 40 | AllExitCh: allExitCh, 41 | } 42 | 43 | go func() { 44 | interval := 10 * time.Second 45 | reportTicker := time.NewTicker(interval) 46 | defer reportTicker.Stop() 47 | 48 | elapsed := int64(interval / time.Second) // 60 49 | 50 | for { 51 | <-reportTicker.C 52 | 53 | hashes := m.hashes.Load() 54 | log.Warningf("Total hashrate: %d h/s", hashes/elapsed) 55 | 56 | m.hashes.Sub(hashes) 57 | } 58 | }() 59 | 60 | return m 61 | } 62 | 63 | func (t *Miner) Mining(work Job) { 64 | ok := t.running.CompareAndSwap(false, true) 65 | if !ok { 66 | panic("try over mining") 67 | } 68 | 69 | diff := new(big.Int).SetBytes(work.block.BlockHeader.Difficulty) 70 | target := new(big.Int).Div(ngtypes.MaxTarget, diff) 71 | 72 | log.Warn("mining ready") 73 | 74 | var miningWG sync.WaitGroup 75 | for threadID := 0; threadID < t.threadNum; threadID++ { 76 | miningWG.Add(1) 77 | 78 | go func(threadID int) { 79 | defer miningWG.Done() 80 | 81 | for { 82 | if t.quitSignal.Load() { 83 | return 84 | } 85 | 86 | // Compute the PoW value of this nonce 87 | nonce := make([]byte, 8) 88 | _, err := rand.Read(nonce) 89 | if err != nil { 90 | return 91 | } 92 | 93 | hash := astrobwt.POW_0alloc(work.block.GetPoWRawHeader(nonce)) 94 | 95 | t.hashes.Inc() 96 | 97 | if hash != [32]byte{} && new(big.Int).SetBytes(hash[:]).Cmp(target) < 0 { 98 | log.Warningf("thread %d found nonce %x for block @ %d", threadID, nonce, work.block.GetHeight()) 99 | log.Debugf("%s < %s", new(big.Int).SetBytes(hash[:]), target) 100 | work.SetNonce(nonce) 101 | t.foundCh <- work 102 | return 103 | } 104 | // } 105 | } 106 | }(threadID) 107 | } 108 | miningWG.Wait() 109 | t.AllExitCh <- struct{}{} 110 | } 111 | 112 | func (t *Miner) ExitJob() { 113 | ok := t.running.CompareAndSwap(true, false) 114 | if ok { 115 | log.Warn("exiting all jobs") 116 | t.quitSignal.Store(true) 117 | <-t.AllExitCh 118 | t.quitSignal.Store(false) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /cmd/genesistool/defaults.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | name = "genesistool" 5 | usage = "Helper for generating initial variables for genesis items in ngchain" 6 | description = `This tool is set for chain developers to check the correctness of the genesis information (e.g. tx, block ...). 7 | And it will give suggestion values to help correct the chain's genesis info'` 8 | ) 9 | 10 | var ( 11 | Commit string // from `git rev-parse HEAD` 12 | Tag string // from `git describe --tags --abbrev=0` 13 | Version string 14 | ) 15 | 16 | func init() { 17 | if Tag == "" && Commit == "" { 18 | panic("invalid version: tag:" + Tag + " commit: " + Commit) 19 | } 20 | if Tag != "" { 21 | Version = Tag 22 | } else { 23 | Version = "v0.0.0-" + Commit 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /cmd/genesistool/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/mr-tron/base58" 9 | "github.com/urfave/cli/v2" 10 | 11 | "github.com/ngchain/ngcore/keytools" 12 | "github.com/ngchain/ngcore/ngtypes" 13 | "github.com/ngchain/ngcore/utils" 14 | ) 15 | 16 | var ErrGenesisKeyFileMissing = errors.New("file genesis.key is missing, try to create one first") 17 | 18 | var filenameFlag = &cli.StringFlag{ 19 | Name: "filename", 20 | Aliases: []string{"f"}, 21 | Value: "genesis.key", 22 | Usage: "the genesis.key file", 23 | } 24 | 25 | var passwordFlag = &cli.StringFlag{ 26 | Name: "password", 27 | Aliases: []string{"p"}, 28 | Value: "", 29 | Usage: "the password to genesis.key file (default: \"\")", 30 | } 31 | 32 | var checkCommand = &cli.Command{ 33 | Name: "check", 34 | Flags: []cli.Flag{filenameFlag, passwordFlag}, 35 | Description: "check genesis blocks and generateTx and re-generate them if error occurs", 36 | Action: func(context *cli.Context) error { 37 | filename := context.String("filename") 38 | password := context.String("password") 39 | 40 | localKey := keytools.ReadLocalKey(filename, password) 41 | if localKey == nil { 42 | panic(ErrGenesisKeyFileMissing) 43 | } 44 | 45 | raw := base58.FastBase58Encoding(utils.PublicKey2Bytes(localKey.PubKey())) 46 | fmt.Printf("genesis public key: %s \n", raw) 47 | 48 | fmt.Printf("genesis Address: %s \n", ngtypes.NewAddress(localKey).String()) 49 | 50 | for _, network := range ngtypes.AvailableNetworks { 51 | fmt.Printf("checking %s\n", network) 52 | 53 | gtx := ngtypes.GetGenesisGenerateTx(network) 54 | if err := gtx.CheckGenerate(0); err != nil { 55 | fmt.Printf("current genesis generate tx sign %x is invalid, err: %s, resignaturing... \n", gtx.Sign, err) 56 | 57 | err = gtx.Signature(localKey) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | fmt.Printf("Genesis Generate Tx Sign: %x \n", gtx.Sign) 63 | } else { 64 | fmt.Printf("Genesis block's generate tx is healthy \n") 65 | } 66 | 67 | b := ngtypes.GetGenesisBlock(network) 68 | if err := b.CheckError(); err != nil { 69 | fmt.Printf("Current genesis block is invalid, err: %s, use the generate tx above to re-calc nonce... \n", err) 70 | err := b.ToUnsealing([]*ngtypes.FullTx{gtx}) 71 | if err != nil { 72 | fmt.Print(err) 73 | } 74 | 75 | genBlockNonce(b) 76 | } else { 77 | fmt.Printf("Genesis block is healthy \n") 78 | } 79 | } 80 | 81 | return nil 82 | }, 83 | } 84 | 85 | var displayCommand = &cli.Command{ 86 | Name: "display", 87 | Flags: nil, 88 | Description: "check genesis blocks and generateTx and re-generate them if error occurs", 89 | Action: func(context *cli.Context) error { 90 | for _, network := range ngtypes.AvailableNetworks { 91 | b := ngtypes.GetGenesisBlock(network) 92 | jsonBlock, _ := utils.JSON.MarshalToString(b) 93 | fmt.Println(jsonBlock) 94 | } 95 | 96 | return nil 97 | }, 98 | } 99 | 100 | func main() { 101 | app := cli.NewApp() 102 | 103 | app.Name = name 104 | app.Usage = usage 105 | app.Description = description 106 | app.Version = Version 107 | app.Action = nil 108 | app.Commands = []*cli.Command{checkCommand, displayCommand} 109 | 110 | app.Flags = []cli.Flag{filenameFlag, passwordFlag} 111 | 112 | err := app.Run(os.Args) 113 | if err != nil { 114 | panic(err) 115 | } 116 | 117 | os.Exit(0) 118 | } 119 | -------------------------------------------------------------------------------- /cmd/genesistool/utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/rand" 5 | "fmt" 6 | "github.com/ngchain/astrobwt" 7 | "math/big" 8 | "runtime" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | ) 12 | 13 | func genBlockNonce(b *ngtypes.FullBlock) { 14 | diff := new(big.Int).SetBytes(b.BlockHeader.Difficulty) 15 | genesisTarget := new(big.Int).Div(ngtypes.MaxTarget, diff) 16 | fmt.Printf("Genesis block's diff %d, target %x \n", diff, genesisTarget.Bytes()) 17 | 18 | nCh := make(chan []byte, 1) 19 | stopCh := make(chan struct{}, 1) 20 | thread := runtime.NumCPU() 21 | 22 | for i := 0; i < thread; i++ { 23 | go calcHash(b, genesisTarget, nCh, stopCh) 24 | } 25 | 26 | answer := <-nCh 27 | stopCh <- struct{}{} 28 | 29 | fmt.Printf("Genesis Block Nonce Hex: %x \n", answer) 30 | } 31 | 32 | func calcHash(b *ngtypes.FullBlock, target *big.Int, answerCh chan []byte, stopCh chan struct{}) { 33 | // calcHash get the hash of block 34 | 35 | random := make([]byte, ngtypes.NonceSize) 36 | 37 | for { 38 | select { 39 | case <-stopCh: 40 | return 41 | default: 42 | rand.Read(random) 43 | blob := b.GetPoWRawHeader(random) 44 | 45 | hash := astrobwt.POW_0alloc(blob[:]) 46 | if new(big.Int).SetBytes(hash[:]).Cmp(target) < 0 { 47 | answerCh <- random 48 | return 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /cmd/ngcore/cli_tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | 7 | "github.com/c0mm4nd/go-jsonrpc2" 8 | "github.com/c0mm4nd/go-jsonrpc2/jsonrpc2http" 9 | "github.com/urfave/cli/v2" 10 | ) 11 | 12 | func getCliToolsCommand() *cli.Command { 13 | return &cli.Command{ 14 | Name: "cli", 15 | Flags: []cli.Flag{ 16 | &cli.StringFlag{ 17 | Name: "a", 18 | Aliases: []string{"addr"}, 19 | Usage: "the daemon rpc server address", 20 | Value: "http://localhost:52521", 21 | }, 22 | }, 23 | Description: "built-in rpc client", 24 | Subcommands: []*cli.Command{}, 25 | Action: func(context *cli.Context) error { 26 | // create new client 27 | cmd := context.Args().Get(0) 28 | args := context.Args().Get(1) 29 | var params []byte 30 | if args == "" { 31 | params = nil 32 | } else { 33 | params = []byte(args) 34 | } 35 | c := jsonrpc2http.NewClient() 36 | msg := jsonrpc2.NewJsonRpcRequest(1, cmd, params) 37 | request, err := jsonrpc2http.NewClientRequest(context.String("a"), msg) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | response, err := c.Do(request) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | raw, _ := io.ReadAll(response.Body) 48 | fmt.Println(string(raw)) 49 | 50 | return nil 51 | }, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /cmd/ngcore/defaults.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | const ( 4 | // app. 5 | name = "ngcore" 6 | usage = "Brand-new golang daemon implement of ngchain network node" 7 | description = "The ngchain is a radically updating brand-new blockchain network, " + 8 | "which is not a fork of ethereum or any other chain." 9 | 10 | // flag values. 11 | defaultTCPP2PPort = 52520 12 | defaultRPCHost = "127.0.0.1" 13 | defaultRPCPort = 52521 14 | 15 | defaultDBFolder = "ngdb" 16 | ) 17 | 18 | var ( 19 | Commit string // from `git rev-parse HEAD` 20 | Tag string // from `git describe --tags --abbrev=0` 21 | Version string 22 | ) 23 | 24 | func init() { 25 | if Tag == "" && Commit == "" { 26 | panic("invalid version: tag:" + Tag + " commit: " + Commit) 27 | } 28 | if Tag != "" { 29 | Version = Tag 30 | } else { 31 | Version = "v0.0.0-" + Commit 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd/ngcore/key_tools.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/urfave/cli/v2" 7 | 8 | "github.com/ngchain/ngcore/keytools" 9 | ) 10 | 11 | func getKeyToolsCommand() *cli.Command { 12 | filenameFlag := &cli.StringFlag{ 13 | Name: "filename", 14 | Aliases: []string{"f"}, 15 | Value: "", 16 | Usage: "when empty, keyfile will be written into " + keytools.GetDefaultFile(), 17 | } 18 | 19 | passwordFlag := &cli.StringFlag{ 20 | Name: "password", 21 | Aliases: []string{"p"}, 22 | } 23 | 24 | privateKeyFlag := &cli.StringFlag{ 25 | Name: "privateKey", 26 | Aliases: []string{"pk"}, 27 | } 28 | 29 | newKeyCommand := &cli.Command{ 30 | Name: "new", 31 | Usage: "create a new key pair only", 32 | Flags: nil, 33 | Action: func(context *cli.Context) error { 34 | key := keytools.NewLocalKey() 35 | keytools.PrintKeysAndAddress(key) 36 | return nil 37 | }, 38 | } 39 | 40 | createKeyCommand := &cli.Command{ 41 | Name: "create", 42 | Usage: "create a new key pair and save into the key file", 43 | Flags: []cli.Flag{filenameFlag, passwordFlag}, 44 | Action: func(ctx *cli.Context) error { 45 | key := keytools.CreateLocalKey(strings.TrimSpace(ctx.String("filename")), strings.TrimSpace(ctx.String("password"))) 46 | keytools.PrintKeysAndAddress(key) 47 | return nil 48 | }, 49 | } 50 | 51 | recoverKeyCommand := &cli.Command{ 52 | Name: "recover", 53 | Usage: "recover the keyfile from a privateKey string", 54 | Flags: []cli.Flag{filenameFlag, passwordFlag, privateKeyFlag}, 55 | Action: func(ctx *cli.Context) error { 56 | key := keytools.RecoverLocalKey( 57 | strings.TrimSpace(ctx.String("filename")), 58 | strings.TrimSpace(ctx.String("password")), 59 | strings.TrimSpace(ctx.String("privateKey")), 60 | ) 61 | keytools.PrintKeysAndAddress(key) 62 | return nil 63 | }, 64 | } 65 | 66 | parseKeyCommand := &cli.Command{ 67 | Name: "parse", 68 | Usage: "parse keys from a key file", 69 | Flags: []cli.Flag{filenameFlag, passwordFlag}, 70 | Action: func(ctx *cli.Context) error { 71 | key := keytools.ReadLocalKey(strings.TrimSpace(ctx.String("filename")), strings.TrimSpace(ctx.String("password"))) 72 | keytools.PrintKeysAndAddress(key) 73 | return nil 74 | }, 75 | } 76 | 77 | return &cli.Command{ 78 | Name: "keytools", 79 | Description: "a little built-in key tools for the key file", 80 | Subcommands: []*cli.Command{newKeyCommand, parseKeyCommand, createKeyCommand, recoverKeyCommand}, 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cmd/ngcore/main.go: -------------------------------------------------------------------------------- 1 | // package main is the entry of daemon 2 | package main 3 | 4 | import ( 5 | "os" 6 | 7 | "github.com/urfave/cli/v2" 8 | ) 9 | 10 | func main() { 11 | app := cli.NewApp() 12 | 13 | app.Name = name 14 | app.Usage = usage 15 | app.Description = description 16 | app.Version = Version 17 | app.Action = action 18 | app.Flags = []cli.Flag{ 19 | nonStrictModeFlag, snapshotModeFlag, 20 | p2pTCPPortFlag, p2pKeyFileFlag, 21 | rpcHostFlag, rpcPortFlag, rpcDisableFlag, 22 | isBootstrapFlag, profileFlag, 23 | 24 | inMemFlag, dbFolderFlag, 25 | 26 | testNetFlag, zeroNetFlag, 27 | } 28 | 29 | app.Commands = []*cli.Command{ 30 | getKeyToolsCommand(), 31 | getCliToolsCommand(), 32 | } 33 | 34 | err := app.Run(os.Args) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | os.Exit(0) 40 | } 41 | -------------------------------------------------------------------------------- /consensus/doc.go: -------------------------------------------------------------------------------- 1 | // Package consensus implements the PoWork consensus 2 | package consensus 3 | -------------------------------------------------------------------------------- /consensus/generate_tx_builder.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/ngchain/secp256k1" 7 | 8 | "github.com/ngchain/ngcore/ngtypes" 9 | ) 10 | 11 | // CreateGenerateTx will create a generate Tx for new Block. 12 | // generate Tx is disallowed to edit external so use more local var. 13 | func CreateGenerateTx(network ngtypes.Network, privateKey *secp256k1.PrivateKey, height uint64, extraData []byte) *ngtypes.FullTx { 14 | addr := ngtypes.NewAddress(privateKey) 15 | fee := big.NewInt(0) 16 | gen := ngtypes.NewUnsignedTx( 17 | network, 18 | ngtypes.GenerateTx, 19 | height, 20 | 0, 21 | []ngtypes.Address{addr}, 22 | []*big.Int{ngtypes.GetBlockReward(height)}, 23 | fee, 24 | extraData, 25 | ) 26 | 27 | err := gen.Signature(privateKey) 28 | if err != nil { 29 | log.Error(err) 30 | } 31 | 32 | return gen 33 | } 34 | -------------------------------------------------------------------------------- /consensus/pow_test.go: -------------------------------------------------------------------------------- 1 | package consensus_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ngchain/ngcore/blockchain" 7 | "github.com/ngchain/ngcore/consensus" 8 | "github.com/ngchain/ngcore/ngblocks" 9 | "github.com/ngchain/ngcore/ngp2p" 10 | "github.com/ngchain/ngcore/ngpool" 11 | "github.com/ngchain/ngcore/ngstate" 12 | "github.com/ngchain/ngcore/ngtypes" 13 | "github.com/ngchain/ngcore/storage" 14 | ) 15 | 16 | func TestNewConsensusManager(t *testing.T) { 17 | db := storage.InitTempStorage() 18 | 19 | defer func() { 20 | err := db.Close() 21 | if err != nil { 22 | panic(err) 23 | } 24 | }() 25 | 26 | net := ngtypes.ZERONET 27 | store := ngblocks.Init(db, net) 28 | state := ngstate.InitStateFromGenesis(db, net) 29 | chain := blockchain.Init(db, net, store, nil) 30 | 31 | localNode := ngp2p.InitLocalNode(chain, ngp2p.P2PConfig{ 32 | Network: net, 33 | Port: 52520, 34 | DisableDiscovery: true, 35 | }) 36 | pool := ngpool.Init(db, chain, localNode) 37 | 38 | consensus.InitPoWConsensus(db, chain, pool, state, localNode, consensus.PoWorkConfig{ 39 | Network: net, 40 | DisableConnectingBootstraps: true, 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /consensus/report.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "github.com/ngchain/ngcore/ngtypes" 5 | "runtime" 6 | "time" 7 | ) 8 | 9 | func (pow *PoWork) reportLoop() { 10 | interval := time.NewTicker(time.Minute) 11 | for { 12 | <-interval.C 13 | latestBlock := pow.Chain.GetLatestBlock().(*ngtypes.FullBlock) 14 | log.Warnf("local latest block@%d: %x", latestBlock.GetHeight(), latestBlock.GetHash()) 15 | runtime.Gosched() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /consensus/sync.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/ngchain/ngcore/ngtypes" 7 | 8 | "github.com/libp2p/go-libp2p/core/peer" 9 | 10 | "github.com/ngchain/ngcore/ngp2p" 11 | "github.com/ngchain/ngcore/utils" 12 | ) 13 | 14 | const ( 15 | minDesiredPeerCount = 3 // TODO: add peer num requirement, avoid mining alone 16 | ) 17 | 18 | // syncModule is a submodule to the pow, managing the sync of blocks. 19 | type syncModule struct { 20 | pow *PoWork 21 | 22 | localNode *ngp2p.LocalNode 23 | 24 | storeMu sync.RWMutex 25 | store map[peer.ID]*RemoteRecord 26 | 27 | *utils.Locker 28 | } 29 | 30 | // newSyncModule creates a new sync module. 31 | func newSyncModule(c ngtypes.Consensus, localNode *ngp2p.LocalNode) *syncModule { 32 | pow := c.(*PoWork) 33 | syncMod := &syncModule{ 34 | pow: pow, 35 | localNode: localNode, 36 | storeMu: sync.RWMutex{}, 37 | store: make(map[peer.ID]*RemoteRecord), 38 | 39 | Locker: utils.NewLocker(), 40 | } 41 | 42 | latest := pow.Chain.GetLatestBlock() 43 | log.Warnf("current latest block: %x@%d", latest.GetHash(), latest.GetHeight()) 44 | 45 | return syncMod 46 | } 47 | 48 | // put the peer and its remote status into mod. 49 | func (mod *syncModule) putRemote(id peer.ID, remote *RemoteRecord) { 50 | mod.storeMu.Lock() 51 | defer mod.storeMu.Unlock() 52 | mod.store[id] = remote 53 | } 54 | -------------------------------------------------------------------------------- /consensus/sync_bootstrap.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "sync" 7 | 8 | "github.com/libp2p/go-libp2p/core/peer" 9 | ) 10 | 11 | func (mod *syncModule) bootstrap() { 12 | log.Warn("bootstrapping ... ") 13 | peerStore := mod.localNode.Peerstore() 14 | 15 | // init the store 16 | var wg sync.WaitGroup 17 | peers := peerStore.Peers() 18 | localID := mod.localNode.ID() 19 | for _, id := range peers { 20 | wg.Add(1) 21 | go func(id peer.ID) { 22 | p, _ := mod.localNode.Peerstore().FirstSupportedProtocol(id, mod.localNode.GetWiredProtocol()) 23 | if p == mod.localNode.GetWiredProtocol() && id != localID { 24 | err := mod.getRemoteStatus(id) 25 | if err != nil { 26 | log.Debug(err) 27 | } 28 | } 29 | wg.Done() 30 | }(id) 31 | } 32 | wg.Wait() 33 | 34 | peerNum := len(mod.store) 35 | if peerNum < minDesiredPeerCount { 36 | log.Warnf("lack remote peer for bootstrapping, current peer num: %d", peerNum) 37 | // TODO: when peer count is less than the minDesiredPeerCount, the consensus shouldn't do any sync nor converge 38 | } 39 | 40 | slice := make([]*RemoteRecord, len(mod.store)) 41 | i := 0 42 | 43 | for _, v := range mod.store { 44 | slice[i] = v 45 | i++ 46 | } 47 | 48 | sort.SliceStable(slice, func(i, j int) bool { 49 | return slice[i].lastChatTime > slice[j].lastChatTime 50 | }) 51 | sort.SliceStable(slice, func(i, j int) bool { 52 | return slice[i].latest > slice[j].latest 53 | }) 54 | 55 | // catch error 56 | var err error 57 | if records := mod.MustSync(slice); len(records) != 0 { 58 | for _, record := range records { 59 | if !mod.pow.StrictMode && mod.pow.Chain.GetLatestBlockHeight() == 0 { 60 | // 61 | err = mod.switchToRemoteCheckpoint(record) 62 | if err != nil { 63 | panic(fmt.Errorf("failed to fast sync via checkpoint: %w", err)) 64 | } 65 | } 66 | 67 | if mod.pow.SnapshotMode { 68 | err = mod.doSnapshotSync(record) 69 | } else { 70 | err = mod.doSync(record) 71 | } 72 | 73 | if err != nil { 74 | log.Warnf("do sync failed: %s, maybe require converging", err) 75 | } else { 76 | break 77 | } 78 | } 79 | } 80 | 81 | // do converge check after sync check 82 | if records := mod.MustConverge(slice); len(records) != 0 { 83 | for _, record := range records { 84 | if mod.pow.SnapshotMode { 85 | err = mod.doSnapshotConverging(record) 86 | } else { 87 | err = mod.doConverging(record) 88 | } 89 | 90 | if err != nil { 91 | log.Errorf("converging failed: %s", err) 92 | record.recordFailure() 93 | } else { 94 | break 95 | } 96 | } 97 | } 98 | if err != nil { 99 | panic(err) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /consensus/sync_converge.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | 7 | "github.com/ngchain/ngcore/ngp2p/defaults" 8 | "github.com/ngchain/ngcore/ngtypes" 9 | "github.com/ngchain/ngcore/utils" 10 | ) 11 | 12 | // MustConverge detection ignites the forking in local node 13 | // then do a filter covering all remotes to get the longest chain (if length is same, choose the heavier latest block one). 14 | func (mod *syncModule) MustConverge(slice []*RemoteRecord) []*RemoteRecord { 15 | ret := make([]*RemoteRecord, 0) 16 | latestHeight := mod.pow.Chain.GetLatestBlockHeight() 17 | latestCheckPoint := mod.pow.Chain.GetLatestCheckpoint() 18 | 19 | for _, r := range slice { 20 | if r.shouldConverge(latestCheckPoint, latestHeight) { 21 | ret = append(ret, r) 22 | } 23 | } 24 | 25 | return ret 26 | } 27 | 28 | // force local chain be same as the remote record 29 | // converge is a danger operation so all msg are warn level. 30 | func (mod *syncModule) doConverging(record *RemoteRecord) error { 31 | if mod.Locker.IsLocked() { 32 | return nil 33 | } 34 | 35 | mod.Locker.Lock() 36 | defer mod.Locker.Unlock() 37 | 38 | log.Warnf("start converging chain from remote node %s, target height: %d", record.id, record.latest) 39 | chain, err := mod.getBlocksForConverging(record) 40 | if err != nil { 41 | return fmt.Errorf("failed to get blocks for converging: %w", err) 42 | } 43 | 44 | // localSamepoint, _ := mod.pow.Chain.GetBlockByHeight(chain[1].Height) 45 | // log.Warnf("have got the diffpoint: block@%d: local: %x remote %x", chain[1].Height, chain[1].Hash(), localSamepoint.Hash()) 46 | 47 | err = mod.pow.Chain.ForceApplyBlocks(chain) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | // RULE: there are 3 choices 53 | // 1. regenerate the state(time-consuming) 54 | // 2. download the state from remote(maybe unreliable) 55 | // 3. flash back(require remove destroy and assign tx) 56 | // Currently choose the No.1 57 | log.Warnf("regenerateing local state") 58 | err = mod.pow.State.RebuildFromBlockStore() 59 | if err != nil { 60 | return err 61 | } 62 | 63 | log.Warn("converging finished") 64 | return nil 65 | } 66 | 67 | // getBlocksForConverging gets the blocks since the diffpoint (inclusive) by comparing hashes between local and remote. 68 | func (mod *syncModule) getBlocksForConverging(record *RemoteRecord) ([]*ngtypes.FullBlock, error) { 69 | blocks := make([]*ngtypes.FullBlock, 0) 70 | 71 | localHeight := mod.pow.Chain.GetLatestBlockHeight() 72 | localOriginHeight := mod.pow.Chain.GetOriginBlock().GetHeight() 73 | 74 | ptr := localHeight 75 | 76 | // when the chainLen (the len of returned chain) is not equal to defaults.MaxBlocks, means it has reach the latest height 77 | for { 78 | if ptr <= localOriginHeight { 79 | panic("converging failed: completely different chains!") 80 | } 81 | 82 | blockHashes := make([][]byte, 0, defaults.MaxBlocks) 83 | to := ptr 84 | roundHashes := utils.MinUint64(defaults.MaxBlocks, ptr-localOriginHeight) 85 | ptr -= roundHashes 86 | from := ptr + 1 87 | 88 | // get local hashes as params 89 | for h := from; h <= to; h++ { 90 | b, err := mod.pow.Chain.GetBlockByHeight(h) 91 | if err != nil { 92 | // when gap is too large 93 | return nil, err 94 | } 95 | 96 | blockHashes = append(blockHashes, b.GetHash()) 97 | } 98 | 99 | // To == from+to means converging mode 100 | chain, err := mod.getRemoteChain(record.id, blockHashes, bytes.Join([][]byte{utils.PackUint64LE(from), utils.PackUint64LE(to)}, nil)) 101 | if err != nil { 102 | return nil, err 103 | } 104 | if chain == nil { 105 | // chain == nil means all hashes are matched 106 | localChain := make([]*ngtypes.FullBlock, 0, defaults.MaxBlocks) 107 | for i := range blockHashes { 108 | block, err := mod.pow.Chain.GetBlockByHash(blockHashes[i]) 109 | if err != nil { 110 | return nil, fmt.Errorf("failed on constructing local chain: %w", err) 111 | } 112 | localChain = append(localChain, block.(*ngtypes.FullBlock)) 113 | } 114 | 115 | blocks = append(localChain, blocks...) 116 | } else { 117 | blocks = append(chain, blocks...) 118 | break 119 | } 120 | } 121 | 122 | return blocks, nil 123 | } 124 | -------------------------------------------------------------------------------- /consensus/sync_fast.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "encoding/binary" 5 | "fmt" 6 | 7 | "github.com/ngchain/ngcore/ngp2p/wired" 8 | "github.com/ngchain/ngcore/ngtypes" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // convert local origin to remote checkpoint. 13 | func (mod *syncModule) switchToRemoteCheckpoint(record *RemoteRecord) error { 14 | if mod.Locker.IsLocked() { 15 | return nil 16 | } 17 | 18 | mod.Locker.Lock() 19 | defer mod.Locker.Unlock() 20 | 21 | log.Warnf("start syncing with remote node %s, target height %d", record.id, record.latest) 22 | 23 | // get chain 24 | remoteCheckpoint, err := mod.getRemoteCheckpoint(record) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | err = mod.pow.Chain.InitFromCheckpoint(remoteCheckpoint) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | return nil 35 | } 36 | 37 | func (mod *syncModule) getRemoteCheckpoint(record *RemoteRecord) (*ngtypes.FullBlock, error) { 38 | to := make([]byte, 16) 39 | 40 | if record.checkpointHeight <= 2*ngtypes.BlockCheckRound { 41 | return ngtypes.GetGenesisBlock(mod.pow.Network), nil 42 | } 43 | 44 | checkpointHeight := record.checkpointHeight - 2*ngtypes.BlockCheckRound 45 | binary.LittleEndian.PutUint64(to[0:], checkpointHeight) 46 | binary.LittleEndian.PutUint64(to[8:], checkpointHeight) 47 | id, s, err := mod.localNode.SendGetChain(record.id, nil, to) // nil means get MaxBlocks number blocks 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | reply, err := wired.ReceiveReply(id, s) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | switch reply.Header.Type { 58 | case wired.ChainMsg: 59 | chainPayload, err := wired.DecodeChainPayload(reply.Payload) 60 | if err != nil { 61 | return nil, fmt.Errorf("failed to send ping: %w", err) 62 | } 63 | 64 | if len(chainPayload.Blocks) != 1 { 65 | log.Debugf("%#v", chainPayload.Blocks) 66 | return nil, errors.Wrapf(wired.ErrMsgPayloadInvalid, 67 | "invalid blocks payload length: should be 1 but got %d", len(chainPayload.Blocks)) 68 | } 69 | 70 | // checkpoint := chainPayload.Blocks[0] 71 | // if !bytes.Equal(checkpoint.Hash(), record.checkpointHash) { 72 | // return nil, fmt.Errorf("invalid checkpoint: should be %x, but got %x", record.checkpointHash, checkpoint.Hash()) 73 | // } 74 | 75 | return chainPayload.Blocks[0], err 76 | 77 | case wired.RejectMsg: 78 | return nil, errors.Wrapf(ErrMsgRejected, "getchain is rejected by remote by reason: %s", string(reply.Payload)) 79 | 80 | default: 81 | return nil, errors.Wrapf(wired.ErrMsgTypeInvalid, "remote replies ping with invalid messgae type: %s", reply.Header.Type) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /consensus/sync_loop.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "sort" 5 | "time" 6 | ) 7 | 8 | // main loop of sync module. 9 | func (mod *syncModule) loop() { 10 | ticker := time.NewTicker(10 * time.Second) 11 | 12 | for { 13 | <-ticker.C 14 | log.Infof("checking sync status") 15 | 16 | // do get status 17 | for _, id := range mod.localNode.Peerstore().Peers() { 18 | p, _ := mod.localNode.Peerstore().FirstSupportedProtocol(id, mod.localNode.GetWiredProtocol()) 19 | if p == mod.localNode.GetWiredProtocol() && id != mod.localNode.ID() { 20 | err := mod.getRemoteStatus(id) 21 | if err != nil { 22 | log.Warnf("failed to get remote status from %s: %s", id, err) 23 | } 24 | } 25 | } 26 | 27 | // do sync check takes the priority 28 | // convert map to slice first 29 | slice := make([]*RemoteRecord, len(mod.store)) 30 | i := 0 31 | 32 | for _, v := range mod.store { 33 | slice[i] = v 34 | i++ 35 | } 36 | 37 | sort.SliceStable(slice, func(i, j int) bool { 38 | return slice[i].lastChatTime > slice[j].lastChatTime 39 | }) 40 | sort.SliceStable(slice, func(i, j int) bool { 41 | return slice[i].latest > slice[j].latest 42 | }) 43 | 44 | var err error 45 | if records := mod.MustSync(slice); len(records) != 0 { 46 | for _, record := range records { 47 | if mod.pow.SnapshotMode { 48 | err = mod.doSnapshotSync(record) 49 | } else { 50 | err = mod.doSync(record) 51 | } 52 | 53 | if err != nil { 54 | log.Warnf("do sync failed: %s, maybe require converging", err) 55 | } else { 56 | break 57 | } 58 | } 59 | } 60 | if err == nil { 61 | continue 62 | } 63 | 64 | if records := mod.MustConverge(slice); len(records) != 0 { 65 | for _, record := range records { 66 | if mod.pow.SnapshotMode { 67 | err = mod.doSnapshotConverging(record) 68 | } else { 69 | err = mod.doConverging(record) 70 | } 71 | 72 | if err != nil { 73 | log.Errorf("converging failed: %s", err) 74 | record.recordFailure() 75 | } else { 76 | break 77 | } 78 | } 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /consensus/sync_normal.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import "github.com/pkg/errors" 4 | 5 | // MustSync will start a sync and stop until reaching the latest height 6 | // RULE: checkpoint converging: when a node mined a checkpoint, all other node are forced to start sync. 7 | func (mod *syncModule) MustSync(slice []*RemoteRecord) []*RemoteRecord { 8 | ret := make([]*RemoteRecord, 0) 9 | latestHeight := mod.pow.Chain.GetLatestBlockHeight() 10 | 11 | for _, r := range slice { 12 | if r.shouldSync(latestHeight) { 13 | ret = append(ret, r) 14 | } 15 | } 16 | 17 | return ret 18 | } 19 | 20 | func (mod *syncModule) doSync(record *RemoteRecord) error { 21 | if mod.Locker.IsLocked() { 22 | return nil 23 | } 24 | 25 | mod.Locker.Lock() 26 | defer mod.Locker.Unlock() 27 | 28 | log.Warnf("start syncing with remote node %s, target height %d", record.id, record.latest) 29 | 30 | // get chain 31 | for mod.pow.Chain.GetLatestBlockHeight() < record.latest { 32 | chain, err := mod.getRemoteChainFromLocalLatest(record) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | for i := 0; i < len(chain); i++ { 38 | err = mod.pow.Chain.ApplyBlock(chain[i]) 39 | if err != nil { 40 | return errors.Wrapf(err, "failed on applying block@%d", chain[i].GetHeight()) 41 | } 42 | } 43 | } 44 | 45 | height := mod.pow.Chain.GetLatestBlockHeight() 46 | log.Warnf("sync finished with remote node %s, local height %d", record.id, height) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /consensus/sync_remote_record.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import ( 4 | "bytes" 5 | "math/big" 6 | "time" 7 | 8 | "github.com/libp2p/go-libp2p/core/peer" 9 | "go.uber.org/atomic" 10 | 11 | "github.com/ngchain/ngcore/ngtypes" 12 | ) 13 | 14 | type RemoteRecord struct { 15 | id peer.ID 16 | origin uint64 // rank 17 | latest uint64 18 | 19 | checkpointHeight uint64 20 | checkpointHash []byte // trigger 21 | checkpointActualDiff *big.Int // rank 22 | lastChatTime int64 23 | 24 | failureNum *atomic.Uint32 25 | lastFailedTime int64 26 | } 27 | 28 | func NewRemoteRecord(id peer.ID, origin, latest uint64, checkpointHash, checkpointActualDiff []byte) *RemoteRecord { 29 | var checkpointHeight uint64 30 | if latest%ngtypes.BlockCheckRound == 0 { 31 | checkpointHeight = latest 32 | } else { 33 | checkpointHeight = latest - latest%ngtypes.BlockCheckRound 34 | } 35 | 36 | return &RemoteRecord{ 37 | id: id, 38 | origin: origin, 39 | latest: latest, 40 | checkpointHeight: checkpointHeight, 41 | checkpointHash: checkpointHash, 42 | checkpointActualDiff: new(big.Int).SetBytes(checkpointActualDiff), 43 | lastChatTime: time.Now().Unix(), 44 | failureNum: atomic.NewUint32(0), 45 | lastFailedTime: 0, 46 | } 47 | } 48 | 49 | func (r *RemoteRecord) update(origin, latest uint64, checkpointHash, checkpointActualDiff []byte) { 50 | r.origin = origin 51 | r.latest = latest 52 | r.checkpointHeight = latest - latest%ngtypes.BlockCheckRound 53 | r.checkpointHash = checkpointHash 54 | r.checkpointActualDiff = new(big.Int).SetBytes(checkpointActualDiff) 55 | r.lastChatTime = time.Now().Unix() 56 | } 57 | 58 | func (r *RemoteRecord) shouldSync(latestHeight uint64) bool { 59 | if time.Now().Unix() < r.lastFailedTime+int64(60*60) { 60 | return false 61 | } 62 | 63 | if r.latest/ngtypes.BlockCheckRound <= latestHeight/ngtypes.BlockCheckRound { 64 | return false 65 | } 66 | 67 | return true 68 | } 69 | 70 | // RULE: when converging? 71 | // Situation #1: remote height is higher than local, AND checkpoint is on higher level 72 | // Situation #2: remote height is higher than local, AND checkpoint is on same level, AND remote checkpoint takes more rank (with more ActualDiff) 73 | // TODO: add a cap for converging. 74 | func (r *RemoteRecord) shouldConverge(latestCheckPoint *ngtypes.FullBlock, latestHeight uint64) bool { 75 | if time.Now().Unix() < r.lastFailedTime+int64(60*60) { 76 | return false 77 | } 78 | 79 | cpHash := latestCheckPoint.GetHash() 80 | 81 | if !bytes.Equal(r.checkpointHash, cpHash) && 82 | r.latest > latestHeight && 83 | r.latest/ngtypes.BlockCheckRound > latestHeight/ngtypes.BlockCheckRound { 84 | return true 85 | } 86 | 87 | if !bytes.Equal(r.checkpointHash, cpHash) && 88 | r.latest > latestHeight && 89 | r.latest/ngtypes.BlockCheckRound == latestHeight/ngtypes.BlockCheckRound && 90 | r.checkpointActualDiff != nil && 91 | r.checkpointActualDiff.Cmp(latestCheckPoint.GetActualDiff()) > 0 { 92 | return true 93 | } 94 | 95 | return false 96 | } 97 | 98 | func (r *RemoteRecord) recordFailure() { 99 | r.failureNum.Inc() 100 | if r.failureNum.Load() > 3 { 101 | r.lastFailedTime = time.Now().Unix() 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /consensus/sync_snapshot.go: -------------------------------------------------------------------------------- 1 | package consensus 2 | 3 | import "github.com/pkg/errors" 4 | 5 | // online available for initial sync 6 | // TODO: init from a checkpoint, not genesisblock. 7 | func (mod *syncModule) doSnapshotSync(record *RemoteRecord) error { 8 | if mod.Locker.IsLocked() { 9 | return nil 10 | } 11 | 12 | mod.Locker.Lock() 13 | defer mod.Locker.Unlock() 14 | 15 | log.Warnf("start snapshot syncing with remote node %s, target height %d", record.id, record.latest) 16 | 17 | for mod.pow.Chain.GetLatestBlockHeight() < record.checkpointHeight { 18 | chain, err := mod.getRemoteChainFromLocalLatest(record) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | err = mod.pow.Chain.ForceApplyBlocks(chain) 24 | if err != nil { 25 | return err 26 | } 27 | } 28 | 29 | sheet, err := mod.getRemoteStateSheet(record) 30 | if err != nil { 31 | return err 32 | } 33 | err = mod.pow.State.RebuildFromSheet(sheet) 34 | if err != nil { 35 | return errors.Wrapf(err, "failed on rebuilding state with sheet %x", sheet.BlockHash) 36 | } 37 | 38 | height := mod.pow.Chain.GetLatestBlockHeight() 39 | log.Warnf("snapshot sync finished with remote node %s, local height %d", record.id, height) 40 | 41 | return nil 42 | } 43 | 44 | func (mod *syncModule) doSnapshotConverging(record *RemoteRecord) error { 45 | if mod.Locker.IsLocked() { 46 | return nil 47 | } 48 | 49 | mod.Locker.Lock() 50 | defer mod.Locker.Unlock() 51 | 52 | log.Warnf("start converging chain from remote node %s, target height: %d", record.id, record.latest) 53 | chain, err := mod.getBlocksForConverging(record) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | localSamepoint, _ := mod.pow.Chain.GetBlockByHeight(chain[1].GetHeight()) 59 | log.Warnf("have got the diffpoint: block@%d: local: %x remote %x", chain[1].GetHeight(), chain[1].GetHash(), localSamepoint.GetHash()) 60 | 61 | err = mod.pow.Chain.ForceApplyBlocks(chain) 62 | if err != nil { 63 | return err 64 | } 65 | 66 | // RULE: there are 3 choices 67 | // 1. regenerate the state(time-consuming) 68 | // 2. download the state from remote(maybe unreliable) 69 | // 3. flash back(require remove destroy and assign tx) 70 | // Currently choose the No.1 71 | sheet, err := mod.getRemoteStateSheet(record) 72 | if err != nil { 73 | return err 74 | } 75 | log.Warnf("regenerateing local state") 76 | err = mod.pow.State.RebuildFromSheet(sheet) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | log.Warn("converging finished") 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /jsonrpc/doc.go: -------------------------------------------------------------------------------- 1 | // Package jsonrpc is the json-rpc2 module in ngcore 2 | // 3 | // All commands/methods should follow these rules: 4 | // - All (private or public) keys are encoded with base58 5 | // - All bytes are encoded in base64 (forced by protobuf) 6 | // - All numbers are float64, coin uint is NG. So when generating tx, its necessary to multiply the values/fee with 1000000 to make unit be MicroNG 7 | // - All protobuf's bytes are in base64 8 | package jsonrpc 9 | -------------------------------------------------------------------------------- /jsonrpc/main.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/c0mm4nd/go-jsonrpc2/jsonrpc2http" 7 | logging "github.com/ngchain/zap-log" 8 | 9 | "github.com/ngchain/ngcore/consensus" 10 | ) 11 | 12 | var log = logging.Logger("rpc") 13 | 14 | type ServerConfig struct { 15 | Host string 16 | Port int 17 | DisableP2PMethods bool 18 | DisableMiningMethods bool 19 | } 20 | 21 | // Server is a json-rpc v2 server. 22 | type Server struct { 23 | *ServerConfig 24 | *jsonrpc2http.Server 25 | 26 | pow *consensus.PoWork 27 | } 28 | 29 | // NewServer will create a new Server, with registered *jsonrpc2http.HTTPHandler. But not running. 30 | func NewServer(pow *consensus.PoWork, config ServerConfig) *Server { 31 | s := &Server{ 32 | ServerConfig: &config, 33 | Server: nil, 34 | 35 | pow: pow, 36 | } 37 | 38 | s.Server = jsonrpc2http.NewServer(jsonrpc2http.ServerConfig{ 39 | Addr: fmt.Sprintf("%s:%d", config.Host, config.Port), 40 | Handler: nil, 41 | Logger: log, 42 | }) 43 | 44 | registerHTTPHandler(s) 45 | 46 | return s 47 | } 48 | 49 | // Serve will make the server running. 50 | func (s *Server) Serve() { 51 | log.Warnf("JSON RPC listening on: %s \n", s.Addr) 52 | err := s.ListenAndServe() 53 | if err != nil { 54 | panic(err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /jsonrpc/main_test.go: -------------------------------------------------------------------------------- 1 | package jsonrpc_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/ngchain/ngcore/blockchain" 8 | "github.com/ngchain/ngcore/consensus" 9 | "github.com/ngchain/ngcore/jsonrpc" 10 | "github.com/ngchain/ngcore/ngblocks" 11 | "github.com/ngchain/ngcore/ngp2p" 12 | "github.com/ngchain/ngcore/ngpool" 13 | "github.com/ngchain/ngcore/ngstate" 14 | "github.com/ngchain/ngcore/ngtypes" 15 | "github.com/ngchain/ngcore/storage" 16 | ) 17 | 18 | // TODO: add tests for each method rather than testing the server. 19 | func TestNewRPCServer(t *testing.T) { 20 | network := ngtypes.ZERONET 21 | 22 | db := storage.InitTempStorage() 23 | defer func() { 24 | err := db.Close() 25 | if err != nil { 26 | panic(err) 27 | } 28 | }() 29 | 30 | store := ngblocks.Init(db, network) 31 | state := ngstate.InitStateFromGenesis(db, network) 32 | 33 | chain := blockchain.Init(db, network, store, state) 34 | chain.CheckHealth(network) 35 | 36 | localNode := ngp2p.InitLocalNode(chain, ngp2p.P2PConfig{ 37 | P2PKeyFile: "p2p.key", 38 | Network: network, 39 | Port: 52520, 40 | DisableDiscovery: true, 41 | }) 42 | localNode.GoServe() 43 | 44 | pool := ngpool.Init(db, chain, localNode) 45 | 46 | pow := consensus.InitPoWConsensus( 47 | db, 48 | chain, 49 | pool, 50 | state, 51 | localNode, 52 | consensus.PoWorkConfig{ 53 | Network: network, 54 | DisableConnectingBootstraps: true, 55 | }, 56 | ) 57 | pow.GoLoop() 58 | 59 | rpc := jsonrpc.NewServer(pow, jsonrpc.ServerConfig{ 60 | Host: "", 61 | Port: 52521, 62 | DisableP2PMethods: false, 63 | DisableMiningMethods: false, 64 | }) 65 | go rpc.Serve() 66 | 67 | time.Sleep(2 * time.Minute) 68 | } 69 | -------------------------------------------------------------------------------- /jsonrpc/mining.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "encoding/hex" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/c0mm4nd/go-jsonrpc2" 9 | 10 | "github.com/ngchain/ngcore/jsonrpc/workpool" 11 | "github.com/ngchain/ngcore/ngtypes" 12 | "github.com/ngchain/ngcore/utils" 13 | ) 14 | 15 | // type GetWorkParams struct { 16 | // PrivateKey string `json:"private_key"` 17 | // } 18 | 19 | var workPool = workpool.GetWorkerPool() 20 | 21 | type GetWorkReply struct { 22 | WorkID uint64 `json:"id"` 23 | Block string `json:"block"` 24 | Txs string `json:"txs"` 25 | } 26 | 27 | // getWorkFunc provides a free style interface for miner client getting latest block mining work 28 | func (s *Server) getWorkFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 29 | block, txs := s.pow.GetBareBlockTemplateWithTxs() 30 | id := uint64(time.Now().UnixNano()) 31 | reply := &GetWorkReply{ 32 | WorkID: id, 33 | Block: utils.HexRLPEncode(block), 34 | Txs: utils.HexRLPEncode(txs), 35 | } 36 | 37 | workPool.Put(strconv.FormatUint(reply.WorkID, 10), reply) 38 | 39 | raw, err := utils.JSON.Marshal(reply) 40 | if err != nil { 41 | log.Error(err) 42 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 43 | } 44 | 45 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, raw) 46 | } 47 | 48 | type SubmitWorkParams struct { 49 | WorkID uint64 `json:"id"` 50 | Nonce string `json:"nonce"` 51 | GenTx string `json:"gen"` 52 | } 53 | 54 | // submitWorkFunc 55 | func (s *Server) submitWorkFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 56 | var params SubmitWorkParams 57 | 58 | err := utils.JSON.Unmarshal(*msg.Params, ¶ms) 59 | if err != nil { 60 | log.Error(err) 61 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 62 | } 63 | 64 | originReply, err := workPool.Get(strconv.FormatUint(params.WorkID, 10)) 65 | if err != nil { 66 | log.Error(err) 67 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 68 | } 69 | 70 | reply := originReply.(*GetWorkReply) 71 | 72 | nonce, err := hex.DecodeString(params.Nonce) 73 | if err != nil { 74 | log.Error(err) 75 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 76 | } 77 | 78 | var genTx ngtypes.FullTx 79 | err = utils.HexRLPDecode(params.GenTx, &genTx) 80 | if err != nil { 81 | log.Error(err) 82 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 83 | } 84 | 85 | var block ngtypes.FullBlock 86 | var txs []*ngtypes.FullTx 87 | 88 | err = utils.HexRLPDecode(reply.Block, &block) 89 | if err != nil { 90 | log.Error(err) 91 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 92 | } 93 | err = utils.HexRLPDecode(reply.Txs, &txs) 94 | if err != nil { 95 | log.Error(err) 96 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 97 | } 98 | 99 | err = block.ToUnsealing(append([]*ngtypes.FullTx{&genTx}, txs...)) 100 | if err != nil { 101 | log.Error(err) 102 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 103 | } 104 | err = block.ToSealed(nonce) 105 | if err != nil { 106 | log.Error(err) 107 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 108 | } 109 | 110 | err = s.pow.MinedNewBlock(&block) 111 | if err != nil { 112 | log.Error(err) 113 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 114 | } 115 | 116 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, nil) 117 | } 118 | -------------------------------------------------------------------------------- /jsonrpc/p2p.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/c0mm4nd/go-jsonrpc2" 7 | "github.com/libp2p/go-libp2p/core/peer" 8 | "github.com/multiformats/go-multiaddr" 9 | 10 | "github.com/ngchain/ngcore/utils" 11 | ) 12 | 13 | type addPeerParams struct { 14 | PeerMultiAddr string `json:"peerMultiAddr"` 15 | } 16 | 17 | func (s *Server) addPeerFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 18 | var params addPeerParams 19 | 20 | err := utils.JSON.Unmarshal(*msg.Params, ¶ms) 21 | if err != nil { 22 | log.Error(err) 23 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 24 | } 25 | 26 | targetAddr, err := multiaddr.NewMultiaddr(params.PeerMultiAddr) 27 | if err != nil { 28 | log.Error(err) 29 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 30 | } 31 | 32 | targetInfo, err := peer.AddrInfoFromP2pAddr(targetAddr) 33 | if err != nil { 34 | log.Error(err) 35 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 36 | } 37 | 38 | err = s.pow.LocalNode.Connect(context.Background(), *targetInfo) 39 | if err != nil { 40 | log.Error(err) 41 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 42 | } 43 | 44 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, nil) 45 | } 46 | 47 | func (s *Server) getNetworkFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 48 | network, err := utils.JSON.Marshal(s.pow.Network.String()) 49 | if err != nil { 50 | log.Error(err) 51 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 52 | } 53 | 54 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, network) 55 | } 56 | 57 | func (s *Server) getPeersFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 58 | raw, err := utils.JSON.Marshal(s.pow.LocalNode.Peerstore().PeersWithAddrs()) 59 | if err != nil { 60 | log.Error(err) 61 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 62 | } 63 | 64 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, raw) 65 | } 66 | -------------------------------------------------------------------------------- /jsonrpc/regiser_handlers.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "github.com/c0mm4nd/go-jsonrpc2" 5 | "github.com/ngchain/ngcore/consensus" 6 | ) 7 | 8 | // registerHTTPHandler will register jsonrpc functions onto the Server. 9 | func registerHTTPHandler(s *Server) { 10 | s.RegisterJsonRpcHandleFunc("ping", func(message *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 11 | return jsonrpc2.NewJsonRpcSuccess(message.ID, []byte("pong")) 12 | }) 13 | 14 | // p2p 15 | if !s.DisableP2PMethods { 16 | s.RegisterJsonRpcHandleFunc("addNode", s.addPeerFunc) // keep this alia 17 | s.RegisterJsonRpcHandleFunc("addPeer", s.addPeerFunc) 18 | s.RegisterJsonRpcHandleFunc("getNodes", s.getPeersFunc) // keep this alia 19 | s.RegisterJsonRpcHandleFunc("getPeers", s.getPeersFunc) 20 | s.RegisterJsonRpcHandleFunc("getNetwork", s.getNetworkFunc) 21 | } 22 | 23 | // chain 24 | s.RegisterJsonRpcHandleFunc("getLatestBlockHeight", s.requireSynced(s.getLatestBlockHeightFunc)) 25 | s.RegisterJsonRpcHandleFunc("getLatestBlockHash", s.requireSynced(s.getLatestBlockHashFunc)) 26 | s.RegisterJsonRpcHandleFunc("getLatestBlock", s.requireSynced(s.getLatestBlockFunc)) 27 | s.RegisterJsonRpcHandleFunc("getBlockByHeight", s.getBlockByHeightFunc) 28 | s.RegisterJsonRpcHandleFunc("getBlockByHash", s.getBlockByHashFunc) 29 | 30 | s.RegisterJsonRpcHandleFunc("getTxByHash", s.getTxByHashFunc) 31 | 32 | // state 33 | s.RegisterJsonRpcHandleFunc("sendTx", s.sendTxFunc) 34 | s.RegisterJsonRpcHandleFunc("signTx", s.signTxFunc) 35 | s.RegisterJsonRpcHandleFunc("genRegister", s.genRegisterFunc) 36 | s.RegisterJsonRpcHandleFunc("genDestroy", s.genDestroyFunc) 37 | s.RegisterJsonRpcHandleFunc("genTransaction", s.genTransactionFunc) 38 | s.RegisterJsonRpcHandleFunc("genAppend", s.genAppendFunc) 39 | s.RegisterJsonRpcHandleFunc("genDelete", s.genDeleteFunc) 40 | 41 | s.RegisterJsonRpcHandleFunc("getAccountByAddress", s.requireSynced(s.getAccountByAddressFunc)) 42 | s.RegisterJsonRpcHandleFunc("getAccountByNum", s.requireSynced(s.getAccountByNumFunc)) 43 | s.RegisterJsonRpcHandleFunc("getBalanceByNum", s.requireSynced(s.getBalanceByNumFunc)) 44 | s.RegisterJsonRpcHandleFunc("getBalanceByAddress", s.requireSynced(s.getBalanceByAddressFunc)) 45 | 46 | // mining 47 | if !s.DisableMiningMethods { 48 | // s.RegisterJsonRpcHandleFunc("submitBlock", s.requireSynced(s.submitBlockFunc)) 49 | // s.RegisterJsonRpcHandleFunc("getBlockTemplate", s.requireSynced(s.getBlockTemplateFunc)) 50 | s.RegisterJsonRpcHandleFunc("getWork", s.requireSynced(s.getWorkFunc)) // dangerous: public key reveal 51 | s.RegisterJsonRpcHandleFunc("submitWork", s.requireSynced(s.submitWorkFunc)) // dangerous: attack pow hash on verification 52 | } 53 | 54 | // utils 55 | s.RegisterJsonRpcHandleFunc("publicKeyToAddress", s.publicKeyToAddressFunc) 56 | } 57 | 58 | func (s *Server) requireSynced(f func(*jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage) func(*jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 59 | if s.pow.SyncMod.IsLocked() { 60 | return func(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 61 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, consensus.ErrChainOnSyncing)) 62 | } 63 | } 64 | 65 | return f 66 | } 67 | -------------------------------------------------------------------------------- /jsonrpc/state_vm.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | // TODO: add options on machine joining, e.g. encryption 4 | // type runContractParams struct { 5 | // FakeAccountNum uint64 `json:"fakeAccountNum"` 6 | // RawContract []byte `json:"rawContract"` 7 | // } 8 | // 9 | // // runContractFunc typically used to run the long loop task. Can be treated as a deploy 10 | // func (s *Server) runContractFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 11 | // var params runContractParams 12 | // err := utils.JSON.Unmarshal(msg.Params, ¶ms) 13 | // if err != nil { 14 | // log.Error(err) 15 | // return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 16 | // } 17 | // 18 | // _, err = hive.NewVM(ngtypes.NewAccount(params.FakeAccountNum, nil, params.RawContract, nil)) 19 | // if err != nil { 20 | // log.Error(err) 21 | // return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 22 | // } 23 | // 24 | // return jsonrpc2.NewJsonRpcSuccess(msg.ID, nil) 25 | // } 26 | -------------------------------------------------------------------------------- /jsonrpc/utils.go: -------------------------------------------------------------------------------- 1 | package jsonrpc 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/c0mm4nd/go-jsonrpc2" 7 | "github.com/mr-tron/base58" 8 | "github.com/ngchain/secp256k1" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | "github.com/ngchain/ngcore/utils" 12 | ) 13 | 14 | // some utils for wallet clients 15 | 16 | type getAddressParams struct { 17 | PrivateKeys []string 18 | } 19 | 20 | type getAddressReply struct { 21 | Address ngtypes.Address 22 | } 23 | 24 | // publicKeyToAddressFunc helps client to get the schnorr publickey of private keys. 25 | func (s *Server) publicKeyToAddressFunc(msg *jsonrpc2.JsonRpcMessage) *jsonrpc2.JsonRpcMessage { 26 | var params getAddressParams 27 | err := utils.JSON.Unmarshal(*msg.Params, ¶ms) 28 | if err != nil { 29 | log.Error(err) 30 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 31 | } 32 | 33 | privKeys := make([]*secp256k1.PrivateKey, len(params.PrivateKeys)) 34 | for i := 0; i < len(params.PrivateKeys); i++ { 35 | bPriv, err := base58.FastBase58Decoding(params.PrivateKeys[i]) 36 | if err != nil { 37 | log.Error(err) 38 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 39 | } 40 | 41 | privKeys[i] = secp256k1.NewPrivateKey(new(big.Int).SetBytes(bPriv)) 42 | } 43 | 44 | addr, err := ngtypes.NewAddressFromMultiKeys(privKeys...) 45 | if err != nil { 46 | log.Error(err) 47 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 48 | } 49 | 50 | result := getAddressReply{ 51 | Address: addr, 52 | } 53 | 54 | raw, err := utils.JSON.Marshal(result) 55 | if err != nil { 56 | log.Error(err) 57 | return jsonrpc2.NewJsonRpcError(msg.ID, jsonrpc2.NewError(0, err)) 58 | } 59 | 60 | return jsonrpc2.NewJsonRpcSuccess(msg.ID, raw) 61 | } 62 | -------------------------------------------------------------------------------- /jsonrpc/workpool/expirable_map.go: -------------------------------------------------------------------------------- 1 | package workpool 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | type Entry struct { 9 | Timestamp int64 10 | Value interface{} 11 | } 12 | 13 | type ExpirableMap struct { 14 | m map[string]*Entry 15 | l sync.RWMutex 16 | } 17 | 18 | func NewExpirableMap(l int, expire func(time.Time, *Entry) bool) (m *ExpirableMap) { 19 | m = &ExpirableMap{m: make(map[string]*Entry, l)} 20 | go func() { 21 | for now := range time.Tick(time.Second) { 22 | m.l.Lock() 23 | for k, entry := range m.m { 24 | if expire(now, entry) { 25 | delete(m.m, k) 26 | } 27 | } 28 | m.l.Unlock() 29 | } 30 | }() 31 | return 32 | } 33 | 34 | func (m *ExpirableMap) Len() int { 35 | return len(m.m) 36 | } 37 | 38 | func (m *ExpirableMap) Put(k string, v interface{}) { 39 | m.l.Lock() 40 | it, ok := m.m[k] 41 | if !ok { 42 | it = &Entry{Value: v} 43 | m.m[k] = it 44 | } 45 | it.Timestamp = time.Now().Unix() 46 | m.l.Unlock() 47 | } 48 | 49 | func (m *ExpirableMap) Get(k string) (v interface{}, ok bool) { 50 | m.l.RLock() 51 | var it *Entry 52 | if it, ok = m.m[k]; ok { 53 | v = it.Value 54 | it.Timestamp = time.Now().Unix() // update the last use time 55 | m.m[k] = it 56 | } 57 | m.l.RUnlock() 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /jsonrpc/workpool/pool.go: -------------------------------------------------------------------------------- 1 | package workpool 2 | 3 | import ( 4 | "errors" 5 | "time" 6 | ) 7 | 8 | type WorkPool struct { 9 | m *ExpirableMap 10 | } 11 | 12 | var workPool *WorkPool 13 | 14 | func GetWorkerPool() *WorkPool { 15 | if workPool == nil { 16 | workPool = &WorkPool{ 17 | NewExpirableMap(0, func(t time.Time, _ *Entry) bool { 18 | now := time.Now().Unix() 19 | return now-t.Unix() > 60*10 // expire in 10 min 20 | }), 21 | } 22 | } 23 | 24 | return workPool 25 | } 26 | 27 | var ( 28 | ErrBlockNotExists = errors.New("no such block in the work pool") 29 | // ErrValNotBlock = errors.New("the value in pool is not a block template") 30 | ) 31 | 32 | func (wp *WorkPool) Get(k string) (any, error) { 33 | block, ok := wp.m.Get(k) 34 | if !ok { 35 | return nil, ErrBlockNotExists 36 | } 37 | 38 | // block, ok := iBlock.(*ngtypes.FullBlock) 39 | // if !ok { 40 | // return nil, ErrValNotBlock 41 | // } 42 | 43 | return block, nil 44 | } 45 | 46 | func (wp *WorkPool) Put(k string, v any) { 47 | wp.m.Put(k, v) 48 | } 49 | -------------------------------------------------------------------------------- /keytools/defaults.go: -------------------------------------------------------------------------------- 1 | package keytools 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | ) 7 | 8 | const ( 9 | defaultKeyFolder = ".ngkeys" 10 | defaultKeyFile = "ngcore.key" // "~/.ngkeys/ngcore.key" 11 | ) 12 | 13 | // GetDefaultFolder returns the default folder storing keyfile. 14 | func GetDefaultFolder() string { 15 | home, err := os.UserHomeDir() 16 | if err != nil { 17 | panic("failed to get home folder") 18 | } 19 | 20 | return filepath.Join(home, defaultKeyFolder) 21 | } 22 | 23 | // GetDefaultFile returns the default location storing keyfile. 24 | func GetDefaultFile() string { 25 | home, err := os.UserHomeDir() 26 | if err != nil { 27 | panic("failed to get home folder") 28 | } 29 | 30 | return filepath.Join(home, defaultKeyFolder, defaultKeyFile) 31 | } 32 | -------------------------------------------------------------------------------- /keytools/p2p.go: -------------------------------------------------------------------------------- 1 | package keytools 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/libp2p/go-libp2p/core/crypto" 9 | ) 10 | 11 | func readKeyFromFile(filename string) crypto.PrivKey { 12 | keyFile, err := os.Open(filepath.Clean(filename)) 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | raw, err := io.ReadAll(keyFile) 18 | if err != nil { 19 | panic(err) 20 | } 21 | 22 | _ = keyFile.Close() 23 | 24 | priv, err := crypto.UnmarshalPrivateKey(raw) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | return priv 30 | } 31 | 32 | func GetP2PKey(path string) crypto.PrivKey { 33 | // read from db / file 34 | if path == "" { 35 | home, err := os.UserHomeDir() 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | path = filepath.Join(home, ".ngkeys") 41 | if _, err := os.Stat(path); os.IsNotExist(err) { 42 | err := os.Mkdir(path, os.ModePerm) 43 | if err != nil { 44 | panic(err) 45 | } 46 | } 47 | 48 | path = filepath.Join(path, "ngp2p.key") 49 | } 50 | 51 | if _, err := os.Stat(path); os.IsNotExist(err) { 52 | priv, _, err := crypto.GenerateKeyPair(crypto.Secp256k1, 256) 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | raw, err := crypto.MarshalPrivateKey(priv) 58 | if err != nil { 59 | panic(err) 60 | } 61 | 62 | // log.Info("creating bootstrap key") 63 | 64 | f, err := os.Create(path) 65 | if err != nil { 66 | panic(err) 67 | } 68 | 69 | _, _ = f.Write(raw) 70 | _ = f.Close() 71 | } 72 | 73 | return readKeyFromFile(path) 74 | } 75 | -------------------------------------------------------------------------------- /keytools/tools.go: -------------------------------------------------------------------------------- 1 | // Package keytools is the module to reuse the key pair 2 | package keytools 3 | 4 | import ( 5 | "fmt" 6 | "math/big" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/mr-tron/base58" 11 | "github.com/ngchain/secp256k1" 12 | 13 | "github.com/ngchain/ngcore/ngtypes" 14 | "github.com/ngchain/ngcore/utils" 15 | ) 16 | 17 | // ReadLocalKey will read the local AES-256-GCM encrypted secp256k1 key file to load an ecdsa private key. 18 | func ReadLocalKey(filename string, password string) *secp256k1.PrivateKey { 19 | var key *secp256k1.PrivateKey 20 | 21 | if filename == "" { 22 | path := GetDefaultFolder() 23 | if _, err := os.Stat(path); os.IsNotExist(err) { 24 | err := os.Mkdir(path, os.ModePerm) 25 | if err != nil { 26 | panic(err) 27 | } 28 | } 29 | 30 | filename = GetDefaultFile() 31 | } 32 | 33 | if _, err := os.Stat(filename); err != nil { 34 | key = CreateLocalKey(filename, password) 35 | } else { 36 | var raw []byte 37 | 38 | raw, err = os.ReadFile(filepath.Clean(filename)) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | rawPK := utils.AES256GCMDecrypt(raw, []byte(password)) 44 | key = secp256k1.NewPrivateKey(new(big.Int).SetBytes(rawPK)) 45 | } 46 | 47 | return key 48 | } 49 | 50 | // NewLocalKey will create a privateKey only. 51 | func NewLocalKey() *secp256k1.PrivateKey { 52 | key, err := secp256k1.GeneratePrivateKey() 53 | if err != nil { 54 | panic(err) 55 | } 56 | 57 | return key 58 | } 59 | 60 | // CreateLocalKey will create a keyfile named *filename* and encrypted with *password* in aes-256-gcm. 61 | func CreateLocalKey(filename, password string) *secp256k1.PrivateKey { 62 | key := NewLocalKey() 63 | 64 | if filename == "" { 65 | path := GetDefaultFolder() 66 | if _, err := os.Stat(path); os.IsNotExist(err) { 67 | err := os.Mkdir(path, os.ModePerm) 68 | if err != nil { 69 | panic(err) 70 | } 71 | } 72 | 73 | filename = GetDefaultFile() 74 | } 75 | 76 | // save key to ngcore.key file 77 | file, err := os.Create(filename) 78 | if err != nil { 79 | panic(err) 80 | } 81 | 82 | encrypted := utils.AES256GCMEncrypt(key.D.Bytes(), []byte(password)) 83 | 84 | _, err = file.Write(encrypted) 85 | if err != nil { 86 | panic(err) 87 | } 88 | 89 | _ = file.Close() 90 | 91 | return key 92 | } 93 | 94 | // RecoverLocalKey will recover a keyfile named *filename* with the password from the privateKey string. 95 | func RecoverLocalKey(filename, password, privateKey string) *secp256k1.PrivateKey { 96 | bKey, err := base58.FastBase58Decoding(privateKey) 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | key := secp256k1.NewPrivateKey(new(big.Int).SetBytes(bKey)) 102 | 103 | if filename == "" { 104 | path := GetDefaultFolder() 105 | if _, err := os.Stat(path); os.IsNotExist(err) { 106 | err := os.Mkdir(path, os.ModePerm) 107 | if err != nil { 108 | panic(err) 109 | } 110 | } 111 | 112 | filename = GetDefaultFile() 113 | } 114 | 115 | // save key to ngcore.key file 116 | file, err := os.Create(filename) 117 | if err != nil { 118 | panic(err) 119 | } 120 | 121 | encrypted := utils.AES256GCMEncrypt(key.D.Bytes(), []byte(password)) 122 | 123 | _, err = file.Write(encrypted) 124 | if err != nil { 125 | panic(err) 126 | } 127 | 128 | _ = file.Close() 129 | 130 | return key 131 | } 132 | 133 | // PrintKeysAndAddress will print the **privateKey and its publicKey** to the console. 134 | func PrintKeysAndAddress(privateKey *secp256k1.PrivateKey) { 135 | rawPrivateKey := privateKey.Serialize() // its D 136 | fmt.Println("private key: ", base58.FastBase58Encoding(rawPrivateKey)) 137 | 138 | bPubKey := utils.PublicKey2Bytes(privateKey.PubKey()) 139 | fmt.Println("public key: ", base58.FastBase58Encoding(bPubKey)) 140 | 141 | address := ngtypes.NewAddress(privateKey) 142 | fmt.Println("address: ", base58.FastBase58Encoding(address[:])) 143 | } 144 | -------------------------------------------------------------------------------- /keytools/tools_test.go: -------------------------------------------------------------------------------- 1 | package keytools_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/mr-tron/base58" 8 | 9 | "github.com/ngchain/ngcore/keytools" 10 | ) 11 | 12 | func TestKeyMgr_ReadLocalKey(t *testing.T) { 13 | t.Parallel() 14 | 15 | privKey := keytools.CreateLocalKey("ngtest.key", "test") 16 | privKey2 := keytools.ReadLocalKey("ngtest.key", "test") 17 | 18 | if !reflect.DeepEqual(privKey, privKey2) { 19 | t.Log(privKey) 20 | t.Log(privKey2) 21 | t.Fail() 22 | } 23 | 24 | pk := keytools.RecoverLocalKey("ngtest.key", "test", base58.FastBase58Encoding(privKey.Serialize())) 25 | if !reflect.DeepEqual(pk, privKey) { 26 | t.Log(privKey) 27 | t.Log(privKey2) 28 | t.Fail() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ngblocks/check.go: -------------------------------------------------------------------------------- 1 | package ngblocks 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "go.etcd.io/bbolt" 8 | "github.com/ngchain/ngcore/storage" 9 | ) 10 | 11 | var ( 12 | ErrBlockHeightConflict = errors.New("already has a block on the same height") 13 | ErrPrevBlockNotExist = errors.New("prev block does not exist") 14 | ) 15 | 16 | func checkBlock(blockBucket *bbolt.Bucket, height uint64, prevHash []byte) error { 17 | if blockHeightExists(blockBucket, height) { 18 | return ErrBlockHeightConflict 19 | } 20 | 21 | if !blockPrevHashExists(blockBucket, height, prevHash) { 22 | return ErrPrevBlockNotExist 23 | } 24 | 25 | return nil 26 | } 27 | 28 | func blockHeightExists(blockBucket *bbolt.Bucket, height uint64) bool { 29 | if height == 0 { 30 | return true 31 | } 32 | _, err := GetBlockByHeight(blockBucket, height) 33 | if errors.Is(err, storage.ErrKeyNotFound) { 34 | return false 35 | } 36 | 37 | if err != nil { 38 | log.Error(err) 39 | } 40 | 41 | return true 42 | } 43 | 44 | func blockPrevHashExists(blockBucket *bbolt.Bucket, height uint64, prevHash []byte) bool { 45 | if height == 0 && bytes.Equal(prevHash, make([]byte, 32)) { 46 | return true 47 | } 48 | 49 | b, err := GetBlockByHash(blockBucket, prevHash) 50 | if err != nil { 51 | return false 52 | } 53 | 54 | if b.GetHeight() == height-1 { 55 | return true 56 | } 57 | 58 | return false 59 | } 60 | -------------------------------------------------------------------------------- /ngblocks/doc.go: -------------------------------------------------------------------------------- 1 | // Package ngblocks is a store for blocks which contains txs 2 | package ngblocks 3 | -------------------------------------------------------------------------------- /ngblocks/get.go: -------------------------------------------------------------------------------- 1 | package ngblocks 2 | 3 | import ( 4 | "encoding/binary" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | "go.etcd.io/bbolt" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | "github.com/ngchain/ngcore/storage" 12 | "github.com/ngchain/ngcore/utils" 13 | ) 14 | 15 | func GetTxByHash(txBucket *bbolt.Bucket, hash []byte) (*ngtypes.FullTx, error) { 16 | rawTx := txBucket.Get(hash) 17 | if rawTx == nil { 18 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such tx in hash %x", hash) 19 | } 20 | 21 | var tx ngtypes.FullTx 22 | err := rlp.DecodeBytes(rawTx, &tx) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | return &tx, nil 28 | } 29 | 30 | func GetBlockByHash(blockBucket *bbolt.Bucket, hash []byte) (*ngtypes.FullBlock, error) { 31 | rawBlock := blockBucket.Get(hash) 32 | if rawBlock == nil { 33 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such block in hash %x", hash) 34 | } 35 | 36 | var b ngtypes.FullBlock 37 | err := rlp.DecodeBytes(rawBlock, &b) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | return &b, nil 43 | } 44 | 45 | func GetBlockByHeight(blockBucket *bbolt.Bucket, height uint64) (*ngtypes.FullBlock, error) { 46 | key := utils.PackUint64LE(height) 47 | hash := blockBucket.Get(key) 48 | if hash == nil { 49 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such block in height %d", height) 50 | } 51 | 52 | rawBlock := blockBucket.Get(hash) 53 | if rawBlock == nil { 54 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such block in hash %x", hash) 55 | } 56 | 57 | var b ngtypes.FullBlock 58 | err := rlp.DecodeBytes(rawBlock, &b) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return &b, nil 64 | } 65 | 66 | func GetLatestHeight(blockBucket *bbolt.Bucket) (uint64, error) { 67 | height := blockBucket.Get(storage.LatestHeightTag) 68 | if height == nil { 69 | return 0, errors.Wrapf(storage.ErrKeyNotFound, "no such hash in latestTag") 70 | } 71 | 72 | return binary.LittleEndian.Uint64(height), nil 73 | } 74 | 75 | func GetLatestHash(blockBucket *bbolt.Bucket) ([]byte, error) { 76 | hash := blockBucket.Get(storage.LatestHashTag) 77 | if hash == nil { 78 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such hash in latestTag") 79 | } 80 | 81 | return hash, nil 82 | } 83 | 84 | func GetLatestBlock(blockBucket *bbolt.Bucket) (*ngtypes.FullBlock, error) { 85 | hash, err := GetLatestHash(blockBucket) 86 | if err != nil { 87 | return nil, err 88 | } 89 | 90 | block, err := GetBlockByHash(blockBucket, hash) 91 | if err != nil { 92 | return nil, err 93 | } 94 | 95 | return block, nil 96 | } 97 | 98 | func GetOriginHeight(blockBucket *bbolt.Bucket) (uint64, error) { 99 | height := blockBucket.Get(storage.OriginHeightTag) 100 | if height == nil { 101 | return 0, errors.Wrapf(storage.ErrKeyNotFound, "no such hash in originHeightTag") 102 | } 103 | 104 | return binary.LittleEndian.Uint64(height), nil 105 | } 106 | 107 | func GetOriginHash(blockBucket *bbolt.Bucket) ([]byte, error) { 108 | hash := blockBucket.Get(storage.OriginHashTag) 109 | if hash == nil { 110 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "no such hash in originHashTag") 111 | } 112 | 113 | return hash, nil 114 | } 115 | 116 | func GetOriginBlock(blockBucket *bbolt.Bucket) (*ngtypes.FullBlock, error) { 117 | hash, err := GetOriginHash(blockBucket) 118 | if err != nil { 119 | return nil, err 120 | } 121 | 122 | block, err := GetBlockByHash(blockBucket, hash) 123 | if err != nil { 124 | return nil, err 125 | } 126 | 127 | return block, nil 128 | } 129 | -------------------------------------------------------------------------------- /ngblocks/put.go: -------------------------------------------------------------------------------- 1 | package ngblocks 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | "go.etcd.io/bbolt" 8 | 9 | "github.com/ngchain/ngcore/ngtypes" 10 | "github.com/ngchain/ngcore/storage" 11 | "github.com/ngchain/ngcore/utils" 12 | ) 13 | 14 | var ErrPutEmptyBlock = errors.New("putting empty block into the db") 15 | 16 | // PutNewBlock puts a new block into db and updates the tags. 17 | // should check block before putting 18 | // dev should continue upgrading the state after PutNewBlock 19 | func PutNewBlock(blockBucket *bbolt.Bucket, txBucket *bbolt.Bucket, block *ngtypes.FullBlock) error { 20 | if block == nil { 21 | return ErrPutEmptyBlock 22 | } 23 | 24 | hash := block.GetHash() 25 | 26 | err := checkBlock(blockBucket, block.GetHeight(), block.GetPrevHash()) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | log.Infof("putting block@%d: %x", block.GetHeight(), hash) 32 | err = putBlock(blockBucket, hash, block) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | // put txs 38 | err = putTxs(txBucket, block) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | // update helper 44 | err = putLatestTags(blockBucket, block.GetHeight(), hash) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | return nil 50 | } 51 | 52 | func putTxs(txBucket *bbolt.Bucket, block *ngtypes.FullBlock) error { 53 | for i := range block.Txs { 54 | hash := block.Txs[i].GetHash() 55 | 56 | raw, err := rlp.EncodeToBytes(block.Txs[i]) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | err = txBucket.Put(hash, raw) 62 | if err != nil { 63 | return err 64 | } 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func putBlock(blockBucket *bbolt.Bucket, hash []byte, block *ngtypes.FullBlock) error { 71 | raw, err := rlp.EncodeToBytes(block) 72 | if err != nil { 73 | return err 74 | } 75 | 76 | // put block hash & height 77 | err = blockBucket.Put(hash, raw) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | err = blockBucket.Put(utils.PackUint64LE(block.GetHeight()), hash) 83 | if err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func putLatestTags(blockBucket *bbolt.Bucket, height uint64, hash []byte) error { 91 | err := blockBucket.Put(storage.LatestHeightTag, utils.PackUint64LE(height)) 92 | if err != nil { 93 | return err 94 | } 95 | err = blockBucket.Put(storage.LatestHashTag, hash) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /ngblocks/put_force.go: -------------------------------------------------------------------------------- 1 | package ngblocks 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | "github.com/pkg/errors" 6 | 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | // ForcePutNewBlock puts a block into db regardless of local store check 11 | // should check block self before putting 12 | func (store *BlockStore) ForcePutNewBlock(blockBucket *bbolt.Bucket, txBucket *bbolt.Bucket, block *ngtypes.FullBlock) error { 13 | if block == nil { 14 | panic("block is nil") 15 | } 16 | 17 | hash := block.GetHash() 18 | 19 | // deleting txs 20 | if blockHeightExists(blockBucket, block.GetHeight()) { 21 | b, err := GetBlockByHeight(blockBucket, block.GetHeight()) 22 | if err != nil { 23 | return errors.Wrapf(err, "failed to get existing block@%d", block.GetHeight()) 24 | } 25 | 26 | err = delTxs(txBucket, b.Txs...) 27 | if err != nil { 28 | return errors.Wrap(err, "failed to del txs") 29 | } 30 | } 31 | 32 | if !blockPrevHashExists(blockBucket, block.GetHeight(), block.GetPrevHash()) { 33 | return errors.Wrapf(ErrPrevBlockNotExist, "cannot find block %x", block.GetPrevHash()) 34 | } 35 | 36 | log.Infof("putting block@%d: %x", block.GetHeight(), hash) 37 | err := putBlock(blockBucket, hash, block) 38 | if err != nil { 39 | return errors.Wrap(err, "failed to put block") 40 | } 41 | 42 | // put txs 43 | err = putTxs(txBucket, block) 44 | if err != nil { 45 | return errors.Wrap(err, "failed to put txs") 46 | } 47 | 48 | // update helper 49 | err = putLatestTags(blockBucket, block.GetHeight(), hash) 50 | if err != nil { 51 | return errors.Wrap(err, "failed to update tags") 52 | } 53 | return nil 54 | } 55 | 56 | func delTxs(txBucket *bbolt.Bucket, txs ...*ngtypes.FullTx) error { 57 | for i := range txs { 58 | hash := txs[i].GetHash() 59 | 60 | err := txBucket.Delete(hash) 61 | if err != nil { 62 | return errors.Wrapf(err, "failed to delete tx %x", hash) 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | -------------------------------------------------------------------------------- /ngblocks/store.go: -------------------------------------------------------------------------------- 1 | package ngblocks 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | logging "github.com/ngchain/zap-log" 6 | 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | var log = logging.Logger("blocks") 11 | 12 | // BlockStore managers a badger DB, which stores vaults and blocks and some helper tags for managing. 13 | // TODO: Add DAG support to extend the capacity of store 14 | // initialize with genesis blocks first, 15 | // then load the origin in bootstrap process 16 | type BlockStore struct { 17 | *bbolt.DB 18 | Network ngtypes.Network 19 | } 20 | 21 | // Init will do all initialization for the block store. 22 | func Init(db *bbolt.DB, network ngtypes.Network) *BlockStore { 23 | store := &BlockStore{ 24 | DB: db, 25 | Network: network, 26 | } 27 | 28 | store.initWithGenesis() 29 | // err := store.initWithBlockchain(blocks...) 30 | // if err != nil { 31 | // panic(err) 32 | // } 33 | 34 | return store 35 | } 36 | -------------------------------------------------------------------------------- /ngp2p/bootstrap_nodes.go: -------------------------------------------------------------------------------- 1 | package ngp2p 2 | 3 | import ( 4 | "github.com/multiformats/go-multiaddr" 5 | ) 6 | 7 | // BootstrapNodes is a list of all bootstrap nodes 8 | var BootstrapNodes []multiaddr.Multiaddr 9 | 10 | func init() { 11 | BootstrapNodes = make([]multiaddr.Multiaddr, 0) // dht.DefaultBootstrapPeers 12 | for _, s := range []string{ 13 | "/dnsaddr/bootstrap.ngin.cash/p2p/16Uiu2HAm8ZMX2dNvvsRb4amNZYTwd9XaPbtawCfStWVNMzSUgZhN", 14 | "/dnsaddr/bootstrap.ngin.cash/p2p/16Uiu2HAmRCHKtLwjzRCju4fF1BDirzAKe6VAeZx8SsoZZKy49ox6", 15 | "/dnsaddr/bootstrap.ngin.cash/p2p/16Uiu2HAmRv3oGGYKEUJfJbd7tYGUAy4Kq9y6H9voHupR4wBsktxW", 16 | "/ip4/127.0.0.1/tcp/52520/p2p/16Uiu2HAm1HFhVjeLTmmhvmtRL4SdzNUdyJUxkiT3ekx6HwaXT7uc", 17 | } { 18 | ma, err := multiaddr.NewMultiaddr(s) 19 | if err != nil { 20 | panic(err) 21 | } 22 | BootstrapNodes = append(BootstrapNodes, ma) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ngp2p/broadcast/broadcast.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import ( 4 | "context" 5 | 6 | pubsub "github.com/libp2p/go-libp2p-pubsub" 7 | core "github.com/libp2p/go-libp2p/core" 8 | logging "github.com/ngchain/zap-log" 9 | 10 | "github.com/ngchain/ngcore/ngp2p/defaults" 11 | "github.com/ngchain/ngcore/ngtypes" 12 | ) 13 | 14 | type Broadcast struct { 15 | PubSub *pubsub.PubSub 16 | node core.Host 17 | 18 | network ngtypes.Network 19 | topics map[string]*pubsub.Topic 20 | subscriptions map[string]*pubsub.Subscription 21 | 22 | blockTopic string 23 | txTopic string 24 | 25 | OnBlock chan *ngtypes.FullBlock 26 | OnTx chan *ngtypes.FullTx 27 | } 28 | 29 | var log = logging.Logger("bcast") 30 | 31 | func NewBroadcastProtocol(node core.Host, network ngtypes.Network, blockCh chan *ngtypes.FullBlock, txCh chan *ngtypes.FullTx) *Broadcast { 32 | var err error 33 | 34 | b := &Broadcast{ 35 | PubSub: nil, 36 | node: node, 37 | network: network, 38 | topics: make(map[string]*pubsub.Topic), 39 | subscriptions: make(map[string]*pubsub.Subscription), 40 | 41 | blockTopic: defaults.GetBroadcastBlockTopic(network), 42 | txTopic: defaults.GetBroadcastTxTopic(network), 43 | 44 | OnBlock: blockCh, 45 | OnTx: txCh, 46 | } 47 | 48 | b.PubSub, err = pubsub.NewFloodSub(context.Background(), node) 49 | if err != nil { 50 | panic(err) 51 | } 52 | 53 | b.topics[b.blockTopic], err = b.PubSub.Join(b.blockTopic) 54 | if err != nil { 55 | panic(err) 56 | } 57 | 58 | b.subscriptions[b.blockTopic], err = b.topics[b.blockTopic].Subscribe() 59 | if err != nil { 60 | panic(err) 61 | } 62 | 63 | b.topics[b.txTopic], err = b.PubSub.Join(b.txTopic) 64 | if err != nil { 65 | panic(err) 66 | } 67 | 68 | b.subscriptions[b.txTopic], err = b.topics[b.txTopic].Subscribe() 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | return b 74 | } 75 | 76 | func (b *Broadcast) GoServe() { 77 | go b.blockListener(b.subscriptions[b.blockTopic]) 78 | go b.txListener(b.subscriptions[b.txTopic]) 79 | } 80 | 81 | func (b *Broadcast) blockListener(sub *pubsub.Subscription) { 82 | for { 83 | msg, err := sub.Next(context.Background()) 84 | if err != nil { 85 | log.Error(err) 86 | continue 87 | } 88 | 89 | go b.onBroadcastBlock(msg) 90 | } 91 | } 92 | 93 | func (b *Broadcast) txListener(sub *pubsub.Subscription) { 94 | for { 95 | msg, err := sub.Next(context.Background()) 96 | if err != nil { 97 | log.Error(err) 98 | continue 99 | } 100 | 101 | go b.onBroadcastTx(msg) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /ngp2p/broadcast/broadcast_block.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | pubsub "github.com/libp2p/go-libp2p-pubsub" 8 | 9 | "github.com/ngchain/ngcore/ngtypes" 10 | ) 11 | 12 | func (b *Broadcast) BroadcastBlock(block *ngtypes.FullBlock) error { 13 | raw, err := rlp.EncodeToBytes(block) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | err = b.topics[b.blockTopic].Publish(context.Background(), raw) 19 | if err != nil { 20 | return err 21 | } 22 | 23 | log.Debugf("broadcast block@%d: %x", block.GetHeight(), block.GetHash()) 24 | 25 | return nil 26 | } 27 | 28 | func (b *Broadcast) onBroadcastBlock(msg *pubsub.Message) { 29 | var newBlock ngtypes.FullBlock 30 | 31 | err := rlp.DecodeBytes(msg.Data, &newBlock) 32 | if err != nil { 33 | log.Error(err) 34 | return 35 | } 36 | 37 | log.Debugf("received a new block broadcast@%d", newBlock.GetHeight()) 38 | 39 | b.OnBlock <- &newBlock 40 | } 41 | -------------------------------------------------------------------------------- /ngp2p/broadcast/broadcast_tx.go: -------------------------------------------------------------------------------- 1 | package broadcast 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | pubsub "github.com/libp2p/go-libp2p-pubsub" 8 | 9 | "github.com/ngchain/ngcore/ngtypes" 10 | ) 11 | 12 | func (b *Broadcast) BroadcastTx(tx *ngtypes.FullTx) error { 13 | log.Debugf("broadcasting tx %s", tx.BS58()) 14 | 15 | raw, err := rlp.EncodeToBytes(tx) 16 | if err != nil { 17 | log.Errorf("failed to sign pb data") 18 | return err 19 | } 20 | 21 | err = b.topics[b.txTopic].Publish(context.Background(), raw) 22 | if err != nil { 23 | log.Error(err) 24 | return err 25 | } 26 | 27 | log.Debugf("broadcast Tx: %s", tx.ID()) 28 | 29 | return nil 30 | } 31 | 32 | func (b *Broadcast) onBroadcastTx(msg *pubsub.Message) { 33 | var newTx ngtypes.FullTx 34 | 35 | err := rlp.DecodeBytes(msg.Data, &newTx) 36 | if err != nil { 37 | log.Error(err) 38 | return 39 | } 40 | 41 | b.OnTx <- &newTx 42 | } 43 | -------------------------------------------------------------------------------- /ngp2p/defaults/defaults.go: -------------------------------------------------------------------------------- 1 | package defaults 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/ngchain/ngcore/ngtypes" 7 | ) 8 | 9 | // MaxBlocks limits the max number of blocks which are transfered on p2p network. 10 | const MaxBlocks = 1000 11 | 12 | // pattern: /ngp2p/protocol-name/version 13 | const ( 14 | protocolVersion = "/0.0.1" 15 | ) 16 | 17 | func getGenesisBlockHash(network ngtypes.Network) string { 18 | return hex.EncodeToString(ngtypes.GetGenesisBlock(network).GetHash()) 19 | } 20 | 21 | func GetWiredProtocol(network ngtypes.Network) string { 22 | return "/ngp2p/wired/" + getGenesisBlockHash(network) + protocolVersion 23 | } 24 | 25 | func GetDHTProtocolExtension(network ngtypes.Network) string { 26 | return "/ngp2p/dht/" + getGenesisBlockHash(network) + protocolVersion 27 | } 28 | 29 | func GetBroadcastBlockTopic(network ngtypes.Network) string { 30 | return "/ngp2p/broadcast/block/" + getGenesisBlockHash(network) + protocolVersion 31 | } 32 | 33 | func GetBroadcastTxTopic(network ngtypes.Network) string { 34 | return "/ngp2p/broadcast/tx/" + getGenesisBlockHash(network) + protocolVersion 35 | } 36 | -------------------------------------------------------------------------------- /ngp2p/dht.go: -------------------------------------------------------------------------------- 1 | package ngp2p 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | 7 | "github.com/libp2p/go-libp2p" 8 | dht "github.com/libp2p/go-libp2p-kad-dht" 9 | core "github.com/libp2p/go-libp2p/core" 10 | "github.com/libp2p/go-libp2p/core/host" 11 | "github.com/libp2p/go-libp2p/core/peer" 12 | "github.com/libp2p/go-libp2p/core/protocol" 13 | "github.com/libp2p/go-libp2p/core/routing" 14 | "github.com/multiformats/go-multiaddr" 15 | "go.uber.org/atomic" 16 | 17 | "github.com/ngchain/ngcore/ngp2p/defaults" 18 | "github.com/ngchain/ngcore/ngtypes" 19 | ) 20 | 21 | var p2pDHT *dht.IpfsDHT 22 | 23 | func getPublicRouter(network ngtypes.Network) libp2p.Option { 24 | return libp2p.Routing(func(h host.Host) (routing.PeerRouting, error) { 25 | var err error 26 | p2pDHT, err = dht.New( 27 | context.Background(), h, 28 | dht.Mode(dht.ModeAutoServer), 29 | dht.ProtocolExtension( 30 | protocol.ID(defaults.GetDHTProtocolExtension(network)))) 31 | return p2pDHT, err 32 | }) 33 | } 34 | 35 | // active DHT 36 | func activeDHT(ctx context.Context, kademliaDHT *dht.IpfsDHT, host core.Host, disableConnectingBootstraps bool) { 37 | err := kademliaDHT.Bootstrap(ctx) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | if disableConnectingBootstraps { 43 | connectToDHTBootstrapNodes(ctx, host, []multiaddr.Multiaddr{}) 44 | } else { 45 | connectToDHTBootstrapNodes(ctx, host, BootstrapNodes) 46 | } 47 | 48 | } 49 | 50 | func connectToDHTBootstrapNodes(ctx context.Context, h host.Host, mas []multiaddr.Multiaddr) int32 { 51 | var wg sync.WaitGroup 52 | var numConnected atomic.Int32 53 | for _, ma := range mas { 54 | wg.Add(1) 55 | go func(ma multiaddr.Multiaddr) { 56 | pi, err := peer.AddrInfoFromP2pAddr(ma) 57 | if err != nil { 58 | panic(err) 59 | } 60 | defer wg.Done() 61 | err = h.Connect(ctx, *pi) 62 | if err != nil { 63 | log.Errorf("error connecting to bootstrap node %q: %v", ma, err) 64 | } else { 65 | numConnected.Inc() 66 | } 67 | }(ma) 68 | } 69 | wg.Wait() 70 | return numConnected.Load() 71 | } 72 | -------------------------------------------------------------------------------- /ngp2p/doc.go: -------------------------------------------------------------------------------- 1 | // Package ngp2p is the ngin p2p protocol implement based on libp2p 2 | // 3 | // ngp2p uses libp2p(ipfs)'s dht for public peer discovery and mDNS for private, and uses pub-sub to work the broadcast net 4 | // 5 | // the main peer-to-peer communication is based on Wired Protocol, which uses fast protobuf to encode and decode objects 6 | package ngp2p 7 | -------------------------------------------------------------------------------- /ngp2p/main.go: -------------------------------------------------------------------------------- 1 | package ngp2p 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/libp2p/go-libp2p" 8 | "github.com/libp2p/go-libp2p/core/host" 9 | rhost "github.com/libp2p/go-libp2p/p2p/host/routed" 10 | yamux "github.com/libp2p/go-libp2p/p2p/muxer/yamux" 11 | "github.com/libp2p/go-libp2p/p2p/transport/tcp" 12 | logging "github.com/ngchain/zap-log" 13 | 14 | "github.com/ngchain/ngcore/blockchain" 15 | "github.com/ngchain/ngcore/keytools" 16 | "github.com/ngchain/ngcore/ngp2p/broadcast" 17 | "github.com/ngchain/ngcore/ngp2p/wired" 18 | "github.com/ngchain/ngcore/ngtypes" 19 | ) 20 | 21 | var log = logging.Logger("ngp2p") 22 | 23 | var _ host.Host = (*LocalNode)(nil) 24 | 25 | // LocalNode is the local host on p2p network 26 | type LocalNode struct { 27 | host.Host // lib-p2p host 28 | network ngtypes.Network 29 | P2PConfig P2PConfig 30 | 31 | *wired.Wired 32 | *broadcast.Broadcast 33 | } 34 | 35 | type P2PConfig struct { 36 | P2PKeyFile string 37 | Network ngtypes.Network 38 | Port int 39 | DisableDiscovery bool 40 | DisableConnectingBootstraps bool 41 | } 42 | 43 | // InitLocalNode creates a new node with its implemented protocols. 44 | func InitLocalNode(chain *blockchain.Chain, config P2PConfig) *LocalNode { 45 | ctx := context.Background() 46 | priv := keytools.GetP2PKey(config.P2PKeyFile) 47 | 48 | transports := libp2p.ChainOptions( 49 | libp2p.Transport(tcp.NewTCPTransport), 50 | // libp2p.Transport(ws.New), 51 | ) 52 | 53 | listenAddrs := libp2p.ListenAddrStrings( 54 | fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", config.Port), 55 | fmt.Sprintf("/ip6/::/tcp/%d", config.Port), 56 | ) 57 | 58 | muxers := libp2p.ChainOptions( 59 | libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport), 60 | ) 61 | 62 | localHost, err := libp2p.New( 63 | transports, 64 | listenAddrs, 65 | muxers, 66 | libp2p.Identity(priv), 67 | getPublicRouter(config.Network), 68 | libp2p.NATPortMap(), 69 | // libp2p.EnableAutoRelay(), 70 | ) 71 | if err != nil { 72 | panic(err) 73 | } 74 | 75 | // init 76 | log.Warnf("P2P Listening on: /ip4//tcp/%d/p2p/%s \n", config.Port, localHost.ID().String()) 77 | 78 | localNode := &LocalNode{ 79 | // sub modules 80 | Host: rhost.Wrap(localHost, p2pDHT), 81 | network: config.Network, 82 | Wired: wired.NewWiredProtocol(localHost, config.Network, chain), 83 | Broadcast: broadcast.NewBroadcastProtocol(localHost, config.Network, make(chan *ngtypes.FullBlock), make(chan *ngtypes.FullTx)), 84 | } 85 | 86 | if !config.DisableDiscovery { 87 | activeDHT(ctx, p2pDHT, localNode, config.DisableConnectingBootstraps) 88 | } 89 | 90 | return localNode 91 | } 92 | 93 | func (localNode *LocalNode) GoServe() { 94 | localNode.Wired.GoServe() 95 | localNode.Broadcast.GoServe() 96 | } 97 | -------------------------------------------------------------------------------- /ngp2p/wired/message.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | core "github.com/libp2p/go-libp2p/core" 8 | 9 | "github.com/ngchain/ngcore/ngtypes" 10 | ) 11 | 12 | type MsgType uint8 13 | 14 | const ( 15 | InvalidMsg MsgType = iota 16 | PingMsg 17 | PongMsg 18 | RejectMsg 19 | // MsgNotFound - deleted because Reject can cover not-found msg. 20 | ) 21 | 22 | func (mt MsgType) String() string { 23 | switch mt { 24 | case InvalidMsg: 25 | return "InvalidMsg" 26 | case PingMsg: 27 | return "PingMsg" 28 | case PongMsg: 29 | return "PongMsg" 30 | case RejectMsg: 31 | return "RejectMsg" 32 | default: 33 | return fmt.Sprintf("UnknownMsg: %d", mt) 34 | } 35 | } 36 | 37 | const ( 38 | GetChainMsg MsgType = iota + 0x10 39 | ChainMsg 40 | GetSheetMsg 41 | SheetMsg 42 | ) 43 | 44 | type ChainType uint8 45 | 46 | const ( 47 | InvalidChain ChainType = iota 48 | BlockChain 49 | HeaderChain 50 | // HashChain // insecure. 51 | ) 52 | 53 | type MsgHeader struct { 54 | Network ngtypes.Network 55 | 56 | ID []byte 57 | Type MsgType 58 | Timestamp uint64 59 | PeerKey []byte 60 | Sign []byte 61 | } 62 | 63 | type Message struct { 64 | Header *MsgHeader 65 | Payload []byte 66 | } 67 | 68 | // NewHeader is a helper method: generate message data shared between all node's p2p protocols. 69 | func NewHeader(host core.Host, network ngtypes.Network, msgID []byte, msgType MsgType) *MsgHeader { 70 | peerKey, err := host.Peerstore().PubKey(host.ID()).Raw() 71 | if err != nil { 72 | panic("Failed to get public key for sender from local peer store.") 73 | } 74 | 75 | return &MsgHeader{ 76 | Network: network, 77 | ID: msgID, 78 | Type: msgType, 79 | Timestamp: uint64(time.Now().Unix()), 80 | PeerKey: peerKey, 81 | Sign: nil, 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ngp2p/wired/message_payload.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import "github.com/ngchain/ngcore/ngtypes" 4 | 5 | // StatusPayload is the payload used when ping pong. 6 | type StatusPayload struct { 7 | Origin uint64 8 | Latest uint64 9 | CheckpointHash []byte 10 | CheckpointDiff []byte // actual diff 11 | } 12 | 13 | type GetChainPayload struct { 14 | Type ChainType 15 | From [][]byte 16 | To []byte 17 | } 18 | 19 | type ChainPayload struct { 20 | Type ChainType 21 | Headers []*ngtypes.BlockHeader `rlp:"optional"` 22 | Blocks []*ngtypes.FullBlock `rlp:"optional"` 23 | } 24 | 25 | // GetSheetPayload is the payload for getting a sheet from remote 26 | // Design: 27 | // fast-sync for state 28 | // support checkpoint height only 29 | // when fast-sync: 30 | // 1. sync to latest checkpoint 31 | // 2. sync to latest checkpoint state 32 | // 3. sync the remaining blocks 33 | // 4. update local state 34 | type GetSheetPayload struct { 35 | Height uint64 36 | Hash []byte 37 | } 38 | 39 | type SheetPayload struct { 40 | Sheet *ngtypes.Sheet 41 | } 42 | -------------------------------------------------------------------------------- /ngp2p/wired/message_recv.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | "github.com/libp2p/go-libp2p/core/network" 8 | "github.com/libp2p/go-msgio" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | var ( 13 | ErrMsgMalformed = errors.New("malformed message") 14 | ErrMsgIDInvalid = errors.New("message id is invalid") 15 | ErrMsgTypeInvalid = errors.New("message type is invalid") 16 | ErrMsgSignInvalid = errors.New("message sign is invalid") 17 | ErrMsgPayloadInvalid = errors.New("message payload is invalid") 18 | ) 19 | 20 | // ReceiveReply will receive the correct reply message from the stream. 21 | func ReceiveReply(uuid []byte, stream network.Stream) (*Message, error) { 22 | r := msgio.NewReader(stream) 23 | raw, err := r.ReadMsg() 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | err = stream.Close() 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | var msg Message 34 | err = rlp.DecodeBytes(raw, &msg) 35 | if err != nil { 36 | return nil, err 37 | } 38 | 39 | if msg.Header == nil { 40 | return nil, errors.Wrap(ErrMsgMalformed, "response doesnt have msg header") 41 | } 42 | 43 | if msg.Header.Type == InvalidMsg { 44 | return nil, errors.Wrap(ErrMsgTypeInvalid, "invalid message type") 45 | } 46 | 47 | if !bytes.Equal(msg.Header.ID, uuid) { 48 | return nil, errors.Wrap(ErrMsgIDInvalid, "invalid message id") 49 | } 50 | 51 | if !Verify(stream.Conn().RemotePeer(), &msg) { 52 | return nil, errors.Wrap(ErrMsgSignInvalid, "failed to verify the sign of message") 53 | } 54 | 55 | return &msg, nil 56 | } 57 | -------------------------------------------------------------------------------- /ngp2p/wired/message_send.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | core "github.com/libp2p/go-libp2p/core" 8 | "github.com/libp2p/go-libp2p/core/network" 9 | "github.com/libp2p/go-libp2p/core/peer" 10 | "github.com/libp2p/go-libp2p/core/protocol" 11 | "github.com/libp2p/go-msgio" 12 | ) 13 | 14 | // Send is a helper method - writes a protobuf go data object to a network stream. 15 | // then the stream will be returned and caller is able to read the response from it. 16 | func Send(host core.Host, protocolID protocol.ID, peerID peer.ID, data interface{}) (network.Stream, error) { 17 | raw, err := rlp.EncodeToBytes(data) 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | stream, err := host.NewStream(context.Background(), peerID, protocolID) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | w := msgio.NewWriter(stream) 28 | if err = w.WriteMsg(raw); err != nil { 29 | return nil, err 30 | } 31 | 32 | return stream, nil 33 | } 34 | 35 | func Reply(stream network.Stream, data interface{}) error { 36 | raw, err := rlp.EncodeToBytes(data) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if err = msgio.NewWriter(stream).WriteMsg(raw); err != nil { 42 | return err 43 | } 44 | 45 | // // close the stream and waits to read an EOF from the other side. 46 | // err = stream.Close() 47 | // if err != nil { 48 | // return err 49 | // } 50 | 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /ngp2p/wired/message_verify.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | core "github.com/libp2p/go-libp2p/core" 6 | "github.com/libp2p/go-libp2p/core/crypto" 7 | "github.com/libp2p/go-libp2p/core/peer" 8 | ) 9 | 10 | // Verify verifies the data and sign in message. 11 | func Verify(peerID peer.ID, message *Message) bool { 12 | sign := message.Header.Sign 13 | message.Header.Sign = nil 14 | 15 | raw, err := rlp.EncodeToBytes(message) 16 | if err != nil { 17 | log.Errorf("failed to marshal pb message: %v", err) 18 | return false 19 | } 20 | 21 | message.Header.Sign = sign 22 | 23 | return verifyMessageData(raw, sign, peerID, message.Header.PeerKey) 24 | } 25 | 26 | // Signature an outgoing p2p message payload. 27 | func Signature(host core.Host, message *Message) ([]byte, error) { 28 | message.Header.Sign = nil 29 | 30 | data, err := rlp.EncodeToBytes(message) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | key := host.Peerstore().PrivKey(host.ID()) 36 | res, err := key.Sign(data) 37 | 38 | return res, err 39 | } 40 | 41 | // verifyMessageData verifies incoming p2p message data integrity. 42 | // it is included in Verify so plz using Verify. 43 | func verifyMessageData(data, signature []byte, peerID peer.ID, pubKeyData []byte) bool { 44 | key, err := crypto.UnmarshalPublicKey(pubKeyData) 45 | if err != nil { 46 | log.Error(err, "Failed to extract key from message key data") 47 | return false 48 | } 49 | 50 | // extract node id from the provided public key 51 | idFromKey, err := peer.IDFromPublicKey(key) 52 | if err != nil { 53 | log.Error(err, "Failed to extract peer id from public key") 54 | return false 55 | } 56 | 57 | // verify that message author node id matches the provided node public key 58 | if idFromKey != peerID { 59 | log.Error(err, "LocalNode id and provided public key mismatch") 60 | return false 61 | } 62 | 63 | res, err := key.Verify(data, signature) 64 | if err != nil { 65 | log.Error(err, "Error authenticating data") 66 | return false 67 | } 68 | 69 | return res 70 | } 71 | -------------------------------------------------------------------------------- /ngp2p/wired/wired.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | core "github.com/libp2p/go-libp2p/core" 6 | "github.com/libp2p/go-libp2p/core/network" 7 | "github.com/libp2p/go-libp2p/core/protocol" 8 | "github.com/libp2p/go-msgio" 9 | logging "github.com/ngchain/zap-log" 10 | 11 | "github.com/ngchain/ngcore/blockchain" 12 | "github.com/ngchain/ngcore/ngp2p/defaults" 13 | "github.com/ngchain/ngcore/ngtypes" 14 | ) 15 | 16 | var log = logging.Logger("wired") 17 | 18 | // Wired type. 19 | type Wired struct { 20 | network ngtypes.Network 21 | host core.Host // local host 22 | 23 | protocolID protocol.ID 24 | 25 | chain *blockchain.Chain 26 | } 27 | 28 | func NewWiredProtocol(host core.Host, network ngtypes.Network, chain *blockchain.Chain) *Wired { 29 | w := &Wired{ 30 | network: network, 31 | host: host, 32 | 33 | protocolID: protocol.ID(defaults.GetWiredProtocol(network)), 34 | 35 | chain: chain, 36 | } 37 | 38 | return w 39 | } 40 | 41 | func (w *Wired) GetWiredProtocol() protocol.ID { 42 | return w.protocolID 43 | } 44 | 45 | func (w *Wired) GoServe() { 46 | // register handler 47 | w.host.SetStreamHandler(w.protocolID, func(stream network.Stream) { 48 | log.Debugf("handling new stream from %s", stream.Conn().RemotePeer()) 49 | go w.handleStream(stream) 50 | }) 51 | } 52 | 53 | func (w *Wired) handleStream(stream network.Stream) { 54 | r := msgio.NewReader(stream) 55 | raw, err := r.ReadMsg() 56 | if err != nil { 57 | log.Error(err) 58 | return 59 | } 60 | 61 | // unmarshal it 62 | var msg Message 63 | 64 | err = rlp.DecodeBytes(raw, &msg) 65 | if err != nil { 66 | log.Error(err) 67 | return 68 | } 69 | 70 | if !Verify(stream.Conn().RemotePeer(), &msg) { 71 | w.sendReject(msg.Header.ID, stream, ErrMsgSignInvalid) 72 | return 73 | } 74 | 75 | switch msg.Header.Type { 76 | case PingMsg: 77 | w.onPing(stream, &msg) 78 | case GetChainMsg: 79 | w.onGetChain(stream, &msg) 80 | case GetSheetMsg: 81 | w.onGetChain(stream, &msg) 82 | default: 83 | w.sendReject(msg.Header.ID, stream, ErrMsgTypeInvalid) 84 | } 85 | 86 | err = stream.Close() 87 | if err != nil { 88 | log.Error(err) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_chain.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | "github.com/libp2p/go-libp2p/core/network" 6 | 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | // sendChain will send peer the specific vault's sendChain, which's len is not must be full BlockCheckRound num. 11 | func (w *Wired) sendChain(uuid []byte, stream network.Stream, blocks ...*ngtypes.FullBlock) bool { 12 | if len(blocks) == 0 { 13 | return false 14 | } 15 | 16 | log.Debugf("replying sendChain to %s. Message id: %x, from block@%d to %d", 17 | stream.Conn().RemotePeer(), uuid, blocks[0].GetHeight(), blocks[len(blocks)-1].GetHeight(), 18 | ) 19 | 20 | protoBlocks := make([]*ngtypes.FullBlock, len(blocks)) 21 | for i := 0; i < len(blocks); i++ { 22 | protoBlocks[i] = blocks[i] 23 | } 24 | 25 | payload, err := rlp.EncodeToBytes(&ChainPayload{ 26 | Blocks: protoBlocks, 27 | }) 28 | if err != nil { 29 | log.Debugf("failed to sign pb data: %s", err) 30 | return false 31 | } 32 | 33 | // create message data 34 | resp := &Message{ 35 | Header: NewHeader(w.host, w.network, uuid, ChainMsg), 36 | Payload: payload, 37 | } 38 | 39 | // sign the data 40 | signature, err := Signature(w.host, resp) 41 | if err != nil { 42 | log.Debugf("failed to sign pb data") 43 | return false 44 | } 45 | 46 | // add the signature to the message 47 | resp.Header.Sign = signature 48 | 49 | err = Reply(stream, resp) 50 | if err != nil { 51 | log.Debugf("sendChain to: %s was sent. Message Id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 52 | return false 53 | } 54 | 55 | log.Debugf("sendChain to: %s was sent. Message Id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 56 | 57 | return true 58 | } 59 | 60 | // DecodeChainPayload unmarshal the raw and return the *pb.ChainPayload. 61 | func DecodeChainPayload(rawPayload []byte) (*ChainPayload, error) { 62 | var payload ChainPayload 63 | 64 | err := rlp.DecodeBytes(rawPayload, &payload) 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | return &payload, nil 70 | } 71 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_getsheet.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | "github.com/google/uuid" 6 | "github.com/libp2p/go-libp2p/core/network" 7 | "github.com/libp2p/go-libp2p/core/peer" 8 | "github.com/ngchain/ngcore/ngstate" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | func (w *Wired) SendGetSheet(peerID peer.ID, checkpointHeight uint64, checkpointHash []byte) (id []byte, stream network.Stream, err error) { 13 | payload, err := rlp.EncodeToBytes(&GetSheetPayload{ 14 | Height: checkpointHeight, 15 | Hash: checkpointHash, 16 | }) 17 | if err != nil { 18 | err = errors.Wrapf(err, "failed to encode data into rlp") 19 | log.Debug(err) 20 | return nil, nil, err 21 | } 22 | 23 | id, _ = uuid.New().MarshalBinary() 24 | 25 | // create message data 26 | req := &Message{ 27 | Header: NewHeader(w.host, w.network, id, GetChainMsg), 28 | Payload: payload, 29 | } 30 | 31 | // sign the data 32 | signature, err := Signature(w.host, req) 33 | if err != nil { 34 | err = errors.Wrap(err, "failed to sign pb data") 35 | log.Debug(err) 36 | return nil, nil, err 37 | } 38 | 39 | // add the signature to the message 40 | req.Header.Sign = signature 41 | 42 | stream, err = Send(w.host, w.protocolID, peerID, req) 43 | if err != nil { 44 | log.Debug(err) 45 | return nil, nil, err 46 | } 47 | 48 | log.Debugf("getsheet to: %s was sent. Message Id: %x, request sheet @%d: %x", peerID, req.Header.ID, checkpointHeight, checkpointHash) 49 | 50 | return req.Header.ID, stream, nil 51 | } 52 | 53 | func (w *Wired) onGetSheet(stream network.Stream, msg *Message) { 54 | log.Debugf("Received getsheet request from %s.", stream.Conn().RemotePeer()) 55 | 56 | var getSheetPayload GetSheetPayload 57 | 58 | err := rlp.DecodeBytes(msg.Payload, &getSheetPayload) 59 | if err != nil { 60 | w.sendReject(msg.Header.ID, stream, err) 61 | return 62 | } 63 | 64 | log.Debugf("getsheet requests sheet@%d: %x", getSheetPayload.Height, getSheetPayload.Hash) 65 | 66 | sheet := w.chain.GetSnapshot(getSheetPayload.Height, getSheetPayload.Hash) 67 | if sheet == nil { 68 | w.sendReject(msg.Header.ID, stream, ngstate.ErrSnapshotNofFound) 69 | } 70 | 71 | w.sendSheet(msg.Header.ID, stream, sheet) 72 | } 73 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_ping.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | "github.com/google/uuid" 6 | "github.com/libp2p/go-libp2p/core/network" 7 | "github.com/libp2p/go-libp2p/core/peer" 8 | ) 9 | 10 | func (w *Wired) SendPing(peerID peer.ID, origin, latest uint64, checkpointHash, checkpointActualDiff []byte) (id []byte, 11 | stream network.Stream) { 12 | payload, err := rlp.EncodeToBytes(&StatusPayload{ 13 | Origin: origin, 14 | Latest: latest, 15 | CheckpointHash: checkpointHash, 16 | CheckpointDiff: checkpointActualDiff, 17 | }) 18 | if err != nil { 19 | log.Debugf("failed to sign pb data") 20 | return nil, nil 21 | } 22 | 23 | id, _ = uuid.New().MarshalBinary() 24 | 25 | // create message data 26 | req := &Message{ 27 | Header: NewHeader(w.host, w.network, id, PingMsg), 28 | Payload: payload, 29 | } 30 | 31 | // sign the data 32 | signature, err := Signature(w.host, req) 33 | if err != nil { 34 | log.Debugf("failed to sign pb data, %s", err) 35 | return nil, nil 36 | } 37 | 38 | // add the signature to the message 39 | req.Header.Sign = signature 40 | 41 | log.Debugf("Sent ping to: %s was sent. Message Id: %x", peerID, req.Header.ID) 42 | 43 | stream, err = Send(w.host, w.protocolID, peerID, req) 44 | if err != nil { 45 | log.Debugf("failed sending ping to: %s: %s", peerID, err) 46 | return nil, nil 47 | } 48 | 49 | return req.Header.ID, stream 50 | } 51 | 52 | // remote peer requests handler. 53 | func (w *Wired) onPing(stream network.Stream, msg *Message) { 54 | log.Debugf("Received remoteStatus request from %s.", stream.Conn().RemotePeer()) 55 | 56 | var remoteStatus StatusPayload 57 | err := rlp.DecodeBytes(msg.Payload, &remoteStatus) 58 | if err != nil { 59 | w.sendReject(msg.Header.ID, stream, err) 60 | return 61 | } 62 | 63 | // send sendPong 64 | origin := w.chain.GetOriginBlock() 65 | latest := w.chain.GetLatestBlock() 66 | checkpoint := w.chain.GetLatestCheckpoint() 67 | w.sendPong(msg.Header.ID, stream, origin.GetHeight(), latest.GetHeight(), checkpoint.GetHash(), checkpoint.GetActualDiff().Bytes()) 68 | } 69 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_pong.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | "github.com/libp2p/go-libp2p/core/network" 6 | ) 7 | 8 | func (w *Wired) sendPong(uuid []byte, stream network.Stream, origin, latest uint64, checkpointHash, checkpointActualDiff []byte) bool { 9 | log.Debugf("sending pong to %s. Message id: %x...", stream.Conn().RemotePeer(), uuid) 10 | 11 | pongPayload := &StatusPayload{ 12 | Origin: origin, 13 | Latest: latest, 14 | CheckpointHash: checkpointHash, 15 | CheckpointDiff: checkpointActualDiff, 16 | } 17 | 18 | rawPayload, err := rlp.EncodeToBytes(pongPayload) 19 | if err != nil { 20 | return false 21 | } 22 | 23 | resp := &Message{ 24 | Header: NewHeader(w.host, w.network, uuid, PongMsg), 25 | Payload: rawPayload, 26 | } 27 | 28 | // sign the data 29 | signature, err := Signature(w.host, resp) 30 | if err != nil { 31 | log.Debugf("failed to sign response") 32 | return false 33 | } 34 | 35 | // add the signature to the message 36 | resp.Header.Sign = signature 37 | 38 | // send the response 39 | err = Reply(stream, resp) 40 | if err != nil { 41 | log.Debugf("failed sending pong to: %s: %s", stream.Conn().RemotePeer(), err) 42 | return false 43 | } 44 | 45 | log.Debugf("sent pong to: %s with message id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 46 | 47 | return true 48 | } 49 | 50 | // DecodePongPayload unmarshal the raw and return the *message.PongPayload. 51 | func DecodePongPayload(rawPayload []byte) (*StatusPayload, error) { 52 | var pongPayload StatusPayload 53 | 54 | err := rlp.DecodeBytes(rawPayload, &pongPayload) 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return &pongPayload, nil 60 | } 61 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_reject.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/libp2p/go-libp2p/core/network" 5 | ) 6 | 7 | // sendReject will reply sendReject message to remote node. 8 | func (w *Wired) sendReject(uuid []byte, stream network.Stream, err error) bool { 9 | log.Debugf("sending sendReject to %s with message id: %x...", stream.Conn().RemotePeer(), uuid) 10 | 11 | resp := &Message{ 12 | Header: NewHeader(w.host, w.network, uuid, RejectMsg), 13 | Payload: []byte(err.Error()), 14 | } 15 | 16 | // sign the data 17 | signature, err := Signature(w.host, resp) 18 | if err != nil { 19 | log.Debugf("failed to sign response") 20 | return false 21 | } 22 | 23 | // add the signature to the message 24 | resp.Header.Sign = signature 25 | 26 | // send the response 27 | err = Reply(stream, resp) 28 | if err != nil { 29 | log.Debugf("sent sendChain to: %s was with message Id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 30 | return false 31 | } 32 | 33 | log.Debugf("sent sendChain to: %s with message Id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 34 | 35 | return true 36 | } 37 | -------------------------------------------------------------------------------- /ngp2p/wired/wired_sheet.go: -------------------------------------------------------------------------------- 1 | package wired 2 | 3 | import ( 4 | "github.com/c0mm4nd/rlp" 5 | "github.com/libp2p/go-libp2p/core/network" 6 | 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | func (w *Wired) sendSheet(uuid []byte, stream network.Stream, sheet *ngtypes.Sheet) bool { 11 | log.Debugf("sending sheet to %s. Message id: %x...", stream.Conn().RemotePeer(), uuid) 12 | 13 | pongPayload := &SheetPayload{ 14 | Sheet: sheet, 15 | } 16 | 17 | rawPayload, err := rlp.EncodeToBytes(pongPayload) 18 | if err != nil { 19 | return false 20 | } 21 | 22 | resp := &Message{ 23 | Header: NewHeader(w.host, w.network, uuid, PongMsg), 24 | Payload: rawPayload, 25 | } 26 | 27 | // sign the data 28 | signature, err := Signature(w.host, resp) 29 | if err != nil { 30 | log.Debugf("failed to sign response") 31 | return false 32 | } 33 | 34 | // add the signature to the message 35 | resp.Header.Sign = signature 36 | 37 | // send the response 38 | err = Reply(stream, resp) 39 | if err != nil { 40 | log.Debugf("failed sending sheet to: %s: %s", stream.Conn().RemotePeer(), err) 41 | return false 42 | } 43 | 44 | log.Debugf("sent sheet to: %s with message id: %x", stream.Conn().RemotePeer(), resp.Header.ID) 45 | 46 | return true 47 | } 48 | 49 | // DecodeSheetPayload unmarshal the raw and return the *message.PongPayload. 50 | func DecodeSheetPayload(rawPayload []byte) (*SheetPayload, error) { 51 | var sheetPayload SheetPayload 52 | 53 | err := rlp.DecodeBytes(rawPayload, &sheetPayload) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | return &sheetPayload, nil 59 | } 60 | -------------------------------------------------------------------------------- /ngpool/doc.go: -------------------------------------------------------------------------------- 1 | // Package ngpool is an autonomous module to handle the new-coming txs from internal or external events 2 | // ngpool's use requires some other module's initialization: 3 | // - blockchain(and ngblocks) 4 | // - ngstate 5 | // - ngp2p 6 | // - storage 7 | package ngpool 8 | -------------------------------------------------------------------------------- /ngpool/pool.go: -------------------------------------------------------------------------------- 1 | package ngpool 2 | 3 | import ( 4 | "bytes" 5 | "sync" 6 | 7 | "go.etcd.io/bbolt" 8 | logging "github.com/ngchain/zap-log" 9 | 10 | "github.com/ngchain/ngcore/blockchain" 11 | "github.com/ngchain/ngcore/ngp2p" 12 | "github.com/ngchain/ngcore/ngtypes" 13 | ) 14 | 15 | var log = logging.Logger("ngpool") 16 | 17 | // TxPool is a little mem db which stores **signed** tx. 18 | // RULE: One Account can only send one Tx, all Txs will be accepted 19 | // Every time the state updated, the old pool will be deprecated. 20 | type TxPool struct { 21 | sync.Mutex 22 | 23 | db *bbolt.DB 24 | txMap map[uint64]*ngtypes.FullTx // priority first 25 | 26 | chain *blockchain.Chain 27 | localNode *ngp2p.LocalNode 28 | } 29 | 30 | func Init(db *bbolt.DB, chain *blockchain.Chain, localNode *ngp2p.LocalNode) *TxPool { 31 | pool := &TxPool{ 32 | Mutex: sync.Mutex{}, 33 | db: db, 34 | txMap: make(map[uint64]*ngtypes.FullTx), 35 | 36 | chain: chain, 37 | localNode: localNode, 38 | } 39 | 40 | return pool 41 | } 42 | 43 | // IsInPool checks one tx is in pool or not. 44 | func (pool *TxPool) IsInPool(txHash []byte) (exists bool, inPoolTx *ngtypes.FullTx) { 45 | for _, txInQueue := range pool.txMap { 46 | if bytes.Equal(txInQueue.GetHash(), txHash) { 47 | return true, txInQueue 48 | } 49 | } 50 | 51 | return false, nil 52 | } 53 | 54 | // Reset cleans all txs inside the pool. 55 | func (pool *TxPool) Reset() { 56 | pool.txMap = make(map[uint64]*ngtypes.FullTx) 57 | } 58 | -------------------------------------------------------------------------------- /ngpool/pool_get.go: -------------------------------------------------------------------------------- 1 | package ngpool 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/ngchain/ngcore/ngtypes" 7 | ) 8 | 9 | // GetPack will gives a sorted TxTire. 10 | func (pool *TxPool) GetPack(height uint64) ngtypes.TxTrie { 11 | txs := make([]*ngtypes.FullTx, 0) 12 | accountNums := make([]uint64, 0) 13 | 14 | for num := range pool.txMap { 15 | accountNums = append(accountNums, num) 16 | } 17 | 18 | sort.Slice(accountNums, func(i, j int) bool { return accountNums[i] < accountNums[j] }) 19 | 20 | for _, num := range accountNums { 21 | if pool.txMap[num] != nil && pool.txMap[num].Height == height { 22 | txs = append(txs, pool.txMap[num]) 23 | } 24 | } 25 | 26 | trie := ngtypes.NewTxTrie(txs) 27 | // trie.Sort() 28 | 29 | return trie 30 | } 31 | 32 | // GetPackTxs limits the sorted TxTire's txs to meet block txs requirements. 33 | // func (p *TxPool) GetPackTxs() []*ngtypes.Tx { 34 | // txs := p.GetPack().Txs 35 | // size := 0 36 | 37 | // for i := 0; i < len(txs); i++ { 38 | // size += proto.Size(txs[i]) 39 | // if size > ngtypes.BlockMaxTxsSize { 40 | // return txs[:i] 41 | // } 42 | // } 43 | 44 | // return txs 45 | // } 46 | -------------------------------------------------------------------------------- /ngpool/pool_put.go: -------------------------------------------------------------------------------- 1 | package ngpool 2 | 3 | import ( 4 | "go.etcd.io/bbolt" 5 | "github.com/pkg/errors" 6 | 7 | "github.com/ngchain/ngcore/ngstate" 8 | "github.com/ngchain/ngcore/ngtypes" 9 | ) 10 | 11 | // PutNewTxFromLocal puts tx from local(rpc) into txpool. 12 | func (pool *TxPool) PutNewTxFromLocal(tx *ngtypes.FullTx) (err error) { 13 | log.Debugf("putting new tx %x from rpc", tx.GetHash()) 14 | 15 | err = pool.PutTx(tx) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | err = pool.localNode.BroadcastTx(tx) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | return nil 26 | } 27 | 28 | // PutNewTxFromRemote puts tx from local(rpc) into txpool. 29 | func (pool *TxPool) PutNewTxFromRemote(tx *ngtypes.FullTx) (err error) { 30 | log.Debugf("putting new tx %x from p2p", tx.GetHash()) 31 | 32 | err = pool.PutTx(tx) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | return nil 38 | } 39 | 40 | var ErrTxInvalidHeight = errors.New("invalid tx height") 41 | 42 | // PutTx puts txs from network(p2p) or RPC into txpool, should check error before putting. 43 | func (pool *TxPool) PutTx(tx *ngtypes.FullTx) error { 44 | pool.Lock() 45 | defer pool.Unlock() 46 | 47 | err := pool.db.View(func(txn *bbolt.Tx) error { 48 | if err := ngstate.CheckTx(txn, tx); err != nil { 49 | return errors.Wrap(err, "malformed tx, rejected") 50 | } 51 | 52 | return nil 53 | }) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | latestBlock := pool.chain.GetLatestBlock().(*ngtypes.FullBlock) 59 | 60 | if tx.Height != latestBlock.GetHeight() { 61 | return errors.Wrapf(ErrTxInvalidHeight, "tx %x does not belong to current State, found %d, require %d", 62 | tx.GetHash(), tx.Height, latestBlock.GetHeight()) 63 | } 64 | 65 | if pool.txMap[uint64(tx.Convener)] == nil || 66 | pool.txMap[uint64(tx.Convener)].Fee.Cmp(tx.Fee) < 0 { 67 | pool.txMap[uint64(tx.Convener)] = tx 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /ngpool/pool_test.go: -------------------------------------------------------------------------------- 1 | package ngpool_test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestInitialState(t *testing.T) { 8 | // state := ngstate.GetActiveState() 9 | // state.ToSheet() 10 | // _, err := state.GetTotalBalanceByNum(1) 11 | // if err != nil { 12 | // panic(err) 13 | // } 14 | // _, err = state.GetTotalBalanceByAddress(ngtypes.GenesisAddress) 15 | // if err != nil { 16 | // panic(err) 17 | // } 18 | } 19 | 20 | func TestTxPool(t *testing.T) { 21 | // tx := ngtypes.NewUnsignedTx(ngproto.TxType_TRANSACTION, ngtypes.GetGenesisBlockHash(), 1) 22 | // pool.PutTx(tx) 23 | } 24 | -------------------------------------------------------------------------------- /ngstate/doc.go: -------------------------------------------------------------------------------- 1 | // Package ngstate provides a global account status manager which controls all accounts and balance 2 | // besides that, the manager can do generating current sheet for fast sync 3 | // 4 | // ngstate can get init from genesis sheet or remote sheet. 5 | // 6 | // To use ngstate, dev should init these modules first 7 | // - ngblocks 8 | // - storage 9 | package ngstate 10 | -------------------------------------------------------------------------------- /ngstate/errors.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import "github.com/pkg/errors" 4 | 5 | var ErrSnapshotNofFound = errors.New("cannot find the snapshot") 6 | -------------------------------------------------------------------------------- /ngstate/events.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman" 5 | ) 6 | 7 | // CallOnTx when applying new tx 8 | func (vm *VM) CallOnTx(ins *wasman.Instance) { 9 | vm.RLock() 10 | defer vm.RUnlock() 11 | 12 | // TODO: add tx into the external modules 13 | 14 | _, _, err := ins.CallExportedFunc("main") // main's params should be ( i64) 15 | if err != nil { 16 | vm.logger.Error(err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ngstate/hooks.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | //export CreateTx(i32, i64, i64, i64) (i32, i32) 4 | 5 | //export AddParticipant 6 | // func AddParticipant(to int64, value int64) (rawPtr, rawLen int32) { 7 | // } 8 | 9 | //export SendTx 10 | // func SendTx() { 11 | // } 12 | -------------------------------------------------------------------------------- /ngstate/imports.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman" 5 | ) 6 | 7 | // InitBuiltInImports will bind go's host func with the contract module 8 | func (vm *VM) InitBuiltInImports() error { 9 | err := initLogImports(vm) 10 | if err != nil { 11 | return err 12 | } 13 | 14 | err = initAccountImports(vm) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | err = initCoinImports(vm) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | err = initTxImports(vm) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | return nil 30 | } 31 | 32 | func initLogImports(vm *VM) error { 33 | err := vm.linker.DefineAdvancedFunc("log", "debug", func(ins *wasman.Instance) interface{} { 34 | return func(ptr uint32, size uint32) { 35 | message := string(ins.Memory.Value[ptr : ptr+size]) 36 | // TODO: turn off debug by default 37 | // RULE: add --vm-debug , to enable debug 38 | vm.logger.Debug(message) 39 | } 40 | }) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func initCoinImports(vm *VM) error { 49 | err := vm.linker.DefineAdvancedFunc("coin", "transfer", func(ins *wasman.Instance) interface{} { 50 | return func(to, value int64) int32 { 51 | err := vmTransfer(vm.txn, vm.self.Num, uint64(to), uint64(value)) 52 | if err != nil { 53 | vm.logger.Error(err) 54 | return 0 55 | } 56 | 57 | return 1 58 | } 59 | }) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | return nil 65 | } 66 | -------------------------------------------------------------------------------- /ngstate/imports_account.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman" 5 | 6 | "github.com/ngchain/ngcore/ngtypes" 7 | ) 8 | 9 | func initAccountImports(vm *VM) error { 10 | err := vm.linker.DefineAdvancedFunc("account", "get_host", func(ins *wasman.Instance) interface{} { 11 | return func() uint64 { 12 | return vm.self.Num // host means the account which is hosting the contract 13 | } 14 | }) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | err = vm.linker.DefineAdvancedFunc("account", "get_owner_size", func(ins *wasman.Instance) interface{} { 20 | return func(accountNum uint64) uint32 { 21 | return ngtypes.AddressSize // addr is 35 bytes 22 | } 23 | }) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | err = vm.linker.DefineAdvancedFunc("account", "get_owner", func(ins *wasman.Instance) interface{} { 29 | return func(accountNum uint64, ptr uint32) uint32 { 30 | acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 31 | if err != nil { 32 | vm.logger.Error(err) 33 | return 0 34 | } 35 | 36 | l, err := cp(ins, ptr, acc.Owner[:]) 37 | if err != nil { 38 | vm.logger.Error(err) 39 | return 0 40 | } 41 | 42 | return l 43 | } 44 | }) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | err = vm.linker.DefineAdvancedFunc("account", "get_contract_size", func(ins *wasman.Instance) interface{} { 50 | return func(accountNum uint64) uint32 { 51 | acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 52 | if err != nil { 53 | vm.logger.Error(err) 54 | return 0 55 | } 56 | 57 | return uint32(len(acc.Contract)) 58 | } 59 | }) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | err = vm.linker.DefineAdvancedFunc("account", "get_contract", func(ins *wasman.Instance) interface{} { 65 | return func(accountNum uint64, ptr uint32) uint32 { 66 | acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 67 | if err != nil { 68 | vm.logger.Error(err) 69 | return 0 70 | } 71 | 72 | l, err := cp(ins, ptr, acc.Contract) 73 | if err != nil { 74 | vm.logger.Error(err) 75 | return 0 76 | } 77 | 78 | return l 79 | } 80 | }) 81 | if err != nil { 82 | return err 83 | } 84 | 85 | // err = vm.linker.DefineAdvancedFunc("account", "get_context_size", func(ins *wasman.Instance) interface{} { 86 | // return func(accountNum uint64) uint32 { 87 | // acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 88 | // if err != nil { 89 | // vm.logger.Error(err) 90 | // return 0 91 | // } 92 | // 93 | // return uint32(len(acc.Context)) 94 | // } 95 | // }) 96 | // if err != nil { 97 | // return err 98 | // } 99 | 100 | // TODO: use kv setter and getter 101 | // err = vm.linker.DefineAdvancedFunc("account", "get_context", func(ins *wasman.Instance) interface{} { 102 | // return func(accountNum uint64, ptr uint32) uint32 { 103 | // acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 104 | // if err != nil { 105 | // vm.logger.Error(err) 106 | // return 0 107 | // } 108 | // 109 | // l, err := cp(ins, ptr, acc.Context) 110 | // if err != nil { 111 | // vm.logger.Error(err) 112 | // return 0 113 | // } 114 | // 115 | // return l 116 | // } 117 | // }) 118 | // if err != nil { 119 | // return err 120 | // } 121 | 122 | // TODO: write to Context when num is self 123 | // err = vm.linker.DefineAdvancedFunc("account", "write_context", func(ins *wasman.Instance) interface{} { 124 | // return func(accountNum uint64, ptr uint32) uint32 { 125 | // acc, err := getAccountByNum(vm.txn, ngtypes.AccountNum(accountNum)) 126 | // if err != nil { 127 | // vm.logger.Error(err) 128 | // return 0 129 | // } 130 | // 131 | // l, err := cp(ins, ptr, acc.Context) 132 | // if err != nil { 133 | // vm.logger.Error(err) 134 | // return 0 135 | // } 136 | // 137 | // return l 138 | // } 139 | // }) 140 | // if err != nil { 141 | // return err 142 | // } 143 | 144 | return nil 145 | } 146 | -------------------------------------------------------------------------------- /ngstate/state_helper.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/c0mm4nd/rlp" 7 | "go.etcd.io/bbolt" 8 | "github.com/pkg/errors" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | "github.com/ngchain/ngcore/storage" 12 | ) 13 | 14 | func getAccountByNum(txn *bbolt.Tx, num ngtypes.AccountNum) (*ngtypes.Account, error) { 15 | num2accBucket := txn.Bucket(storage.Num2AccBucketName) 16 | 17 | rawAcc := num2accBucket.Get(num.Bytes()) 18 | if rawAcc != nil { 19 | return nil, errors.Wrapf(storage.ErrKeyNotFound, "cannot find account %d", num) 20 | } 21 | 22 | var acc ngtypes.Account 23 | err := rlp.DecodeBytes(rawAcc, &acc) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | return &acc, nil 29 | } 30 | 31 | // DONE: make sure num/addr = 1/1 32 | func getAccountNumByAddr(txn *bbolt.Tx, addr ngtypes.Address) (ngtypes.AccountNum, error) { 33 | addr2numBucket := txn.Bucket(storage.Addr2NumBucketName) 34 | 35 | rawNum := addr2numBucket.Get(addr[:]) 36 | if rawNum == nil { 37 | return 0, errors.Wrapf(storage.ErrKeyNotFound, "cannot find %s's account", addr) 38 | } 39 | 40 | num := ngtypes.NewNumFromBytes(rawNum) 41 | 42 | return num, nil 43 | } 44 | 45 | func getBalance(txn *bbolt.Tx, addr ngtypes.Address) *big.Int { 46 | addr2balBucket := txn.Bucket(storage.Addr2BalBucketName) 47 | 48 | rawBalance := addr2balBucket.Get(addr[:]) 49 | if rawBalance == nil { 50 | return big.NewInt(0) 51 | } 52 | 53 | return new(big.Int).SetBytes(rawBalance) 54 | } 55 | 56 | func setAccount(txn *bbolt.Tx, num ngtypes.AccountNum, account *ngtypes.Account) error { 57 | rawAccount, err := rlp.EncodeToBytes(account) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | num2accBucket := txn.Bucket(storage.Num2AccBucketName) 63 | err = num2accBucket.Put(num.Bytes(), rawAccount) 64 | if err != nil { 65 | return errors.Wrap(err, "cannot set account") 66 | } 67 | 68 | return nil 69 | } 70 | 71 | func setBalance(txn *bbolt.Tx, addr ngtypes.Address, balance *big.Int) error { 72 | addr2balBucket := txn.Bucket(storage.Addr2BalBucketName) 73 | 74 | err := addr2balBucket.Put(addr[:], balance.Bytes()) 75 | if err != nil { 76 | return errors.Wrapf(err, "failed to set balance") 77 | } 78 | 79 | return nil 80 | } 81 | 82 | func delAccount(txn *bbolt.Tx, num ngtypes.AccountNum) error { 83 | num2accBucket := txn.Bucket(storage.Num2AccBucketName) 84 | 85 | return num2accBucket.Delete(num.Bytes()) 86 | } 87 | 88 | func setOwnership(txn *bbolt.Tx, addr ngtypes.Address, num ngtypes.AccountNum) error { 89 | addr2numBucket := txn.Bucket(storage.Addr2NumBucketName) 90 | 91 | err := addr2numBucket.Put(addr[:], num.Bytes()) 92 | if err != nil { 93 | return errors.Wrap(err, "cannot set ownership: %s") 94 | } 95 | 96 | return nil 97 | } 98 | 99 | func delOwnership(txn *bbolt.Tx, addr ngtypes.Address) error { 100 | addr2numBucket := txn.Bucket(storage.Addr2NumBucketName) 101 | 102 | return addr2numBucket.Delete(addr[:]) 103 | } 104 | 105 | func accountNumExists(txn *bbolt.Tx, num ngtypes.AccountNum) bool { 106 | num2accBucket := txn.Bucket(storage.Num2AccBucketName) 107 | 108 | return num2accBucket.Get(num.Bytes()) != nil 109 | } 110 | 111 | func addrHasAccount(txn *bbolt.Tx, addr ngtypes.Address) bool { 112 | addr2numBucket := txn.Bucket(storage.Addr2NumBucketName) 113 | 114 | return addr2numBucket.Get(addr[:]) != nil 115 | } 116 | -------------------------------------------------------------------------------- /ngstate/state_snapshot.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "sync" 7 | 8 | "github.com/c0mm4nd/rlp" 9 | "go.etcd.io/bbolt" 10 | 11 | "github.com/ngchain/ngcore/ngblocks" 12 | "github.com/ngchain/ngcore/ngtypes" 13 | "github.com/ngchain/ngcore/storage" 14 | ) 15 | 16 | // var snapshot *atomic.Value 17 | 18 | type SnapshotManager struct { 19 | sync.RWMutex 20 | heightToHash map[uint64]string 21 | hashToSnapshot map[string]*ngtypes.Sheet // hash->sheet 22 | } 23 | 24 | func (sm *SnapshotManager) PutSnapshot(height uint64, hash []byte, sheet *ngtypes.Sheet) { 25 | sm.Lock() 26 | defer sm.Unlock() 27 | 28 | hexHash := hex.EncodeToString(hash) 29 | 30 | sm.heightToHash[height] = hexHash 31 | sm.hashToSnapshot[hexHash] = sheet 32 | } 33 | 34 | // GetSnapshot return the snapshot in a balance sheet at a height, and doo hash check 35 | // for external use with security ensure 36 | func (sm *SnapshotManager) GetSnapshot(height uint64, hash []byte) *ngtypes.Sheet { 37 | sm.RLock() 38 | defer sm.RLocker() 39 | 40 | hexHash, exists := sm.heightToHash[height] 41 | if !exists { 42 | return nil 43 | } 44 | 45 | if hexHash != hex.EncodeToString(hash) { 46 | return nil 47 | } 48 | 49 | return sm.hashToSnapshot[hexHash] 50 | } 51 | 52 | // GetSnapshotByHeight return the snapshot in a balance sheet at a height, without hash check 53 | // for internal use only 54 | func (sm *SnapshotManager) GetSnapshotByHeight(height uint64) *ngtypes.Sheet { 55 | sm.RLock() 56 | defer sm.RLocker() 57 | 58 | hexHash, exists := sm.heightToHash[height] 59 | if !exists { 60 | return nil 61 | } 62 | 63 | return sm.hashToSnapshot[hexHash] 64 | } 65 | 66 | // GetSnapshotByHash return the snapshot in a balance sheet with the hash 67 | // for internal use only 68 | func (sm *SnapshotManager) GetSnapshotByHash(hash []byte) *ngtypes.Sheet { 69 | sm.RLock() 70 | defer sm.RLocker() 71 | 72 | return sm.hashToSnapshot[hex.EncodeToString(hash)] 73 | } 74 | 75 | // generateSnapshot when the block is a checkpoint 76 | func (state *State) generateSnapshot(txn *bbolt.Tx) error { 77 | accounts := make([]*ngtypes.Account, 0) 78 | balances := make([]*ngtypes.Balance, 0) 79 | 80 | blockBucket := txn.Bucket(storage.BlockBucketName) 81 | latestBlock, err := ngblocks.GetLatestBlock(blockBucket) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | num2accBucket := txn.Bucket(storage.Num2AccBucketName) 87 | c := num2accBucket.Cursor() 88 | for num, rawAccount := c.Seek(nil); num != nil; c.Next() { 89 | var account ngtypes.Account 90 | err = rlp.DecodeBytes(rawAccount, &account) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | accounts = append(accounts, &account) 96 | } 97 | 98 | addr2balBucket := txn.Bucket(storage.Addr2BalBucketName) 99 | c = addr2balBucket.Cursor() 100 | 101 | for addr, rawBalance := c.Seek(nil); addr != nil; c.Next() { 102 | balances = append(balances, &ngtypes.Balance{ 103 | Address: new(ngtypes.Address).SetBytes(addr), 104 | Amount: new(big.Int).SetBytes(rawBalance), 105 | }) 106 | } 107 | 108 | sheet := ngtypes.NewSheet(state.Network, latestBlock.GetHeight(), latestBlock.GetHash(), balances, accounts) 109 | state.SnapshotManager.PutSnapshot(latestBlock.GetHeight(), latestBlock.GetHash(), sheet) 110 | return nil 111 | } 112 | 113 | func (state *State) GetSnapshot(height uint64, hash []byte) *ngtypes.Sheet { 114 | return state.SnapshotManager.GetSnapshot(height, hash) 115 | } 116 | -------------------------------------------------------------------------------- /ngstate/test/README.md: -------------------------------------------------------------------------------- 1 | # WASM for Test 2 | 3 | this file is a placeholder. 4 | -------------------------------------------------------------------------------- /ngstate/test/contract.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngchain/ngcore/e87e1d8a3d70baf5b6cd082c4ae150953c8093df/ngstate/test/contract.wasm -------------------------------------------------------------------------------- /ngstate/utils.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "github.com/c0mm4nd/wasman" 5 | "github.com/pkg/errors" 6 | ) 7 | 8 | var ErrOutOfMem = errors.New("out of the allocated memory") 9 | 10 | func cp(ins *wasman.Instance, ptr uint32, data []byte) (uint32, error) { 11 | if len(ins.Memory.Value[ptr:]) < len(data) { 12 | return 0, errors.Wrapf(ErrOutOfMem, "memory is not enough for data: %s", data) 13 | } 14 | 15 | l := copy(ins.Memory.Value[ptr:], data) 16 | return uint32(l), nil 17 | } 18 | -------------------------------------------------------------------------------- /ngstate/vm_func.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "math/big" 5 | 6 | "go.etcd.io/bbolt" 7 | "github.com/ngchain/ngcore/ngtypes" 8 | ) 9 | 10 | func vmTransfer(txn *bbolt.Tx, from, to, value uint64) error { 11 | bigValue := new(big.Int).SetUint64(value) 12 | 13 | convener, err := getAccountByNum(txn, ngtypes.AccountNum(from)) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | convenerBalance := getBalance(txn, convener.Owner) 19 | 20 | if convenerBalance.Cmp(bigValue) < 0 { 21 | return ErrTxrBalanceInsufficient 22 | } 23 | err = setBalance(txn, convener.Owner, new(big.Int).Sub(convenerBalance, bigValue)) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | participant, err := getAccountByNum(txn, ngtypes.AccountNum(to)) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | participantBalance := getBalance(txn, participant.Owner) 34 | 35 | err = setBalance(txn, participant.Owner, new(big.Int).Add( 36 | participantBalance, 37 | bigValue, 38 | )) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | err = setAccount(txn, ngtypes.AccountNum(from), convener) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /ngstate/wasm.go: -------------------------------------------------------------------------------- 1 | package ngstate 2 | 3 | import ( 4 | "bytes" 5 | "strconv" 6 | "sync" 7 | 8 | "github.com/c0mm4nd/wasman" 9 | "github.com/c0mm4nd/wasman/config" 10 | "go.etcd.io/bbolt" 11 | logging "github.com/ngchain/zap-log" 12 | 13 | "github.com/ngchain/ngcore/ngtypes" 14 | ) 15 | 16 | // VM is a vm based on wasmtime, which acts as a sandbox env to exec native func 17 | type VM struct { 18 | sync.RWMutex 19 | 20 | caller *ngtypes.FullTx 21 | self *ngtypes.Account 22 | txn *bbolt.Tx 23 | 24 | linker *wasman.Linker 25 | module *wasman.Module 26 | 27 | logger *logging.ZapEventLogger 28 | } 29 | 30 | // NewVM creates a new Wasm 31 | // call me when a assign or append tx 32 | func NewVM(txn *bbolt.Tx, account *ngtypes.Account) (*VM, error) { 33 | module, err := wasman.NewModule(config.ModuleConfig{}, bytes.NewBuffer(account.Contract)) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | linker := wasman.NewLinker(config.LinkerConfig{}) // TODO: add external modules 39 | 40 | return &VM{ 41 | RWMutex: sync.RWMutex{}, 42 | self: account, 43 | txn: txn, 44 | linker: linker, 45 | module: module, 46 | logger: logging.Logger("vm" + strconv.FormatUint(account.Num, 10)), 47 | }, nil 48 | } 49 | 50 | // Instantiate will generate a runnable instance from thr module 51 | // before Instantiate, the caller should run Init 52 | func (vm *VM) Instantiate(tx *ngtypes.FullTx) (*wasman.Instance, error) { 53 | vm.caller = tx 54 | 55 | instance, err := vm.linker.Instantiate(vm.module) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | return instance, nil 61 | } 62 | -------------------------------------------------------------------------------- /ngstate/wasm_test.go: -------------------------------------------------------------------------------- 1 | package ngstate_test 2 | 3 | // TODO: uncomment when new engine done 4 | // func TestNewWasmVM(t *testing.T) { 5 | // // requires the consensus here 6 | // db := storage.InitMemStorage() 7 | // 8 | // f, _ := os.Open("test/contract.wasm") 9 | // raw, err := ioutil.ReadAll(f) // TODO: implement a mvp 10 | // if err != nil { 11 | // panic(err) 12 | // } 13 | // 14 | // _ = db.Update(func(txn *bbolt.Tx) error { 15 | // contract, err := ngstate.NewVM(txn, ngtypes.NewAccount(500, nil, raw, nil)) 16 | // if err != nil { 17 | // panic(err) 18 | // } 19 | // 20 | // err = contract.InitBuiltInImports() 21 | // if err != nil { 22 | // panic(err) 23 | // } 24 | // 25 | // err = contract.Instantiate() 26 | // if err != nil { 27 | // panic(err) 28 | // } 29 | // 30 | // fakeTx := ngtypes.NewUnsignedTx(ngproto.TxType_TRANSACTION, 31 | // nil, 32 | // 0, 33 | // [][]byte{ngtypes.GenesisAddress}, 34 | // []*big.Int{big.NewInt(0)}, 35 | // big.NewInt(0), 36 | // nil, 37 | // ) 38 | // contract.CallOnTx(fakeTx) // will receive error but main thread wont panic 39 | // 40 | // return nil 41 | // }) 42 | // 43 | // } 44 | -------------------------------------------------------------------------------- /ngtypes/abstract.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import "github.com/ngchain/secp256k1" 4 | 5 | var _ Tx = (*FullTx)(nil) 6 | 7 | // Tx is an abstract transaction interface. 8 | type Tx interface { 9 | GetHash() []byte 10 | IsSigned() bool 11 | } 12 | 13 | var _ Block = (*FullBlock)(nil) 14 | 15 | // var _ Block = (*BlockHeader)(nil) 16 | 17 | // Block is an abstract block interface. 18 | type Block interface { 19 | IsUnsealing() bool 20 | IsSealed() bool 21 | IsGenesis() bool 22 | GetPrevHash() []byte 23 | GetHash() []byte 24 | GetHeight() uint64 25 | GetTx(int) Tx 26 | GetTimestamp() uint64 27 | } 28 | 29 | type Chain interface { 30 | CheckBlock(Block) error 31 | GetLatestBlock() Block 32 | GetLatestBlockHash() []byte 33 | GetLatestBlockHeight() uint64 34 | GetBlockByHeight(height uint64) (Block, error) 35 | GetBlockByHash(hash []byte) (Block, error) 36 | } 37 | 38 | // Consensus is an abstract consensus interface. 39 | type Consensus interface { 40 | GoLoop() 41 | GetChain() Chain 42 | ImportBlock(Block) error 43 | GetBlockTemplate(privateKey *secp256k1.PrivateKey) Block 44 | } 45 | -------------------------------------------------------------------------------- /ngtypes/account.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "bytes" 5 | ) 6 | 7 | // Account is the shell of the address to process the txs and contracts 8 | type Account struct { 9 | Num uint64 10 | Owner Address 11 | Contract []byte 12 | Context *AccountContext 13 | } 14 | 15 | // NewAccount receive parameters and return a new Account(class constructor. 16 | func NewAccount(num AccountNum, ownerAddress Address, contract []byte, context *AccountContext) *Account { 17 | if context == nil { 18 | context = NewAccountContext() 19 | } 20 | 21 | return &Account{ 22 | Num: uint64(num), 23 | Owner: ownerAddress, 24 | Contract: contract, 25 | Context: context, 26 | } 27 | } 28 | 29 | // GetGenesisStyleAccount will return the genesis style account. 30 | func GetGenesisStyleAccount(num AccountNum) *Account { 31 | return NewAccount(num, GenesisAddress, nil, nil) 32 | } 33 | 34 | // Equals returns whether the other is equals to the Account 35 | func (x *Account) Equals(other *Account) (bool, error) { 36 | if !(x.Num == other.Num) { 37 | return false, nil 38 | } 39 | if x.Owner != other.Owner { 40 | return false, nil 41 | } 42 | if !(bytes.Equal(x.Contract, other.Contract)) { 43 | return false, nil 44 | } 45 | if eq, _ := x.Context.Equals(other.Context); !eq { 46 | return false, nil 47 | } 48 | 49 | return true, nil 50 | } 51 | -------------------------------------------------------------------------------- /ngtypes/account_context.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | "sync" 7 | 8 | "github.com/ngchain/ngcore/utils" 9 | ) 10 | 11 | // AccountContext is the Context field of the Account, which 12 | // is a in-mem (on-chain) k-v storage 13 | type AccountContext struct { 14 | Keys []string 15 | Values [][]byte 16 | 17 | mu *sync.RWMutex 18 | valMap map[string][]byte 19 | } 20 | 21 | // NewAccountContext craetes a new empty AccountContext 22 | func NewAccountContext() *AccountContext { 23 | return &AccountContext{ 24 | Keys: make([]string, 0), 25 | Values: make([][]byte, 0), 26 | valMap: make(map[string][]byte), 27 | } 28 | } 29 | 30 | // Set the k-v data 31 | func (ctx *AccountContext) Set(key string, val []byte) { 32 | ctx.mu.Lock() 33 | 34 | ctx.valMap[key] = val 35 | ctx.splitMap() 36 | 37 | ctx.mu.Unlock() 38 | } 39 | 40 | func (ctx *AccountContext) splitMap() { 41 | itemNum := len(ctx.valMap) 42 | 43 | keys := make([]string, itemNum) 44 | values := make([][]byte, itemNum) 45 | i := 0 46 | for k, v := range ctx.valMap { 47 | keys[i] = k 48 | values[i] = v 49 | i++ 50 | } 51 | 52 | ctx.Keys = keys 53 | ctx.Values = values 54 | } 55 | 56 | // Get the value by key 57 | func (ctx *AccountContext) Get(key string) []byte { 58 | ctx.mu.RLock() 59 | ret := ctx.valMap[key] 60 | ctx.mu.RUnlock() 61 | return ret 62 | } 63 | 64 | // Equals checks whether the other is same with this AccountContext 65 | func (ctx *AccountContext) Equals(other *AccountContext) (bool, error) { 66 | if len(ctx.valMap) != len(other.valMap) { 67 | return false, nil 68 | } 69 | 70 | for i := range other.valMap { 71 | if !bytes.Equal(other.valMap[i], ctx.valMap[i]) { 72 | return false, nil 73 | } 74 | } 75 | 76 | return true, nil 77 | } 78 | 79 | // MarshalJSON encodes the context as a map, with hex-encoded values 80 | func (ctx *AccountContext) MarshalJSON() ([]byte, error) { 81 | json := make(map[string]string, len(ctx.valMap)) 82 | for k, v := range ctx.valMap { 83 | json[k] = hex.EncodeToString(v) 84 | } 85 | 86 | return utils.JSON.Marshal(json) 87 | } 88 | 89 | // UnmarshalJSON decodes the AccountContext from the map with hex values 90 | func (ctx *AccountContext) UnmarshalJSON(raw []byte) error { 91 | var json map[string]string 92 | err := utils.JSON.Unmarshal(raw, &json) 93 | if err != nil { 94 | return err 95 | } 96 | 97 | valMap := make(map[string][]byte) 98 | for k, v := range json { 99 | val, err := hex.DecodeString(v) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | valMap[k] = val 105 | } 106 | 107 | ctx.valMap = valMap 108 | return nil 109 | } 110 | -------------------------------------------------------------------------------- /ngtypes/account_json.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "encoding/hex" 5 | 6 | "github.com/ngchain/ngcore/utils" 7 | ) 8 | 9 | type jsonAccount struct { 10 | Num uint64 `json:"num"` 11 | Owner Address `json:"owner"` 12 | 13 | Contract string `json:"contract"` 14 | Context *AccountContext `json:"context"` 15 | } 16 | 17 | // MarshalJSON converts the Account into json bytes 18 | func (x *Account) MarshalJSON() ([]byte, error) { 19 | return utils.JSON.Marshal(jsonAccount{ 20 | Num: x.Num, 21 | Owner: x.Owner, 22 | 23 | Contract: hex.EncodeToString(x.Contract), 24 | Context: x.Context, 25 | }) 26 | } 27 | 28 | // UnmarshalJSON recovers the Account from the json bytes 29 | func (x *Account) UnmarshalJSON(data []byte) error { 30 | var account jsonAccount 31 | err := utils.JSON.Unmarshal(data, &account) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | contract, err := hex.DecodeString(account.Contract) 37 | if err != nil { 38 | return err 39 | } 40 | 41 | *x = *NewAccount( 42 | AccountNum(account.Num), 43 | account.Owner, 44 | contract, 45 | account.Context, 46 | ) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /ngtypes/account_num.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import "encoding/binary" 4 | 5 | // AccountNum is a uint64 number which used as the identifier of the Account 6 | type AccountNum uint64 7 | 8 | // Bytes convert the uint64 AccountNum into bytes in LE 9 | func (num AccountNum) Bytes() []byte { 10 | b := make([]byte, 8) 11 | binary.LittleEndian.PutUint64(b, uint64(num)) 12 | return b 13 | } 14 | 15 | // NewNumFromBytes recovers the AccountNum from the bytes in LE 16 | func NewNumFromBytes(b []byte) AccountNum { 17 | return AccountNum(binary.LittleEndian.Uint64(b)) 18 | } 19 | -------------------------------------------------------------------------------- /ngtypes/account_test.go: -------------------------------------------------------------------------------- 1 | package ngtypes_test 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "github.com/ngchain/secp256k1" 8 | logging "github.com/ngchain/zap-log" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | "github.com/ngchain/ngcore/utils" 12 | ) 13 | 14 | var log = logging.Logger("ngtypes_test") 15 | 16 | // TestNewAccount is testing func NewAccount. 17 | func TestNewAccount(t *testing.T) { 18 | privateKey, err := secp256k1.GeneratePrivateKey() 19 | if err != nil { 20 | log.Error(err) 21 | } 22 | 23 | addr := ngtypes.NewAddress(privateKey) 24 | 25 | randUint64 := rand.Uint64() 26 | acc := ngtypes.NewAccount( 27 | ngtypes.AccountNum(randUint64), 28 | addr, 29 | // big.NewInt(0), 30 | nil, 31 | nil, 32 | ) 33 | t.Log(acc) 34 | } 35 | 36 | func TestJSONAccount(t *testing.T) { 37 | account1 := ngtypes.GetGenesisStyleAccount(1) 38 | jsonBlock, err := utils.JSON.Marshal(account1) 39 | if err != nil { 40 | t.Error(err) 41 | return 42 | } 43 | 44 | t.Log(string(jsonBlock)) 45 | 46 | account2 := &ngtypes.Account{} 47 | err = utils.JSON.Unmarshal(jsonBlock, account2) 48 | if err != nil { 49 | t.Error(err) 50 | return 51 | } 52 | 53 | if eq, _ := account1.Equals(account2); !eq { 54 | t.Errorf("account \n 2 %#v \n is different from \n 1 %#v", account2, account1) 55 | } 56 | 57 | if eq, _ := account1.Equals(account2); !eq { 58 | t.Errorf("account \n 2 %#v \n is different from \n 1 %#v", account2, account1) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ngtypes/address.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "github.com/mr-tron/base58" 5 | "github.com/ngchain/go-schnorr" 6 | "github.com/ngchain/secp256k1" 7 | 8 | "github.com/ngchain/ngcore/utils" 9 | ) 10 | 11 | // Address is the anonymous publickey for receiving coin 12 | type Address [33]byte 13 | 14 | // NewAddress will return a publickey address 15 | func NewAddress(privKey *secp256k1.PrivateKey) Address { 16 | addr := [33]byte{} 17 | 18 | copy(addr[:], utils.PublicKey2Bytes(privKey.PubKey())) 19 | 20 | return addr 21 | } 22 | 23 | // NewAddressFromMultiKeys will return a publickey address 24 | func NewAddressFromMultiKeys(privKeys ...*secp256k1.PrivateKey) (Address, error) { 25 | addr := [33]byte{} 26 | 27 | if len(privKeys) == 0 { 28 | panic("no private key entered") 29 | } 30 | 31 | pubKeys := make([]secp256k1.PublicKey, len(privKeys)) 32 | pub := schnorr.CombinePublicKeys(pubKeys...) 33 | 34 | copy(addr[:], utils.PublicKey2Bytes(pub)) 35 | return addr, nil 36 | } 37 | 38 | // NewAddressFromBS58 converts a base58 string into the Address 39 | func NewAddressFromBS58(s string) (Address, error) { 40 | addr := [33]byte{} 41 | 42 | raw, err := base58.FastBase58Decoding(s) 43 | if err != nil { 44 | return Address{}, err 45 | } 46 | 47 | copy(addr[:], raw) 48 | return addr, nil 49 | } 50 | 51 | // PubKey gets the public key from address for validation 52 | func (a Address) PubKey() *secp256k1.PublicKey { 53 | return utils.Bytes2PublicKey(a[:]) 54 | } 55 | 56 | func (a Address) SetBytes(b []byte) Address { 57 | copy(a[:], b) 58 | 59 | return a 60 | } 61 | 62 | func (a Address) Bytes() []byte { 63 | return a[:] 64 | } 65 | 66 | // BS58 generates the base58 string representing the Address 67 | func (a Address) BS58() string { 68 | return base58.FastBase58Encoding(a[:]) 69 | } 70 | 71 | func (a Address) String() string { 72 | return a.BS58() 73 | } 74 | 75 | func (a Address) Equals(other Address) bool { 76 | return a == other 77 | } 78 | 79 | // MarshalJSON makes the base58 string as the Address' json value 80 | func (a Address) MarshalJSON() ([]byte, error) { 81 | raw := base58.FastBase58Encoding(a[:]) 82 | 83 | return utils.JSON.Marshal(raw) 84 | } 85 | 86 | // UnmarshalJSON recovers the Address from the base58 string json value 87 | func (a Address) UnmarshalJSON(b []byte) error { 88 | var bs58Addr string 89 | err := utils.JSON.Unmarshal(b, &bs58Addr) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | addr, err := base58.FastBase58Decoding(bs58Addr) 95 | if err != nil { 96 | return err 97 | } 98 | 99 | copy(a[:], addr) 100 | return nil 101 | } 102 | -------------------------------------------------------------------------------- /ngtypes/address_balance.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import "math/big" 4 | 5 | // Balance is a unit in Sheet.Balances, which represents the remaining 6 | // coin amount of the address 7 | type Balance struct { 8 | Address Address 9 | Amount *big.Int 10 | } 11 | -------------------------------------------------------------------------------- /ngtypes/block_genesis.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | var genesisBlock *FullBlock 4 | 5 | // GetGenesisBlock will return a complete sealed GenesisBlock. 6 | func GetGenesisBlock(network Network) *FullBlock { 7 | txs := []*FullTx{ 8 | GetGenesisGenerateTx(network), 9 | } 10 | 11 | if genesisBlock == nil { 12 | txTrie := NewTxTrie(txs) 13 | headerTrie := NewHeaderTrie(nil) 14 | genesisBlock = NewBlock( 15 | network, 16 | 0, 17 | GetGenesisTimestamp(network), 18 | 19 | make([]byte, HashSize), 20 | txTrie.TrieRoot(), 21 | headerTrie.TrieRoot(), 22 | minimumBigDifficulty.Bytes(), // this is a number, dont put any padding on 23 | GetGenesisBlockNonce(network), 24 | txs, 25 | []*BlockHeader{}, 26 | ) 27 | genesisBlock.GetHash() 28 | } 29 | 30 | return genesisBlock 31 | } 32 | -------------------------------------------------------------------------------- /ngtypes/block_header.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | 7 | "github.com/c0mm4nd/rlp" 8 | "github.com/cbergoon/merkletree" 9 | "golang.org/x/crypto/sha3" 10 | ) 11 | 12 | // BlockHeader is the fix-sized header of the block, which is able to 13 | // describe itself. 14 | type BlockHeader struct { 15 | Network Network // 1 16 | 17 | Height uint64 // 4 18 | Timestamp uint64 // 4 19 | 20 | PrevBlockHash []byte // 32 21 | TxTrieHash []byte // 32 22 | SubTrieHash []byte // 32 23 | 24 | Difficulty []byte // 32 25 | Nonce []byte `rlp:"tail"` // 8 26 | } 27 | 28 | // GetHeight returns the height of the block. 29 | func (x *BlockHeader) GetHeight() uint64 { 30 | return x.Height 31 | } 32 | 33 | // GetPrevHash returns the hash of the previous block. 34 | func (x *BlockHeader) GetPrevHash() []byte { 35 | return x.PrevBlockHash 36 | } 37 | 38 | // GetTimestamp returns the timestamp of the block. 39 | func (x *BlockHeader) GetTimestamp() uint64 { 40 | return x.Timestamp 41 | } 42 | 43 | // CalculateHash calcs the hash of the Block header with sha3_256, aiming to 44 | // get the merkletree hash when summarizing subs into the header. 45 | func (x *BlockHeader) CalculateHash() ([]byte, error) { 46 | raw, err := rlp.EncodeToBytes(x) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | hash := sha3.Sum256(raw) 52 | 53 | return hash[:], nil 54 | } 55 | 56 | func (x *BlockHeader) GetHash() []byte { 57 | hash, err := x.CalculateHash() 58 | if err != nil { 59 | panic(err) 60 | } 61 | return hash 62 | } 63 | 64 | // ErrNotBlockHeader means the var is not a block header 65 | var ErrNotBlockHeader = errors.New("not a block header") 66 | 67 | // Equals checks whether the block headers equal 68 | func (x *BlockHeader) Equals(other merkletree.Content) (bool, error) { 69 | header, ok := other.(*BlockHeader) 70 | if !ok { 71 | return false, ErrNotBlockHeader 72 | } 73 | 74 | if x.Network != header.Network { 75 | return false, nil 76 | } 77 | if x.Height != header.Height { 78 | return false, nil 79 | } 80 | if x.Timestamp != header.Timestamp { 81 | return false, nil 82 | } 83 | if !bytes.Equal(x.PrevBlockHash, header.PrevBlockHash) { 84 | return false, nil 85 | } 86 | if !bytes.Equal(x.TxTrieHash, header.TxTrieHash) { 87 | return false, nil 88 | } 89 | if !bytes.Equal(x.SubTrieHash, header.SubTrieHash) { 90 | return false, nil 91 | } 92 | if !bytes.Equal(x.Difficulty, header.Difficulty) { 93 | return false, nil 94 | } 95 | if !bytes.Equal(x.Nonce, header.Nonce) { 96 | return false, nil 97 | } 98 | 99 | return true, nil 100 | } 101 | -------------------------------------------------------------------------------- /ngtypes/block_header_trie.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "math/big" 5 | "sort" 6 | 7 | "github.com/cbergoon/merkletree" 8 | "golang.org/x/crypto/sha3" 9 | ) 10 | 11 | // HeaderTrie is a fixed ordered block header container of the subBlocks 12 | type HeaderTrie []*BlockHeader 13 | 14 | // NewHeaderTrie creates new HeaderTrie 15 | func NewHeaderTrie(headers []*BlockHeader) HeaderTrie { 16 | sort.Slice(headers, func(i, j int) bool { 17 | // i nonce < j nonce 18 | return new(big.Int).SetBytes(headers[i].Nonce).Cmp(new(big.Int).SetBytes(headers[j].Nonce)) < 0 19 | }) 20 | return headers 21 | } 22 | 23 | // func (tt *TxTrie) Len() int { 24 | // return len(tt.Txs) 25 | // } 26 | 27 | // Less means that the tx (I) has lower priority (than J). 28 | // func (tt *TxTrie) Less(i, j int) bool { 29 | // return new(big.Int).SetBytes(tt.Txs[i].Fee).Cmp(new(big.Int).SetBytes(tt.Txs[j].Fee)) < 0 || 30 | // tt.Txs[i].Convener > tt.Txs[j].Convener 31 | // } 32 | 33 | // Swap just swap the values of txs. 34 | // func (tt *TxTrie) Swap(i, j int) { 35 | // tt.Txs[i], tt.Txs[j] = tt.Txs[j], tt.Txs[i] 36 | // } 37 | 38 | // Sort will sort the txs from lower priority to higher priority. 39 | // func (tt *TxTrie) Sort() *TxTrie { 40 | // sort.Sort(tt) 41 | // return tt 42 | // } 43 | 44 | // ReverseSort will sort the txs from higher priority to lower priority. 45 | // func (tt *TxTrie) ReverseSort() *TxTrie { 46 | // return sort.Reverse(tt).(*TxTrie) 47 | // } 48 | 49 | // Contains determine if tt.Txs and tx are equal. 50 | func (ht *HeaderTrie) Contains(h *BlockHeader) bool { 51 | for i := 0; i < len(*ht); i++ { 52 | if (*ht)[i] == h { 53 | return true 54 | } 55 | } 56 | 57 | return false 58 | } 59 | 60 | // TrieRoot sort tx tire by trie tree and return the root hash. 61 | func (ht *HeaderTrie) TrieRoot() []byte { 62 | if len(*ht) == 0 { 63 | return make([]byte, HashSize) 64 | } 65 | 66 | mtc := make([]merkletree.Content, len(*ht)) 67 | for i := range *ht { 68 | mtc[i] = (*ht)[i] 69 | } 70 | 71 | trie, err := merkletree.NewTreeWithHashStrategy(mtc, sha3.New256) 72 | if err != nil { 73 | log.Error(err) 74 | } 75 | 76 | return trie.MerkleRoot() 77 | } 78 | -------------------------------------------------------------------------------- /ngtypes/block_json.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "encoding/hex" 5 | "errors" 6 | "math/big" 7 | 8 | "github.com/ngchain/ngcore/utils" 9 | ) 10 | 11 | type jsonBlock struct { 12 | Network string `json:"network"` 13 | 14 | Height uint64 `json:"height"` 15 | Timestamp uint64 `json:"timestamp"` 16 | 17 | PrevBlockHash string `json:"prevBlockHash"` 18 | TxTrieHash string `json:"txTrieHash"` 19 | SubTrieHash string `json:"subTrieHash"` 20 | 21 | Difficulty string `json:"difficulty"` 22 | Nonce string `json:"nonce"` 23 | 24 | Txs []*FullTx `json:"txs"` 25 | 26 | // some helper fields 27 | Hash string `json:"hash,omitempty"` 28 | PoWHash string `json:"powHash,omitempty"` 29 | Txn int `json:"txn,omitempty"` 30 | } 31 | 32 | // MarshalJSON encodes the Block into the json bytes 33 | func (x *FullBlock) MarshalJSON() ([]byte, error) { 34 | return utils.JSON.Marshal(jsonBlock{ 35 | Network: x.BlockHeader.Network.String(), 36 | Height: x.BlockHeader.Height, 37 | Timestamp: x.BlockHeader.Timestamp, 38 | PrevBlockHash: hex.EncodeToString(x.BlockHeader.PrevBlockHash), 39 | TxTrieHash: hex.EncodeToString(x.BlockHeader.TxTrieHash), 40 | SubTrieHash: hex.EncodeToString(x.BlockHeader.SubTrieHash), 41 | Difficulty: new(big.Int).SetBytes(x.BlockHeader.Difficulty).String(), 42 | Nonce: hex.EncodeToString(x.BlockHeader.Nonce), 43 | Txs: x.Txs, 44 | 45 | Hash: hex.EncodeToString(x.GetHash()), 46 | PoWHash: hex.EncodeToString(x.PowHash()), 47 | Txn: len(x.Txs), 48 | }) 49 | } 50 | 51 | // ErrInvalidDiff means the diff cannot load from the string 52 | var ErrInvalidDiff = errors.New("failed to parse blockHeader's difficulty") 53 | 54 | // UnmarshalJSON decode the Block from the json bytes 55 | func (x *FullBlock) UnmarshalJSON(data []byte) error { 56 | var b jsonBlock 57 | err := utils.JSON.Unmarshal(data, &b) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | prevBlockHash, err := hex.DecodeString(b.PrevBlockHash) 63 | if err != nil { 64 | return err 65 | } 66 | txTrieHash, err := hex.DecodeString(b.TxTrieHash) 67 | if err != nil { 68 | return err 69 | } 70 | subTrieHash, err := hex.DecodeString(b.SubTrieHash) 71 | if err != nil { 72 | return err 73 | } 74 | bigDifficulty, ok := new(big.Int).SetString(b.Difficulty, 10) 75 | if !ok { 76 | return ErrInvalidDiff 77 | } 78 | difficulty := bigDifficulty.Bytes() 79 | nonce, err := hex.DecodeString(b.Nonce) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | *x = *NewBlock( 85 | GetNetwork(b.Network), 86 | b.Height, 87 | b.Timestamp, 88 | prevBlockHash, 89 | txTrieHash, 90 | subTrieHash, 91 | difficulty, 92 | nonce, 93 | b.Txs, 94 | []*BlockHeader{}, // TODO 95 | ) 96 | 97 | // err = x.verifyNonce() 98 | // if err != nil { 99 | // return err 100 | // } 101 | 102 | return nil 103 | } 104 | -------------------------------------------------------------------------------- /ngtypes/block_light.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | // LightWeightBlock is a light weight block strcut for db storage 4 | type LightWeightBlock struct { 5 | Header *BlockHeader 6 | Txs [][]byte // tx hashes 7 | Subs [][]byte // subblock hashes 8 | } 9 | -------------------------------------------------------------------------------- /ngtypes/block_test.go: -------------------------------------------------------------------------------- 1 | package ngtypes_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/c0mm4nd/rlp" 8 | "golang.org/x/crypto/sha3" 9 | 10 | "github.com/ngchain/ngcore/ngtypes" 11 | "github.com/ngchain/ngcore/utils" 12 | ) 13 | 14 | func TestPowHash(t *testing.T) { 15 | for _, net := range ngtypes.AvailableNetworks { 16 | b := ngtypes.GetGenesisBlock(net) 17 | headerHash := b.PowHash() 18 | if len(headerHash) != ngtypes.HashSize { 19 | t.Errorf("pow hash %x is not a valid hash", headerHash) 20 | } 21 | } 22 | } 23 | 24 | func TestBlock_IsGenesis(t *testing.T) { 25 | for _, net := range ngtypes.AvailableNetworks { 26 | t.Log(net) 27 | 28 | g := ngtypes.GetGenesisBlock(net) 29 | if !g.IsGenesis() { 30 | t.Fail() 31 | } 32 | 33 | if err := g.CheckError(); err != nil { 34 | t.Error(err) 35 | return 36 | } 37 | 38 | raw, err := rlp.EncodeToBytes(g) 39 | if err != nil { 40 | panic(err) 41 | } 42 | gg := new(ngtypes.FullBlock) 43 | err = rlp.DecodeBytes(raw, gg) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | if !gg.IsGenesis() { 49 | t.Error("failed unmarshalling back to genesis block structure") 50 | return 51 | } 52 | 53 | if err := gg.CheckError(); err != nil { 54 | t.Error(err) 55 | return 56 | } 57 | } 58 | } 59 | 60 | // TestBlock_Marshal test func GetGenesisBlock()'s Marshal(). 61 | func TestBlock_Marshal(t *testing.T) { 62 | for _, net := range ngtypes.AvailableNetworks { 63 | rawBlock, _ := rlp.EncodeToBytes(ngtypes.GetGenesisBlock(net)) 64 | 65 | var genesisBlock ngtypes.FullBlock 66 | _ = rlp.DecodeBytes(rawBlock, &genesisBlock) 67 | _block, _ := rlp.EncodeToBytes(&genesisBlock) 68 | 69 | if !bytes.Equal(rawBlock, _block) { 70 | t.Fail() 71 | } 72 | } 73 | } 74 | 75 | // TestGetGenesisBlock test func GetGenesisBlock()'s parameter passing. 76 | func TestGetGenesisBlock(t *testing.T) { 77 | for _, net := range ngtypes.AvailableNetworks { 78 | t.Logf(string(net)) 79 | d, _ := rlp.EncodeToBytes(ngtypes.GetGenesisBlock(net)) 80 | hash := sha3.Sum256(d) 81 | 82 | t.Logf("GenesisBlock hex: %x", d) 83 | t.Logf("GenesisBlock hash: %x", hash) 84 | t.Logf("GenesisBlock Size: %d bytes", len(d)) 85 | } 86 | } 87 | 88 | func TestBlockJSON(t *testing.T) { 89 | for _, net := range ngtypes.AvailableNetworks { 90 | block := ngtypes.GetGenesisBlock(net) 91 | jsonBlock, err := utils.JSON.Marshal(block) 92 | if err != nil { 93 | t.Error(err) 94 | return 95 | } 96 | 97 | t.Log(string(jsonBlock)) 98 | 99 | block2 := &ngtypes.FullBlock{} 100 | err = utils.JSON.Unmarshal(jsonBlock, &block2) 101 | if err != nil { 102 | t.Error(err) 103 | return 104 | } 105 | 106 | if eq, _ := block.Equals(block2); !eq { 107 | log.Errorf("block %#v", block) 108 | log.Errorf("block2 %#v", block2) 109 | t.Fail() 110 | } 111 | 112 | if eq, _ := block.Equals(block2); !eq { 113 | log.Errorf("block %#v", block) 114 | log.Errorf("block2 %#v", block2) 115 | t.Fail() 116 | } 117 | } 118 | } 119 | 120 | func TestBlockRawPoW(t *testing.T) { 121 | for _, net := range ngtypes.AvailableNetworks { 122 | block := ngtypes.GetGenesisBlock(net) 123 | raw := block.GetPoWRawHeader(nil) 124 | txs := block.Txs 125 | block2, err := ngtypes.NewBlockFromPoWRaw(raw, txs, nil) 126 | if err != nil { 127 | panic(err) 128 | } 129 | 130 | if eq, _ := block.Equals(block2); !eq { 131 | log.Errorf("block %#v", block) 132 | log.Errorf("block2 %#v", block2) 133 | t.Fail() 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /ngtypes/difficulty.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "math/big" 5 | "time" 6 | ) 7 | 8 | var big2 = big.NewInt(2) 9 | 10 | // GetNextDiff is a helper to get next pow block Diff field. 11 | func GetNextDiff(blockHeight uint64, blockTime uint64, tailBlock *FullBlock) *big.Int { 12 | diff := new(big.Int).SetBytes(tailBlock.BlockHeader.Difficulty) 13 | if !tailBlock.IsTail() { 14 | return diff 15 | } 16 | 17 | if tailBlock.GetTimestamp() < GetGenesisTimestamp(tailBlock.BlockHeader.Network) { 18 | panic("network havent start yet") 19 | } 20 | elapsed := tailBlock.GetTimestamp() - GetGenesisTimestamp(tailBlock.BlockHeader.Network) 21 | diffTime := int64(elapsed) - int64(tailBlock.GetHeight())*int64(TargetTime/time.Second) 22 | delta := new(big.Int) 23 | if diffTime < int64(TargetTime/time.Second)*(-2) { 24 | delta.Div(diff, big.NewInt(10)) 25 | } 26 | 27 | if diffTime > int64(TargetTime/time.Second)*(+2) { 28 | delta.Div(diff, big.NewInt(10)) 29 | } 30 | 31 | // reload the diff 32 | diff = new(big.Int).SetBytes(tailBlock.BlockHeader.Difficulty) 33 | d := int64(blockTime) - int64(tailBlock.GetTimestamp()) - int64(TargetTime/time.Second) 34 | delta.Div(diff, big.NewInt(2048)) 35 | delta.Mul(delta, big.NewInt(max(1-(d)/10, -99))) 36 | diff.Add(diff, delta) 37 | 38 | delta.Exp(big2, big.NewInt(int64(blockHeight)/100_000-2), nil) 39 | diff.Add(diff, delta) 40 | 41 | if diff.Cmp(minimumBigDifficulty) < 0 { 42 | diff = minimumBigDifficulty 43 | } 44 | 45 | log.Debugf("New Block Diff: %d", diff) 46 | 47 | return diff 48 | } 49 | 50 | func max(a, b int64) int64 { 51 | if a > b { 52 | return a 53 | } 54 | return b 55 | } 56 | -------------------------------------------------------------------------------- /ngtypes/difficulty_test.go: -------------------------------------------------------------------------------- 1 | package ngtypes_test 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDifficultyAlgo(t *testing.T) { 8 | // network := ngproto.NetworkType_ZERONET 9 | // 10 | // tailBlock := &ngtypes.Block{ 11 | // Timestamp: ngtypes.GetGenesisTimestamp(network) + 9*int64(ngtypes.TargetTime/time.Second) - 129, 12 | // Height: 9, // tail 13 | // Difficulty: ngtypes.GetGenesisBlock(ngproto.NetworkType_TESTNET).GetDifficulty(), 14 | // } 15 | // 16 | // genesisDiff := new(big.Int).SetBytes(ngtypes.GetGenesisBlock(ngproto.NetworkType_TESTNET).GetDifficulty()) 17 | // diff := ngtypes.GetNextDiff(tailBlock) 18 | // if diff.Cmp(genesisDiff) <= 0 { 19 | // t.Errorf("diff %d should be higher than %d", diff, genesisDiff) 20 | // } 21 | // 22 | // nextTailBlock := &ngtypes.Block{ 23 | // Timestamp: ngtypes.GetGenesisTimestamp(network) + 19*int64(ngtypes.TargetTime/time.Second) + 129, 24 | // Height: 19, // tail 25 | // Difficulty: diff.Bytes(), 26 | // } 27 | // 28 | // nextDiff := ngtypes.GetNextDiff(nextTailBlock) 29 | // if nextDiff.Cmp(diff) >= 0 { 30 | // t.Errorf("diff %d should be lower than %d", nextDiff, diff) 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /ngtypes/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package ngtypes implements the block structure and related types. 3 | 4 | # RULE: NGTYPES 5 | 6 | ## Account 7 | 8 | Account is not an unlimited resource, the account is created when register tx and removed with destroy tx 9 | 10 | ## Block 11 | 12 | Block is not only a tx mass, but a vault for network status. 13 | 14 | It means that the security can be ensured with a specific length sub-chain, 15 | the blocks before the chain can be throw. 16 | 17 | The block has several steps to be mature 18 | 19 | (sheetHash)--> BareBlock --(+txs&mtree)--> Unsealing --(+nonce)--> SealedBlock 20 | 21 | The sheetHash is the sheet before applying block txs. 22 | 23 | ## Tx 24 | 25 | Tx is a basic operation method in the ngchain network, acting as extendable structure for the network's functions. 26 | 27 | Tx can handle more than one transfer of coins or operations because of it's values field and participants 28 | field. Also, with the schnorr signature, 29 | the coin can be owned by multi-person at the same time and the account is able to send tx only when all owners signed 30 | the tx. 31 | 32 | Currently, there are 5 types of tx 33 | 34 | 1. Generate Tx: generate tx works when generating the coin, only miner can send this, 35 | and it can also have multi-participants. 36 | 37 | 2. Register Tx: register the account 38 | 39 | 3. Destroy Tx: destroy the account 40 | 41 | 4. Transaction: normal tx, can be used for sending money, or trigger the vm's onTx function 42 | 43 | 5. Assign Tx: assign raw bytes to Contract, overwriting 44 | 45 | 5. Append Tx: append raw bytes to Contract 46 | 47 | ## Sheet 48 | 49 | Sheet is the aggregation of all status, not only accounts, but anonymous addresses */ 50 | package ngtypes 51 | -------------------------------------------------------------------------------- /ngtypes/network.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | // Network is the type of the ngchain network 4 | type Network uint8 5 | 6 | const ( 7 | // ZERONET is the local regression testnet 8 | ZERONET Network = 0 9 | // TESTNET is the public internet testnet 10 | TESTNET Network = 1 11 | // MAINNET is the public network for production 12 | MAINNET Network = 2 13 | ) 14 | 15 | // GetNetwork converts the network name to the Network type 16 | func GetNetwork(netName string) Network { 17 | switch netName { 18 | case "ZERONET": 19 | return ZERONET 20 | case "TESTNET": 21 | return TESTNET 22 | case "MAINNET": 23 | return MAINNET 24 | default: 25 | panic("invalid network: " + netName) 26 | } 27 | } 28 | 29 | func (net Network) String() string { 30 | switch net { 31 | case ZERONET: 32 | return "ZERONET" 33 | case TESTNET: 34 | return "TESTNET" 35 | case MAINNET: 36 | return "MAINNET" 37 | default: 38 | panic("invalid network") 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ngtypes/reward_curve.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/pkg/errors" 7 | ) 8 | 9 | const ( 10 | rewardEra = 1_000_000 11 | 12 | maxBlockRewardNG = 10 13 | minBlockRewardNG = 2 14 | floatingBlockRewardNG = maxBlockRewardNG - minBlockRewardNG 15 | 16 | registerFeeNG = maxBlockRewardNG 17 | ) 18 | 19 | var ( 20 | minReward = new(big.Int).Mul(NG, big.NewInt(minBlockRewardNG)) // 2NG 21 | floatingReward = new(big.Int).Mul(NG, big.NewInt(floatingBlockRewardNG)) // 8NG 22 | ) 23 | 24 | // RegisterFee is the fee for registering a new account 25 | var RegisterFee = new(big.Int).Mul(NG, big.NewInt(registerFeeNG)) 26 | 27 | var ( 28 | big1 = big.NewInt(1) 29 | big10 = big.NewInt(10000) 30 | ) 31 | 32 | var ErrRewardInvalid = errors.New("block reward is invalid") 33 | 34 | // GetBlockReward returns the block reward in a specific height 35 | // reward = 2 + 8*(0.9)^Era 36 | func GetBlockReward(height uint64) *big.Int { 37 | reward := new(big.Int).Set(floatingReward) 38 | 39 | d := new(big.Int) 40 | era := height / rewardEra 41 | for i := uint64(0); i < era; i++ { 42 | // reward = reward * 0.9 43 | d.Mul(reward, big1) 44 | d.Div(reward, big10) 45 | reward.Sub(reward, d) 46 | } 47 | 48 | reward.Add(reward, minReward) 49 | 50 | return reward 51 | } 52 | -------------------------------------------------------------------------------- /ngtypes/reward_curve_test.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import "testing" 4 | 5 | func TestGetBlockReward(t *testing.T) { 6 | t.Log(GetBlockReward(0)) 7 | t.Log(GetBlockReward(100)) 8 | t.Log(GetBlockReward(rewardEra)) 9 | t.Log(GetBlockReward(2 * rewardEra)) 10 | t.Log(GetBlockReward(4 * rewardEra)) 11 | } 12 | -------------------------------------------------------------------------------- /ngtypes/sheet.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | type Sheet struct { 4 | Network Network 5 | Height uint64 6 | BlockHash []byte 7 | Balances []*Balance 8 | Accounts []*Account 9 | } 10 | 11 | // NewSheet gets the rows from db and return the sheet for transport/saving. 12 | func NewSheet(network Network, height uint64, blockHash []byte, balances []*Balance, accounts []*Account) *Sheet { 13 | return &Sheet{ 14 | Network: network, 15 | Height: height, 16 | BlockHash: blockHash, 17 | Balances: balances, 18 | Accounts: accounts, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ngtypes/tx_error.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import "github.com/pkg/errors" 4 | 5 | // Errors for Tx 6 | var ( 7 | // ErrTxSignInvalid occurs when the signature of the Tx doesnt match the Tx 's caller/account 8 | ErrTxSignInvalid = errors.New("signer of tx is not the own of the account") 9 | ErrTxUnsigned = errors.New("unsigned tx") 10 | ErrInvalidPublicKey = errors.New("invalid public key") 11 | 12 | ErrTxNoHeader = errors.New("tx header is nil") 13 | ErrTxTypeInvalid = errors.New("invalid tx type") 14 | ErrTxConvenerInvalid = errors.New("invalid tx convener") 15 | ErrTxParticipantsInvalid = errors.New("invalid tx participants") 16 | ErrTxValuesInvalid = errors.New("invalid tx values") 17 | ErrTxFeeInvalid = errors.New("invalid tx fee") 18 | 19 | ErrTxHeightInvalid = errors.New("invalid tx height") 20 | ErrTxExtraInvalid = errors.New("invalid tx extra") 21 | ErrTxExtraExcess = errors.New("the size of the tx extra is too large") 22 | ) 23 | -------------------------------------------------------------------------------- /ngtypes/tx_extra.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | // some pre-defined extra format for the smart contacts 4 | 5 | // AppendExtra is the pre-defined data strcut for the Extra field in Append Tx 6 | type AppendExtra struct { 7 | Pos uint64 8 | Content []byte 9 | } 10 | 11 | // DeleteExtra is the pre-defined data struct for the Extra field in Delete Tx 12 | type DeleteExtra struct { 13 | Pos uint64 14 | Content []byte 15 | } 16 | -------------------------------------------------------------------------------- /ngtypes/tx_genesis.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "math/big" 5 | ) 6 | 7 | var genesisGenerateTx *FullTx 8 | 9 | // GetGenesisGenerateTx provides the genesis generate tx under current network 10 | func GetGenesisGenerateTx(network Network) *FullTx { 11 | if genesisGenerateTx == nil || genesisGenerateTx.Network != network { 12 | ggtx := NewTx(network, GenerateTx, 0, 0, []Address{GenesisAddress}, 13 | []*big.Int{GetBlockReward(0)}, 14 | big.NewInt(0), 15 | nil, 16 | nil, 17 | ) 18 | 19 | ggtx.ManuallySetSignature(GetGenesisGenerateTxSignature(network)) 20 | 21 | genesisGenerateTx = ggtx 22 | } 23 | 24 | return genesisGenerateTx 25 | } 26 | -------------------------------------------------------------------------------- /ngtypes/tx_json.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | 7 | "github.com/ngchain/ngcore/utils" 8 | ) 9 | 10 | type jsonTx struct { 11 | Network string `json:"network"` 12 | Type TxType `json:"type"` 13 | Height uint64 `json:"prevBlockHash"` 14 | Convener AccountNum `json:"convener"` 15 | Participants []Address `json:"participants"` 16 | Fee *big.Int `json:"fee"` 17 | Values []*big.Int `json:"values"` 18 | Extra string `json:"extra"` 19 | 20 | Sign string `json:"sign"` 21 | 22 | // helpers 23 | Hash string `json:"hash,omitempty"` 24 | } 25 | 26 | // MarshalJSON encodes the tx into the json bytes 27 | func (x *FullTx) MarshalJSON() ([]byte, error) { 28 | return utils.JSON.Marshal(jsonTx{ 29 | Network: x.Network.String(), 30 | Type: x.Type, 31 | Height: x.Height, 32 | Convener: x.Convener, 33 | Participants: x.Participants, 34 | Fee: x.Fee, 35 | Values: x.Values, 36 | Extra: hex.EncodeToString(x.Extra), 37 | 38 | Sign: hex.EncodeToString(x.Sign), 39 | 40 | Hash: hex.EncodeToString(x.GetHash()), 41 | }) 42 | } 43 | 44 | // UnmarshalJSON decodes the Tx from the json bytes 45 | func (x *FullTx) UnmarshalJSON(b []byte) error { 46 | var tx jsonTx 47 | err := utils.JSON.Unmarshal(b, &tx) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | extra, err := hex.DecodeString(tx.Extra) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | sign, err := hex.DecodeString(tx.Sign) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | *x = *NewTx( 63 | GetNetwork(tx.Network), 64 | tx.Type, 65 | tx.Height, 66 | tx.Convener, 67 | tx.Participants, 68 | tx.Values, 69 | tx.Fee, 70 | extra, 71 | sign, 72 | ) 73 | 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /ngtypes/tx_test.go: -------------------------------------------------------------------------------- 1 | package ngtypes_test 2 | 3 | import ( 4 | "encoding/hex" 5 | "math/big" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/c0mm4nd/rlp" 10 | "github.com/ngchain/secp256k1" 11 | 12 | "github.com/ngchain/ngcore/ngtypes" 13 | "github.com/ngchain/ngcore/utils" 14 | ) 15 | 16 | const TEST_HEIGHT = 100 17 | 18 | // TestDeserialize test unsigned transaction whether it is possible to deserialize. 19 | func TestDeserialize(t *testing.T) { 20 | tx := ngtypes.NewUnsignedTx( 21 | ngtypes.TESTNET, 22 | ngtypes.TransactTx, 23 | TEST_HEIGHT, 24 | 0, 25 | []ngtypes.Address{ngtypes.GenesisAddress}, 26 | []*big.Int{new(big.Int).Mul(ngtypes.NG, big.NewInt(1000))}, 27 | big.NewInt(0), 28 | nil, 29 | ) 30 | 31 | raw, _ := rlp.EncodeToBytes(tx) 32 | t.Log(len(raw)) 33 | result := hex.EncodeToString(raw) 34 | t.Log(result) 35 | 36 | var otherTx ngtypes.FullTx 37 | _ = rlp.DecodeBytes(raw, &otherTx) 38 | t.Logf("%#v", otherTx) 39 | } 40 | 41 | // TestTransaction_Signature test generated Key pair. 42 | func TestTransaction_Signature(t *testing.T) { 43 | o := ngtypes.NewUnsignedTx( 44 | ngtypes.TESTNET, 45 | ngtypes.TransactTx, 46 | TEST_HEIGHT, 47 | 1, 48 | []ngtypes.Address{ngtypes.GenesisAddress}, 49 | []*big.Int{big.NewInt(0)}, 50 | big.NewInt(0), 51 | nil, 52 | ) 53 | priv1, _ := secp256k1.GeneratePrivateKey() 54 | priv2, _ := secp256k1.GeneratePrivateKey() 55 | 56 | _ = o.Signature(priv1) 57 | 58 | if err := o.Verify(priv1.PubKey()); err != nil { 59 | t.Errorf("priv1 != o") 60 | } 61 | 62 | if err := o.Verify(priv2.PubKey()); err == nil { 63 | t.Errorf("priv2 == o") 64 | } 65 | } 66 | 67 | func TestGetGenesisGenerate(t *testing.T) { 68 | for _, net := range ngtypes.AvailableNetworks { 69 | gg := ngtypes.GetGenesisGenerateTx(net) 70 | if err := gg.Verify(gg.Participants[0].PubKey()); err != nil { 71 | t.Log(err) 72 | t.Fail() 73 | } 74 | } 75 | } 76 | 77 | func TestTxJSON(t *testing.T) { 78 | for _, net := range ngtypes.AvailableNetworks { 79 | tx1 := ngtypes.GetGenesisGenerateTx(net) 80 | jsonTx, err := utils.JSON.Marshal(tx1) 81 | if err != nil { 82 | t.Error(err) 83 | return 84 | } 85 | 86 | t.Log(string(jsonTx)) 87 | 88 | tx2 := &ngtypes.FullTx{} 89 | err = utils.JSON.Unmarshal(jsonTx, &tx2) 90 | if err != nil { 91 | t.Error(err) 92 | return 93 | } 94 | 95 | if eq, _ := tx1.Equals(tx2); !eq { 96 | t.Errorf("tx \n 2 %#v \n is different from \n 1 %#v", tx2, tx1) 97 | } 98 | 99 | if !reflect.DeepEqual(tx1, tx2) { 100 | t.Errorf("tx \n 2 %#v \n is different from \n 1 %#v", tx2, tx1) 101 | } 102 | } 103 | } 104 | 105 | func TestTxRLP(t *testing.T) { 106 | for _, net := range ngtypes.AvailableNetworks { 107 | tx1 := ngtypes.GetGenesisGenerateTx(net) 108 | rlpTx, err := rlp.EncodeToBytes(tx1) 109 | if err != nil { 110 | t.Error(err) 111 | return 112 | } 113 | 114 | t.Logf("%x", rlpTx) 115 | 116 | tx2 := &ngtypes.FullTx{} 117 | err = rlp.DecodeBytes(rlpTx, &tx2) 118 | if err != nil { 119 | t.Error(err) 120 | return 121 | } 122 | 123 | if eq, _ := tx1.Equals(tx2); !eq { 124 | t.Errorf("tx \n 2 %#v \n is different from \n 1 %#v", tx2, tx1) 125 | } 126 | 127 | if !reflect.DeepEqual(tx1, tx2) { 128 | t.Errorf("tx \n 2 %#v \n is different from \n 1 %#v", tx2, tx1) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ngtypes/tx_trie.go: -------------------------------------------------------------------------------- 1 | package ngtypes 2 | 3 | import ( 4 | "sort" 5 | 6 | "github.com/cbergoon/merkletree" 7 | "golang.org/x/crypto/sha3" 8 | ) 9 | 10 | // TxTrie is a fixed ordered tx container to get the trie root hash. 11 | // This is not thread-safe 12 | type TxTrie []*FullTx 13 | 14 | // NewTxTrie receives ordered ops. 15 | func NewTxTrie(txs []*FullTx) TxTrie { 16 | sort.Slice(txs, func(i, j int) bool { 17 | return txs[i].Convener < txs[j].Convener 18 | }) 19 | return txs 20 | } 21 | 22 | // func (tt *TxTrie) Len() int { 23 | // return len(tt.Txs) 24 | // } 25 | 26 | // Less means that the tx (I) has lower priority (than J). 27 | // func (tt *TxTrie) Less(i, j int) bool { 28 | // return new(big.Int).SetBytes(tt.Txs[i].Fee).Cmp(new(big.Int).SetBytes(tt.Txs[j].Fee)) < 0 || 29 | // tt.Txs[i].Convener > tt.Txs[j].Convener 30 | // } 31 | 32 | // Swap just swap the values of txs. 33 | // func (tt *TxTrie) Swap(i, j int) { 34 | // tt.Txs[i], tt.Txs[j] = tt.Txs[j], tt.Txs[i] 35 | // } 36 | 37 | // Sort will sort the txs from lower priority to higher priority. 38 | // func (tt *TxTrie) Sort() *TxTrie { 39 | // sort.Sort(tt) 40 | // return tt 41 | // } 42 | 43 | // ReverseSort will sort the txs from higher priority to lower priority. 44 | // func (tt *TxTrie) ReverseSort() *TxTrie { 45 | // return sort.Reverse(tt).(*TxTrie) 46 | // } 47 | 48 | // Contains determine if tt.Txs and tx are equal. 49 | func (tt *TxTrie) Contains(tx *FullTx) bool { 50 | for i := 0; i < len(*tt); i++ { 51 | if (*tt)[i] == tx { 52 | return true 53 | } 54 | } 55 | 56 | return false 57 | } 58 | 59 | // TrieRoot sort tx tire by trie tree and return the root hash. 60 | func (tt *TxTrie) TrieRoot() []byte { 61 | if len(*tt) == 0 { 62 | return make([]byte, HashSize) 63 | } 64 | 65 | mtc := make([]merkletree.Content, len(*tt)) 66 | for i := range *tt { 67 | mtc[i] = (*tt)[i] 68 | } 69 | 70 | trie, err := merkletree.NewTreeWithHashStrategy(mtc, sha3.New256) 71 | if err != nil { 72 | log.Error(err) 73 | } 74 | 75 | return trie.MerkleRoot() 76 | } 77 | -------------------------------------------------------------------------------- /resources/NG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /resources/NG_with_bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngchain/ngcore/e87e1d8a3d70baf5b6cd082c4ae150953c8093df/resources/NG_with_bg.png -------------------------------------------------------------------------------- /resources/NG_with_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /resources/QA.md: -------------------------------------------------------------------------------- 1 | # Q&A 2 | 3 | ## Why I cannot start the daemon on Windows? 4 | 5 | ### Err: panic: failed to listen on any addresses ... 6 | 7 | Maybe you are using hyper-v (or docker4win) and it excluded the ports we need to listen 8 | 9 | **Solution**: 10 | 11 | - run 12 | 13 | ```powershell 14 | net stop winnat 15 | netsh interface ipv4 show excludedportrange protocol=tcp 16 | netsh interface ipv6 show excludedportrange protocol=tcp 17 | 18 | # if the ipv4 havent exclude 52520-52619 19 | netsh int ipv4 add excludedportrange protocol=tcp startport=52520 numberofports=100 20 | 21 | # if the ipv6 havent exclude 52520-52619 22 | netsh int ipv6 add excludedportrange protocol=tcp startport=52520 numberofports=100 23 | 24 | net start winnet 25 | ``` 26 | 27 | -------------------------------------------------------------------------------- /resources/ng_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngchain/ngcore/e87e1d8a3d70baf5b6cd082c4ae150953c8093df/resources/ng_16x16.png -------------------------------------------------------------------------------- /resources/ng_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngchain/ngcore/e87e1d8a3d70baf5b6cd082c4ae150953c8093df/resources/ng_64x64.png -------------------------------------------------------------------------------- /storage/db.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "os" 5 | "path" 6 | "strconv" 7 | "time" 8 | 9 | logging "github.com/ngchain/zap-log" 10 | "go.etcd.io/bbolt" 11 | 12 | "github.com/ngchain/ngcore/ngtypes" 13 | ) 14 | 15 | var log = logging.Logger("storage") 16 | 17 | var db *bbolt.DB 18 | 19 | // InitStorage inits a new DB in data folder. 20 | func InitStorage(network ngtypes.Network, dbFolder string) *bbolt.DB { 21 | if db == nil { 22 | err := os.MkdirAll(dbFolder, os.ModePerm) 23 | if err != nil { 24 | log.Panic(err) 25 | } 26 | dbFilePath := path.Join(dbFolder, network.String()+".db") 27 | 28 | db, err = bbolt.Open(dbFilePath, 0666, nil) 29 | if err != nil { 30 | log.Panic("failed to init dboltDB:", err) 31 | } 32 | 33 | InitDB(db) 34 | } 35 | 36 | return db 37 | } 38 | 39 | func InitTempStorage() *bbolt.DB { 40 | if db == nil { 41 | var err error 42 | 43 | path := path.Join(os.TempDir(), "ngcore_"+strconv.FormatInt(time.Now().UTC().Unix(), 10)) 44 | db, err = bbolt.Open(path, 0666, nil) 45 | if err != nil { 46 | log.Panic("failed to init dboltDB:", err) 47 | } 48 | 49 | InitDB(db) 50 | } 51 | 52 | return db 53 | } 54 | -------------------------------------------------------------------------------- /storage/db_test.go: -------------------------------------------------------------------------------- 1 | package storage_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/ngchain/ngcore/ngtypes" 7 | "github.com/ngchain/ngcore/storage" 8 | ) 9 | 10 | func TestInitStorage(t *testing.T) { 11 | db := storage.InitStorage(ngtypes.ZERONET, ".") 12 | if db == nil { 13 | t.Error("failed to init db on home dir") 14 | return 15 | } 16 | 17 | memdb := storage.InitTempStorage() 18 | if memdb == nil { 19 | t.Error("failed to init db on mem") 20 | return 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /storage/err.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "errors" 4 | 5 | var ErrKeyNotFound = errors.New("key not found") 6 | -------------------------------------------------------------------------------- /storage/init.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "go.etcd.io/bbolt" 4 | 5 | var ( 6 | BlockBucketName = []byte("blocks") 7 | TxBucketName = []byte("txs") 8 | // blockTxPrefix = []byte("bt:") // TODO: add block-tx relationship 9 | 10 | // state buckets 11 | Num2AccBucketName = []byte("num:acc") 12 | Addr2BalBucketName = []byte("addr:bal") 13 | Addr2NumBucketName = []byte("addr:num") 14 | ) 15 | 16 | var ( 17 | LatestHeightTag = []byte("latest:height") 18 | LatestHashTag = []byte("latest:hash") 19 | OriginHeightTag = []byte("origin:height") // store the origin block 20 | OriginHashTag = []byte("origin:hash") 21 | ) 22 | 23 | func InitDB(db *bbolt.DB) { 24 | db.Update(func(txn *bbolt.Tx) error { 25 | _, err := txn.CreateBucketIfNotExists(BlockBucketName) 26 | if err != nil { 27 | return err 28 | } 29 | 30 | _, err = txn.CreateBucketIfNotExists(TxBucketName) 31 | if err != nil { 32 | return err 33 | } 34 | 35 | _, err = txn.CreateBucketIfNotExists(Num2AccBucketName) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | _, err = txn.CreateBucketIfNotExists(Addr2BalBucketName) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | _, err = txn.CreateBucketIfNotExists(Addr2NumBucketName) 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return nil 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /utils/aes256gcm.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | "crypto/rand" 7 | 8 | "golang.org/x/crypto/sha3" 9 | ) 10 | 11 | // AES256GCMEncrypt is used for file encrypt. 12 | func AES256GCMEncrypt(raw []byte, password []byte) (encrypted []byte) { 13 | hashPassword := sha3.Sum256(password) 14 | 15 | c, err := aes.NewCipher(hashPassword[:]) 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | gcm, err := cipher.NewGCM(c) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | nonce := make([]byte, gcm.NonceSize()) 26 | _, _ = rand.Read(nonce) 27 | 28 | return gcm.Seal(nonce, nonce, raw, nil) 29 | } 30 | 31 | // AES256GCMDecrypt is used for file decrypt. 32 | func AES256GCMDecrypt(raw []byte, password []byte) (decrypted []byte) { 33 | hashPassword := sha3.Sum256(password) 34 | 35 | c, err := aes.NewCipher(hashPassword[:]) 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | gcm, err := cipher.NewGCM(c) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | nonce, encrypted := raw[:gcm.NonceSize()], raw[gcm.NonceSize():] 46 | 47 | decrypted, err = gcm.Open(nil, nonce, encrypted, nil) 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | return decrypted 53 | } 54 | -------------------------------------------------------------------------------- /utils/aes256gcm_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/ngchain/ngcore/utils" 8 | ) 9 | 10 | func TestAES256GCMEncrypt(t *testing.T) { 11 | t.Parallel() 12 | 13 | raw := []byte("hello") 14 | password := []byte("world") 15 | encrypted := utils.AES256GCMEncrypt(raw, password) 16 | 17 | if !bytes.Equal(utils.AES256GCMDecrypt(encrypted, password), raw) { 18 | t.Fail() 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /utils/comparison.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "bytes" 4 | 5 | func MinUint64(i, j uint64) uint64 { 6 | if i < j { 7 | return i 8 | } 9 | 10 | return j 11 | } 12 | 13 | func MaxUint64(i, j uint64) uint64 { 14 | if i > j { 15 | return i 16 | } 17 | 18 | return j 19 | } 20 | 21 | func BytesListEquals(p1, p2 [][]byte) bool { 22 | if len(p1) != len(p2) { 23 | return false 24 | } 25 | 26 | for i := range p1 { 27 | if !bytes.Equal(p1[i], p2[i]) { 28 | return false 29 | } 30 | } 31 | 32 | return true 33 | } 34 | -------------------------------------------------------------------------------- /utils/doc.go: -------------------------------------------------------------------------------- 1 | // Package utils are some **utilities** while developing 2 | package utils 3 | -------------------------------------------------------------------------------- /utils/format.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | ) 7 | 8 | // Bytes2Hex is a helper func to convert raw bytes to hex string. 9 | func Bytes2Hex(b []byte) string { 10 | s := "" 11 | for i := 0; i < len(b); i++ { 12 | s += fmt.Sprintf("%02X", b[i]) 13 | } 14 | 15 | return s 16 | } 17 | 18 | // Hex2Bytes is a helper func to convert hex string to raw bytes. 19 | func Hex2Bytes(s string) []byte { 20 | b, err := hex.DecodeString(s) 21 | if err != nil { 22 | panic(err) 23 | } 24 | 25 | return b 26 | } 27 | -------------------------------------------------------------------------------- /utils/format_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "testing" 7 | 8 | "github.com/ngchain/ngcore/utils" 9 | ) 10 | 11 | // Testing hex string to []byte. 12 | func TestBytes2Hex(t *testing.T) { 13 | b := make([]byte, 10000) 14 | _, err := rand.Read(b) 15 | if err != nil { 16 | t.Error(err) 17 | } 18 | s := utils.Bytes2Hex(b) 19 | if !bytes.Equal(utils.Hex2Bytes(s), b) { 20 | t.Error("Bytes2Hex or Hex2Bytes wrong") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /utils/hash.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "golang.org/x/crypto/sha3" 4 | 5 | // Sha3Sum256 is a helper func to calc & return the sha3 sum256 []byte hash. 6 | func Sha3Sum256(b []byte) []byte { 7 | hash := sha3.Sum256(b) 8 | 9 | return hash[:] 10 | } 11 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "math/big" 6 | ) 7 | 8 | // BigIntPlusPlus is a helper func to calculate i++ for big int i. 9 | func BigIntPlusPlus(bigInt *big.Int) *big.Int { 10 | return new(big.Int).Add(bigInt, big.NewInt(1)) 11 | } 12 | 13 | // InBytesList is a helper func to check whether the sub bytes in the li list. 14 | func InBytesList(li [][]byte, sub []byte) (in bool) { 15 | for i := range li { 16 | if bytes.Equal(li[i], sub) { 17 | return true 18 | } 19 | } 20 | 21 | return false 22 | } 23 | -------------------------------------------------------------------------------- /utils/json.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | jsoniter "github.com/json-iterator/go" 5 | ) 6 | 7 | // JSON acts as a global json module. 8 | var JSON = jsoniter.ConfigCompatibleWithStandardLibrary 9 | -------------------------------------------------------------------------------- /utils/key.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/ngchain/go-schnorr" 5 | "github.com/ngchain/secp256k1" 6 | ) 7 | 8 | // PublicKey2Bytes is a helper func to convert public key to the **compressed** raw bytes. 9 | func PublicKey2Bytes(publicKey *secp256k1.PublicKey) []byte { 10 | return schnorr.Marshal(secp256k1.S256(), publicKey.X, publicKey.Y) 11 | } 12 | 13 | // Bytes2PublicKey is a helper func to convert **compressed** raw bytes to public key. 14 | func Bytes2PublicKey(data []byte) *secp256k1.PublicKey { 15 | x, y := schnorr.Unmarshal(secp256k1.S256(), data) 16 | return secp256k1.NewPublicKey(x, y) 17 | } 18 | -------------------------------------------------------------------------------- /utils/key_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/ngchain/secp256k1" 8 | 9 | "github.com/ngchain/ngcore/utils" 10 | ) 11 | 12 | func TestKeys(t *testing.T) { 13 | pk, err := secp256k1.GeneratePrivateKey() 14 | if err != nil { 15 | panic(err) 16 | } 17 | publicKey1 := pk.PubKey() 18 | raw := utils.PublicKey2Bytes(publicKey1) 19 | t.Log(len(raw)) 20 | publicKey2 := utils.Bytes2PublicKey(raw) 21 | if !reflect.DeepEqual(publicKey1, publicKey2) { 22 | t.Fail() 23 | } 24 | 25 | msgHash := utils.Sha3Sum256([]byte("msg")) 26 | hash := [32]byte{} 27 | copy(hash[:], msgHash) 28 | } 29 | -------------------------------------------------------------------------------- /utils/locker.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "runtime" 5 | 6 | "go.uber.org/atomic" 7 | ) 8 | 9 | type Locker struct { 10 | status *atomic.Bool 11 | } 12 | 13 | func NewLocker() *Locker { 14 | return &Locker{status: atomic.NewBool(false)} 15 | } 16 | 17 | func (l *Locker) Lock() { 18 | for !l.status.CompareAndSwap(false, true) { 19 | runtime.Gosched() 20 | } 21 | } 22 | 23 | func (l *Locker) Unlock() { 24 | for !l.status.CompareAndSwap(true, false) { 25 | runtime.Gosched() 26 | } 27 | } 28 | 29 | func (l *Locker) IsLocked() bool { 30 | return l.status.Load() 31 | } 32 | -------------------------------------------------------------------------------- /utils/rand.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | ) 7 | 8 | // RandUint64 generates a random uint64 number(0 to 18446744073709551615). 9 | func RandUint64() uint64 { 10 | raw := make([]byte, 8) 11 | _, _ = rand.Read(raw) 12 | 13 | return binary.LittleEndian.Uint64(raw) 14 | } 15 | 16 | // RandUint32 generates a random uint32 number(0 to 4294967295). 17 | // Useful when getting a random port number. 18 | func RandUint32() uint32 { 19 | raw := make([]byte, 4) 20 | _, _ = rand.Read(raw) 21 | 22 | return binary.LittleEndian.Uint32(raw) 23 | } 24 | 25 | // RandUint16 generates a random uint16 number(0 to 65535). 26 | // Useful when getting a random port number. 27 | func RandUint16() uint16 { 28 | raw := make([]byte, 2) 29 | _, _ = rand.Read(raw) 30 | 31 | return binary.LittleEndian.Uint16(raw) 32 | } 33 | 34 | // RandInt64 generates a random int64 number (-9223372036854775808 to 9223372036854775807). 35 | func RandInt64() int32 { 36 | return int32(RandUint64()) 37 | } 38 | 39 | // RandInt32 generates a random int32 number (-2147483648 to 2147483647). 40 | func RandInt32() int32 { 41 | return int32(RandUint32()) 42 | } 43 | 44 | // RandInt16 generates a random int16 number (-32768 to 32767). 45 | func RandInt16() int16 { 46 | return int16(RandUint16()) 47 | } 48 | -------------------------------------------------------------------------------- /utils/serialize.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | 8 | "github.com/c0mm4nd/rlp" 9 | ) 10 | 11 | // CombineBytes is a helper func to combine bytes without separator. 12 | func CombineBytes(b ...[]byte) []byte { 13 | return bytes.Join(b, nil) 14 | } 15 | 16 | func InsertBytes(s []byte, k int, vs ...byte) []byte { 17 | if n := len(s) + len(vs); n <= cap(s) { 18 | s2 := s[:n] 19 | copy(s2[k+len(vs):], s[k:]) 20 | copy(s2[k:], vs) 21 | return s2 22 | } 23 | s2 := make([]byte, len(s)+len(vs)) 24 | copy(s2, s[:k]) 25 | copy(s2[k:], vs) 26 | copy(s2[k+len(vs):], s[k:]) 27 | return s2 28 | } 29 | 30 | func CutBytes(a []byte, i int, j int) []byte { 31 | copy(a[i:], a[j:]) 32 | for k, n := len(a)-j+i, len(a); k < n; k++ { 33 | a[k] = 0 // or the zero value of T 34 | } 35 | 36 | return a[:len(a)-j+i] 37 | } 38 | 39 | // PackUint64LE converts int64 to bytes in LittleEndian. 40 | func PackUint64LE(n uint64) []byte { 41 | b := make([]byte, 8) 42 | binary.LittleEndian.PutUint64(b, n) 43 | 44 | return b 45 | } 46 | 47 | // PackUint64BE converts int64 to bytes in BigEndian. 48 | func PackUint64BE(n uint64) []byte { 49 | b := make([]byte, 8) 50 | binary.BigEndian.PutUint64(b, n) 51 | 52 | return b 53 | } 54 | 55 | // PackUint32LE converts int32 to bytes in LittleEndian. 56 | func PackUint32LE(n uint32) []byte { 57 | b := make([]byte, 4) 58 | binary.LittleEndian.PutUint32(b, n) 59 | 60 | return b 61 | } 62 | 63 | // PackUint32BE converts int32 to bytes in BigEndian. 64 | func PackUint32BE(n uint32) []byte { 65 | b := make([]byte, 4) 66 | binary.BigEndian.PutUint32(b, n) 67 | 68 | return b 69 | } 70 | 71 | // PackUint16LE converts int16 to bytes in LittleEndian. 72 | func PackUint16LE(n uint16) []byte { 73 | b := make([]byte, 2) 74 | binary.LittleEndian.PutUint16(b, n) 75 | return b 76 | } 77 | 78 | // PackUint16BE converts int16 to bytes in BigEndian. 79 | func PackUint16BE(n uint16) []byte { 80 | b := make([]byte, 2) 81 | binary.BigEndian.PutUint16(b, n) 82 | return b 83 | } 84 | 85 | // ReverseBytes converts bytes order between LittleEndian and BigEndian. 86 | func ReverseBytes(b []byte) []byte { 87 | _b := make([]byte, len(b)) 88 | copy(_b, b) 89 | 90 | for i, j := 0, len(_b)-1; i < j; i, j = i+1, j-1 { 91 | _b[i], _b[j] = _b[j], _b[i] 92 | } 93 | return _b 94 | } 95 | 96 | func HexRLPEncode(v any) string { 97 | rawBytes, err := rlp.EncodeToBytes(v) 98 | if err != nil { 99 | panic(err) 100 | } 101 | 102 | return hex.EncodeToString(rawBytes) 103 | } 104 | 105 | func HexRLPDecode(s string, v any) error { 106 | rawBytes, err := hex.DecodeString(s) 107 | if err != nil { 108 | return err 109 | } 110 | 111 | err = rlp.DecodeBytes(rawBytes, v) 112 | if err != nil { 113 | return err 114 | } 115 | 116 | return nil 117 | } 118 | -------------------------------------------------------------------------------- /utils/serialize_test.go: -------------------------------------------------------------------------------- 1 | package utils_test 2 | --------------------------------------------------------------------------------