├── CHANGELOG.md ├── docs └── .gitignore ├── AUTHORS.md ├── .dockerignore ├── contrib ├── git │ ├── .gitconfig.tmp │ └── .gitmessage.tmp └── LICENSE-APPLY ├── test └── test_cov.sh ├── .gitattributes ├── dependencies.txt ├── scripts ├── unit_test_cov.sh ├── changelog.sh ├── check_spelling.sh ├── install_behave.sh ├── ensure_deps.sh └── check_license.sh ├── .codecov.yml ├── README.md ├── .gitignore ├── .circleci └── config.yml ├── version └── version.go ├── CONTRIBUTING.md ├── common ├── common_test.go └── common.go ├── Makefile ├── tools ├── list_buffer_test.go └── list_buffer.go ├── txpool.go ├── LICENSE └── txpool_test.go /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | _build/ 2 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## Development Lead 4 | 5 | - walterkangluo [DSiSc](https://github.com/DSiSc) 6 | 7 | ## Contributors 8 | 9 | None yet. Why not be the first? -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright(c) 2018 DSiSc Group. All Rights Reserved. 3 | # 4 | # SPDX-License-Identifier: Apache-2.0 5 | # 6 | .git 7 | .circleci 8 | .github 9 | .codecov.yml 10 | .mailmap 11 | .travis.yml 12 | -------------------------------------------------------------------------------- /contrib/git/.gitconfig.tmp: -------------------------------------------------------------------------------- 1 | [commit] 2 | template = ~/.gitmessage 3 | 4 | [alias] 5 | co = checkout 6 | ci = commit 7 | br = branch 8 | st = status 9 | last = log -1 10 | lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit 11 | -------------------------------------------------------------------------------- /test/test_cov.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | echo "" > coverage.txt 6 | 7 | for pkg in $(go list ./... | grep -v vendor); do 8 | go test -timeout 5m -race -coverprofile=profile.cov -covermode=atomic "$pkg" 9 | if [ -f profile.cov ]; then 10 | cat profile.cov >> coverage.txt 11 | rm profile.cov 12 | fi 13 | done 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | 3 | * text=auto 4 | 5 | *.sh text eol=lf 6 | *.go text eol=lf 7 | *.yaml text eol=lf 8 | *.yml text eol=lf 9 | *.md text eol=lf 10 | *.json text eol=lf 11 | *.proto text eol=lf 12 | *.py text eol=lf 13 | *.js text eol=lf 14 | *.txt text eol=lf 15 | *.sol linguist-language=Solidity 16 | LICENSE text eol=lf 17 | -------------------------------------------------------------------------------- /dependencies.txt: -------------------------------------------------------------------------------- 1 | # Imported packages that does not exsit under "vendor" folder. 2 | # The following lines listed by git repositories(each line for one git repo) 3 | # alone with compatible version(branch/tag/commit-id). 4 | 5 | github.com/DSiSc/craft:master 6 | github.com/DSiSc/txpool:master 7 | github.com/DSiSc/crypto-suite:master 8 | github.com/DSiSc/blockchain:master 9 | github.com/DSiSc/monkey:master 10 | -------------------------------------------------------------------------------- /scripts/unit_test_cov.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | # Change directory to project root folder 6 | PROJ_FOLDER=$(cd "$(dirname "$0")/..";pwd) 7 | cd $PROJ_FOLDER 8 | 9 | echo "" > coverage.txt 10 | 11 | for pkg in $(go list ./... | grep -v vendor); do 12 | go test -timeout 5m -race -coverprofile=profile.cov -covermode=atomic "$pkg" 13 | if [ -f profile.cov ]; then 14 | cat profile.cov >> coverage.txt 15 | rm profile.cov 16 | fi 17 | done 18 | -------------------------------------------------------------------------------- /.codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | notify: 3 | require_ci_to_pass: yes 4 | 5 | coverage: 6 | precision: 2 7 | round: down 8 | range: "50...80" 9 | 10 | status: 11 | project: yes 12 | patch: yes 13 | changes: no 14 | 15 | parsers: 16 | gcov: 17 | branch_detection: 18 | conditional: yes 19 | loop: yes 20 | method: no 21 | macro: no 22 | 23 | comment: 24 | layout: "header, diff" 25 | behavior: default 26 | require_changes: no 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # txpool 2 | 3 | A high-performance blockchain transaction pool. 4 | 5 | [![Build Status](https://circleci.com/gh/DSiSc/txpool/tree/master.svg?style=shield)](https://circleci.com/gh/DSiSc/txpool/tree/master) 6 | [![codecov](https://codecov.io/gh/DSiSc/txpool/branch/master/graph/badge.svg)](https://codecov.io/gh/DSiSc/txpool) 7 | 8 | ## Getting started 9 | 10 | Running it then should be as simple as: 11 | 12 | ``` 13 | $ make all 14 | ``` 15 | 16 | ### Testing 17 | 18 | ``` 19 | $ make test 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /contrib/git/.gitmessage.tmp: -------------------------------------------------------------------------------- 1 | # head: (): 2 | # - type: feat, fix, docs, style, refactor, test, chore 3 | # - scope: can be empty (eg. if the change is a global or difficult to assign to a single component) 4 | # - subject: start with verb (such as 'change'), 50-character line 5 | # 6 | # body: 72-character wrapped. This should answer: 7 | # * Why was this change necessary? 8 | # * How does it address the problem? 9 | # * Are there any side effects? 10 | # 11 | # footer: 12 | # - Include a link to the ticket, if any. 13 | # - BREAKING CHANGE 14 | # 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # govendor 8 | #vendor/ 9 | 10 | # IDEs 11 | .project 12 | .settings 13 | .idea 14 | .vscode 15 | 16 | # May be used by the Makefile 17 | build/_workspace/ 18 | build/_vendor/pkg 19 | build/bin/ 20 | 21 | # travis, codecov 22 | profile.tmp 23 | profile.cov 24 | coverage.txt 25 | 26 | # tmp 27 | *.sw? 28 | -------------------------------------------------------------------------------- /scripts/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright(c) 2018 DSiSc Group. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | set -x 9 | 10 | SCRIPT_DIR=$(readlink -f "$(dirname $0)") 11 | CHANGELOG_TEMP="CHANGELOG.new" 12 | 13 | echo "## $2\n$(date)" >> ${CHANGELOG_TEMP} 14 | echo "" >> ${CHANGELOG_TEMP} 15 | git log $1..HEAD --oneline | grep -v Merge | sed -e "s/\([0-9|a-z]*\)/* \[\1\](https:\/\/github.com\/DSiSc\/txpool\/commit\/\1)/" >> ${CHANGELOG_TEMP} 16 | echo "" >> ${CHANGELOG_TEMP} 17 | cat ${SCRIPT_DIR}/../CHANGELOG.md >> ${CHANGELOG_TEMP} 18 | mv -f ${CHANGELOG_TEMP} CHANGELOG.md 19 | -------------------------------------------------------------------------------- /contrib/LICENSE-APPLY: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2018 DSiSc Group. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Golang CircleCI 2.0 configuration file 2 | 3 | version: 2 4 | 5 | jobs: 6 | build: 7 | 8 | docker: 9 | - image: circleci/golang:1.10.3 10 | working_directory: /go/src/github.com/DSiSc/txpool 11 | 12 | steps: 13 | - checkout 14 | 15 | - run: 16 | name: Get dependencies 17 | command: make fetch-deps 18 | 19 | - run: 20 | name: Static checks 21 | command: make static-check 22 | 23 | - run: 24 | name: Correctness check 25 | command: make build && make vet 26 | 27 | - run: 28 | name: Test with coverage 29 | command: | 30 | make coverage 31 | bash <(curl -s https://codecov.io/bash) 32 | -------------------------------------------------------------------------------- /scripts/check_spelling.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright(c) 2018 DSiSc Group. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | CHECK=$(git diff --name-only HEAD * | grep -v .png$ | grep -v .git | grep -v ^CHANGELOG \ 9 | | grep -v ^vendor/ | grep -v ^build/ | sort -u) 10 | 11 | if [[ -z "$CHECK" ]]; then 12 | CHECK=$(git diff-tree --no-commit-id --name-only -r $(git log -2 \ 13 | --pretty=format:"%h") | grep -v .png$ | grep -v .git | grep -v ^CHANGELOG \ 14 | | grep -v ^vendor/ | grep -v ^build/ | sort -u) 15 | fi 16 | 17 | echo "Checking changed go files for spelling errors ..." 18 | errs=`echo $CHECK | xargs misspell -source=text` 19 | if [ -z "$errs" ]; then 20 | echo "spell checker passed" 21 | exit 0 22 | fi 23 | echo "The following files are have spelling errors:" 24 | echo "$errs" 25 | exit 0 26 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | // Copyright(c) 2018 DSiSc Group. All Rights Reserved. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package version 16 | 17 | // The git commit that was compiled. This will be filled in by the compiler. 18 | var GitCommit string 19 | 20 | // The main version number that is being run at the moment. 21 | const Version = "0.0.1" 22 | 23 | // A pre-release marker for the version. If this is "" (empty string) 24 | // then it means that it is a final release. Otherwise, this is a pre-release 25 | // such as "dev" (in development) 26 | var VersionPrerelease = "dev" 27 | 28 | var BuildDate = "" 29 | -------------------------------------------------------------------------------- /scripts/install_behave.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright(c) 2018 DSiSc Group. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | 9 | # 10 | # This script is used on Debian based linux distros. 11 | # (i.e., linux that supports the apt packaging manager.) 12 | # 13 | 14 | # Update system 15 | apt-get update -qq 16 | 17 | # Install Python, pip, behave 18 | # 19 | # install python-dev and libyaml-dev to get compiled speedups 20 | apt-get install --yes python-dev 21 | apt-get install --yes libyaml-dev 22 | 23 | apt-get install --yes python-setuptools 24 | apt-get install --yes python-pip 25 | apt-get install --yes build-essential 26 | # required dependencies for cryptography, which is required by pyOpenSSL 27 | # https://cryptography.io/en/stable/installation/#building-cryptography-on-linux 28 | apt-get install --yes libssl-dev libffi-dev 29 | pip install --upgrade pip 30 | 31 | # Pip packages required for behave tests 32 | pip install -r ../devenv/bddtests-requirements.txt 33 | 34 | # install ruby and apiaryio 35 | #apt-get install --yes ruby ruby-dev gcc 36 | #gem install apiaryio 37 | 38 | # Install Tcl prerequisites for busywork 39 | apt-get install --yes tcl tclx tcllib 40 | 41 | # Install NPM for the SDK 42 | apt-get install --yes npm 43 | -------------------------------------------------------------------------------- /scripts/ensure_deps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Change directory to project root folder 4 | PROJ_FOLDER=$(cd "$(dirname "$0")/..";pwd) 5 | cd $PROJ_FOLDER 6 | 7 | # Read "dependencies.txt" under project root 8 | DEPS=$(grep -v "^#" dependencies.txt | grep -v "^$") 9 | 10 | # Go get all the imported packages (except the ones under "vendor" folder) to $GOPATH 11 | for dep in $DEPS; do 12 | dep_repo=$(echo ${dep} | awk -F ':' '{print $1}') 13 | if [ -d "${GOPATH}/src/${dep_repo}" ]; then 14 | cd ${GOPATH}/src/${dep_repo} 15 | git checkout master &> /dev/null 16 | fi 17 | go get -v -u ${dep_repo} 18 | done 19 | 20 | # Check out to desired version 21 | for dep in $DEPS; do 22 | dep_repo=$(echo ${dep} | awk -F ':' '{print $1}') 23 | dep_ver=$(echo ${dep} | awk -F ':' '{print $2}') 24 | if [ -d "${GOPATH}/src/${dep_repo}" ]; then 25 | 26 | echo "[INFO] Ensuring ${dep_repo} on ${dep_ver} ..." 27 | 28 | cd ${GOPATH}/src/${dep_repo} 29 | 30 | git fetch origin > /dev/null 31 | 32 | # Try checkout to ${dep_ver} 33 | git checkout ${dep_ver} > /dev/null && (git pull &> /dev/null | true) 34 | 35 | if [ $? != 0 ]; then 36 | # If failed, checkout to origin/${dep_ver} 37 | git checkout origin/${dep_ver} > /dev/null 38 | if [ $? != 0 ]; then 39 | echo "[ERROR] Got error when checking out ${dep_ver} under ${dep_repo}, please check." 40 | exit 1 41 | else 42 | echo "[INFO] ${dep_repo} is now on ${dep_ver}" 43 | fi 44 | else 45 | echo "[INFO] ${dep_repo} is now on ${dep_ver}" 46 | fi 47 | else 48 | echo "[WARN] ${GOPATH}/src/${dep_repo} not exist, do nothing, please check dependencies.txt." 49 | fi 50 | done 51 | 52 | -------------------------------------------------------------------------------- /scripts/check_license.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Copyright(c) 2018 DSiSc Corp, SecureKey Technologies Inc. All Rights Reserved. 4 | # 5 | # SPDX-License-Identifier: Apache-2.0 6 | # 7 | 8 | 9 | function filterGeneratedFiles { 10 | for f in $@; do 11 | head -n2 $f | grep -qE '// Code generated by' || echo $f 12 | done 13 | } 14 | 15 | function filterExcludedFiles { 16 | CHECK=`echo "$CHECK" | grep -v .png$ | grep -v .rst$ | grep -v ^.git/ \ 17 | | grep -v .pem$ | grep -v .block$ | grep -v .tx$ | grep -v ^LICENSE$ | grep -v _sk$ \ 18 | | grep -v .key$ | grep -v \\.gen.go$ | grep -v ^Gopkg.lock$ \ 19 | | grep -v .md$ | grep -v ^vendor/ | grep -v ^build/ | grep -v .pb.go$ | sort -u` 20 | 21 | CHECK=$(filterGeneratedFiles "$CHECK") 22 | } 23 | 24 | CHECK=$(git diff --name-only --diff-filter=ACMRTUXB HEAD) 25 | filterExcludedFiles 26 | if [[ -z "$CHECK" ]]; then 27 | LAST_COMMITS=($(git log -2 --pretty=format:"%h")) 28 | CHECK=$(git diff-tree --no-commit-id --name-only --diff-filter=ACMRTUXB -r ${LAST_COMMITS[1]} ${LAST_COMMITS[0]}) 29 | filterExcludedFiles 30 | fi 31 | 32 | if [[ -z "$CHECK" ]]; then 33 | echo "All files are excluded from having license headers" 34 | exit 0 35 | fi 36 | 37 | missing=`echo "$CHECK" | xargs ls -d 2>/dev/null | xargs grep -L "SPDX-License-Identifier"` 38 | if [[ -z "$missing" ]]; then 39 | echo "All files have SPDX-License-Identifier headers" 40 | exit 0 41 | fi 42 | echo "The following files are missing SPDX-License-Identifier headers:" 43 | echo "$missing" 44 | echo 45 | echo "Please replace the Apache license header comment text with:" 46 | echo "SPDX-License-Identifier: Apache-2.0" 47 | 48 | echo 49 | echo "Checking committed files for traditional Apache License headers ..." 50 | missing=`echo "$missing" | xargs ls -d 2>/dev/null | xargs grep -L "http://www.apache.org/licenses/LICENSE-2.0"` 51 | if [[ -z "$missing" ]]; then 52 | echo "All remaining files have Apache 2.0 headers" 53 | exit 0 54 | fi 55 | echo "The following files are missing traditional Apache 2.0 headers:" 56 | echo "$missing" 57 | echo "Fatal Error - All files must have a license header" 58 | exit 1 59 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. 4 | 5 | You can contribute in many ways: 6 | 7 | ## Types of Contributions 8 | 9 | ### Report Bugs 10 | 11 | Report bugs at https://github.com/DSiSc/txpool/issues. 12 | 13 | If you are reporting a bug, please include: 14 | 15 | * Your operating system name and version. 16 | * Any details about your local setup that might be helpful in troubleshooting. 17 | * Detailed steps to reproduce the bug. 18 | 19 | ### Fix Bugs 20 | 21 | Look through the GitHub issues for bugs. Anything tagged with "bug" 22 | is open to whoever wants to implement it. 23 | 24 | ### Implement Features 25 | 26 | Look through the GitHub issues for features. Anything tagged with "feature" 27 | is open to whoever wants to implement it. 28 | 29 | ### Write Documentation 30 | 31 | txpool could always use more documentation, whether as part of the 32 | official txpool docs, in docstrings, or even on the web in blog posts, 33 | articles, and such. 34 | 35 | ### Submit Feedback 36 | 37 | The best way to send feedback is to file an issue at https://github.com/DSiSc/txpool/issues. 38 | 39 | If you are proposing a feature: 40 | 41 | * Explain in detail how it would work. 42 | * Keep the scope as narrow as possible, to make it easier to implement. 43 | * Remember that this is a volunteer-driven project, and that contributions 44 | are welcome :) 45 | 46 | ## Get Started! 47 | 48 | Ready to contribute? Here's how to set up `txpool` for local development. 49 | 50 | 1. Fork the `txpool` repo on GitHub. 51 | 2. Clone your fork locally:: 52 | 53 | $ git clone git@github.com:your_name_here/txpool.git 54 | 55 | 3. Create a branch for local development:: 56 | 57 | $ git checkout -b name-of-your-bugfix-or-feature 58 | 59 | Now you can make your changes locally. 60 | 61 | 4. When you're done making changes, check that your changes pass the tests:: 62 | 63 | $ make test 64 | 65 | 6. Commit your changes and push your branch to GitHub, We use [Angular Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines), Thanks for Angular good job.:: 66 | 67 | $ git add . 68 | $ git commit -m "Your detailed description of your changes." 69 | $ git push origin name-of-your-bugfix-or-feature 70 | 71 | 7. Submit a pull request through the GitHub website. 72 | 73 | Pull Request Guidelines 74 | ----------------------- 75 | 76 | Before you submit a pull request, check that it meets these guidelines: 77 | 78 | 1. The pull request should include tests. 79 | 2. If the pull request adds functionality, the docs should be updated. Put 80 | your new functionality into a function with a docstring, and add the 81 | feature to the list in README.md. 82 | -------------------------------------------------------------------------------- /common/common_test.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "fmt" 5 | "github.com/DSiSc/craft/types" 6 | "github.com/stretchr/testify/assert" 7 | "math/big" 8 | "testing" 9 | ) 10 | 11 | var emptyTx *types.Transaction 12 | 13 | func TestNewTransaction(t *testing.T) { 14 | assert := assert.New(t) 15 | b := types.Address{ 16 | 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, 17 | 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, 18 | } 19 | emptyTx = NewTransaction( 20 | 0, 21 | b, 22 | big.NewInt(0), 23 | 0, 24 | big.NewInt(0), 25 | b[:10], 26 | b, 27 | ) 28 | assert.NotNil(emptyTx) 29 | assert.Equal(emptyTx.Data.From, &b) 30 | assert.Equal(emptyTx.Data.Recipient, &b) 31 | assert.Equal(emptyTx.Data.AccountNonce, uint64(0)) 32 | assert.Equal(emptyTx.Data.GasLimit, uint64(0)) 33 | assert.Equal(emptyTx.Data.Price, big.NewInt(0)) 34 | } 35 | 36 | func TestCopyBytes(t *testing.T) { 37 | s := CopyBytes(nil) 38 | assert.Nil(t, s) 39 | b := []byte{ 40 | 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, 41 | 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, 42 | } 43 | c := CopyBytes(b) 44 | assert.Equal(t, b, c) 45 | } 46 | 47 | func TestTxHash(t *testing.T) { 48 | assert := assert.New(t) 49 | b := types.Address{ 50 | 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, 51 | 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, 52 | } 53 | emptyTx = NewTransaction( 54 | 0, 55 | b, 56 | big.NewInt(0), 57 | 0, 58 | big.NewInt(0), 59 | b[:10], 60 | b, 61 | ) 62 | exceptHash := types.Hash{0x63, 0xa2, 0xa4, 0x4, 0x8d, 0x2c, 0xe4, 0xe8, 0x95, 0xd9, 0x24, 0x21, 0xb3, 0xc7, 0x36, 0xa8, 0xed, 0xf0, 0x83, 0xb7, 0xab, 0x9d, 0xf6, 0xee, 0x7f, 0x4b, 0x57, 0x19, 0xf9, 0x78, 0xef, 0x93} 63 | txHash := TxHash(emptyTx) 64 | assert.Equal(exceptHash, txHash) 65 | 66 | exceptHash1 := TxHash(emptyTx) 67 | assert.Equal(exceptHash, exceptHash1) 68 | } 69 | 70 | // mock a transaction 71 | func mock_transactions(num int) []*types.Transaction { 72 | to := make([]types.Address, num) 73 | for m := 0; m < num; m++ { 74 | for j := 0; j < types.AddressLength; j++ { 75 | to[m][j] = byte(m) 76 | } 77 | } 78 | amount := new(big.Int) 79 | txList := make([]*types.Transaction, 0) 80 | for i := 0; i < num; i++ { 81 | tx := NewTransaction(uint64(i), to[i], amount, uint64(i), amount, nil, to[i]) 82 | txList = append(txList, tx) 83 | } 84 | return txList 85 | } 86 | 87 | func TestHash(t *testing.T) { 88 | assert := assert.New(t) 89 | //tx := mock_transactions(1) 90 | hash := TxHash(mock_transactions(1)[0]) 91 | fmt.Printf("%x\n", hash) 92 | assert.NotNil(hash) 93 | } 94 | 95 | func TestHexToAddress(t *testing.T) { 96 | addHex := "333c3310824b7c685133f2bedb2ca4b8b4df633d" 97 | address := HexToAddress(addHex) 98 | b := types.Address{ 99 | 0x33, 0x3c, 0x33, 0x10, 0x82, 0x4b, 0x7c, 0x68, 0x51, 0x33, 100 | 0xf2, 0xbe, 0xdb, 0x2c, 0xa4, 0xb8, 0xb4, 0xdf, 0x63, 0x3d, 101 | } 102 | assert.Equal(t, b, address) 103 | } 104 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright(c) 2018 DSiSc Group. All Rights Reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | VERSION=$(shell grep "const Version" version/version.go | sed -E 's/.*"(.+)"$$/\1/') 16 | GIT_COMMIT=$(shell git rev-parse HEAD) 17 | GIT_DIRTY=$(shell test -n "`git status --porcelain`" && echo "+CHANGES" || true) 18 | BUILD_DATE=$(shell date '+%Y-%m-%d-%H:%M:%S') 19 | 20 | .PHONY: default help all build test unit-test devenv gotools clean coverage 21 | 22 | default: all 23 | 24 | help: 25 | @echo 'Management commands for DSiSc/txpool:' 26 | @echo 27 | @echo 'Usage:' 28 | @echo ' make lint Check code style.' 29 | @echo ' make spelling Check code spelling.' 30 | @echo ' make fmt Check code formatting.' 31 | @echo ' make static-check Static code check: style & spelling & formatting.' 32 | @echo ' make build Compile the project.' 33 | @echo ' make vet Examine source code and reports suspicious constructs.' 34 | @echo ' make unit-test Run unit tests with coverage report.' 35 | @echo ' make test Run unit tests with coverage report.' 36 | @echo ' make devenv Prepare devenv for test or build.' 37 | @echo ' make fetch-deps Run govendor fetch for deps.' 38 | @echo ' make gotools Prepare go tools depended.' 39 | @echo ' make clean Clean the directory tree.' 40 | @echo 41 | 42 | all: static-check build test 43 | 44 | fmt: 45 | gofmt -d -l . 46 | 47 | spelling: 48 | bash scripts/check_spelling.sh 49 | 50 | lint: 51 | @echo "Check code style..." 52 | golint `go list ./...` 53 | 54 | static-check: fmt spelling lint 55 | 56 | build: 57 | @echo "building txpool ${VERSION}" 58 | @echo "GOPATH=${GOPATH}" 59 | go build -v -ldflags "-X github.com/DSiSc/txpool/version.GitCommit=${GIT_COMMIT}${GIT_DIRTY} -X github.com/DSiSc/txpool/version.BuildDate=${BUILD_DATE}" ./... 60 | 61 | vet: 62 | @echo "Examine source code and reports suspicious constructs..." 63 | go vet `go list ./...` 64 | 65 | unit-test: 66 | @echo "Run unit tests without coverage report..." 67 | go test -v -count=1 -race ./... 68 | 69 | coverage: 70 | @echo "Run unit tests with coverage report..." 71 | bash scripts/unit_test_cov.sh 72 | 73 | test: vet unit-test 74 | 75 | get-tools: 76 | # official tools 77 | go get -u golang.org/x/lint/golint 78 | @# go get -u golang.org/x/tools/cmd/gotype 79 | @# go get -u golang.org/x/tools/cmd/goimports 80 | @# go get -u golang.org/x/tools/cmd/godoc 81 | @# go get -u golang.org/x/tools/cmd/gorename 82 | @# go get -u golang.org/x/tools/cmd/gomvpkg 83 | 84 | # thirdparty tools 85 | go get -u github.com/stretchr/testify 86 | @# go get -u github.com/kardianos/govendor 87 | @# go get -u github.com/axw/gocov/... 88 | @# go get -u github.com/client9/misspell/cmd/misspell 89 | 90 | fetch-deps: get-tools 91 | @echo "Run go get to fetch dependencies as described in dependencies.txt ..." 92 | @bash scripts/ensure_deps.sh 93 | 94 | ## tools & deps 95 | devenv: get-tools fetch-deps 96 | -------------------------------------------------------------------------------- /tools/list_buffer_test.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "github.com/DSiSc/craft/types" 5 | "github.com/DSiSc/txpool/common" 6 | "github.com/stretchr/testify/assert" 7 | "math/big" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | mockHash = common.HexToHash("0x776a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd191") 13 | mockHash1 = common.HexToHash("0x776a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd190") 14 | mockHash2 = common.HexToHash("0x776a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd189") 15 | mockAddr = common.HexToAddress("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b") 16 | ) 17 | 18 | func mockTransaction() *types.Transaction { 19 | return mockTransaction1(mockHash, mockAddr) 20 | } 21 | 22 | func mockTransaction1(hash types.Hash, from types.Address) *types.Transaction { 23 | tx := &types.Transaction{ 24 | Data: types.TxData{ 25 | From: &from, 26 | }, 27 | } 28 | tx.Hash.Store(hash) 29 | return tx 30 | } 31 | 32 | func TestNewListBuffer(t *testing.T) { 33 | assert := assert.New(t) 34 | lb := NewListBuffer(100, 100) 35 | assert.NotNil(lb) 36 | assert.Equal(0, lb.Len()) 37 | } 38 | 39 | func TestListBuffer_AddElement(t *testing.T) { 40 | assert := assert.New(t) 41 | lb := NewListBuffer(100, 100) 42 | assert.NotNil(lb) 43 | tx := mockTransaction() 44 | assert.Nil(lb.AddTx(tx)) 45 | assert.NotNil(lb.AddTx(tx)) 46 | } 47 | 48 | func TestListBuffer_GetElement(t *testing.T) { 49 | assert := assert.New(t) 50 | lb := NewListBuffer(100, 100) 51 | assert.NotNil(lb) 52 | tx := mockTransaction() 53 | assert.Nil(lb.AddTx(tx)) 54 | assert.NotNil(lb.GetTx(tx.Hash.Load().(types.Hash))) 55 | } 56 | 57 | func TestListBuffer_Front(t *testing.T) { 58 | assert := assert.New(t) 59 | lb := NewListBuffer(100, 100) 60 | assert.NotNil(lb) 61 | assert.Nil(lb.AddTx(mockTransaction1(common.HexToHash("0x776a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd191"), common.HexToAddress("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")))) 62 | assert.Nil(lb.AddTx(mockTransaction1(common.HexToHash("0x676a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd191"), common.HexToAddress("0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b")))) 63 | assert.Nil(lb.AddTx(mockTransaction1(common.HexToHash("0x576a2bbddcb56d8bc5a97ca8058a76fa5bb27b2a589c80cf508b86d083bdd191"), common.HexToAddress("0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b")))) 64 | e := lb.TimedTxGroups() 65 | assert.Equal(2, len(e)) 66 | } 67 | 68 | func TestListBuffer_Len(t *testing.T) { 69 | assert := assert.New(t) 70 | lb := NewListBuffer(100, 100) 71 | assert.NotNil(lb) 72 | tx := mockTransaction() 73 | assert.Nil(lb.AddTx(tx)) 74 | assert.Equal(1, lb.Len()) 75 | } 76 | 77 | func TestListBuffer_AddTx(t *testing.T) { 78 | assert := assert.New(t) 79 | lb := NewListBuffer(100, 100) 80 | assert.NotNil(lb) 81 | tx := mockTransaction() 82 | assert.Nil(lb.AddTx(tx)) 83 | assert.Equal(1, lb.Len()) 84 | assert.Equal(1, len(lb.txs)) 85 | 86 | tx1 := mockTransaction1(mockHash1, *tx.Data.From) 87 | tx1.Data.Price = big.NewInt(2) 88 | assert.Nil(lb.AddTx(tx1)) 89 | assert.Equal(1, lb.Len()) 90 | assert.Equal(1, len(lb.txs)) 91 | } 92 | 93 | func TestListBuffer_RemoveOlderTx(t *testing.T) { 94 | assert := assert.New(t) 95 | lb := NewListBuffer(100, 100) 96 | assert.NotNil(lb) 97 | tx := mockTransaction1(mockHash, mockAddr) 98 | assert.Nil(lb.AddTx(tx)) 99 | 100 | tx = mockTransaction1(mockHash1, mockAddr) 101 | tx.Data.AccountNonce = 1 102 | assert.Nil(lb.AddTx(tx)) 103 | 104 | tx = mockTransaction1(mockHash2, mockAddr) 105 | tx.Data.AccountNonce = 2 106 | assert.Nil(lb.AddTx(tx)) 107 | 108 | lb.RemoveOlderTx(mockAddr, 1) 109 | assert.Equal(1, lb.Len()) 110 | assert.Equal(1, len(lb.txs)) 111 | assert.Equal(1, lb.timedTxGroups[mockAddr].Len()) 112 | } 113 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/hex" 5 | gconf "github.com/DSiSc/craft/config" 6 | "github.com/DSiSc/craft/rlp" 7 | "github.com/DSiSc/craft/types" 8 | "github.com/DSiSc/crypto-suite/crypto/sha3" 9 | "hash" 10 | "math/big" 11 | ) 12 | 13 | func HashAlg() hash.Hash { 14 | var alg string 15 | if value, ok := gconf.GlobalConfig.Load(gconf.HashAlgName); ok { 16 | alg = value.(string) 17 | } else { 18 | alg = "SHA256" 19 | } 20 | return sha3.NewHashByAlgName(alg) 21 | } 22 | 23 | func rlpHash(x interface{}) (h types.Hash) { 24 | hw := HashAlg() 25 | rlp.Encode(hw, x) 26 | hw.Sum(h[:0]) 27 | return h 28 | } 29 | 30 | // Hash hashes the RLP encoding of tx. 31 | // It uniquely identifies the transaction. 32 | func TxHash(tx *types.Transaction) types.Hash { 33 | if hash := tx.Hash.Load(); hash != nil { 34 | return hash.(types.Hash) 35 | } 36 | v := rlpHash(tx) 37 | tx.Hash.Store(v) 38 | return v 39 | } 40 | 41 | func CopyBytes(b []byte) (copiedBytes []byte) { 42 | if b == nil { 43 | return nil 44 | } 45 | copiedBytes = make([]byte, len(b)) 46 | copy(copiedBytes, b) 47 | 48 | return 49 | } 50 | 51 | // Lengths of hashes and addresses in bytes. 52 | const ( 53 | AddressLength = 20 54 | HashLength = 32 55 | ) 56 | 57 | // HexToHash sets byte representation of s to hash. 58 | // If b is larger than len(h), b will be cropped from the left. 59 | func HexToHash(s string) types.Hash { return BytesToHash(FromHex(s)) } 60 | 61 | // BytesToHash sets b to hash. 62 | // If b is larger than len(h), b will be cropped from the left. 63 | func BytesToHash(b []byte) types.Hash { 64 | var h types.Hash 65 | if len(b) > len(h) { 66 | b = b[len(b)-HashLength:] 67 | } 68 | 69 | copy(h[HashLength-len(b):], b) 70 | return h 71 | } 72 | 73 | // HexToAddress returns Address with byte values of s. 74 | // If s is larger than len(h), s will be cropped from the left. 75 | func HexToAddress(s string) types.Address { return BytesToAddress(FromHex(s)) } 76 | 77 | // BytesToAddress returns Address with value b. 78 | // If b is larger than len(h), b will be cropped from the left. 79 | func BytesToAddress(b []byte) types.Address { 80 | var a types.Address 81 | if len(b) > len(a) { 82 | b = b[len(b)-AddressLength:] 83 | } 84 | copy(a[AddressLength-len(b):], b) 85 | return a 86 | } 87 | 88 | // FromHex returns the bytes represented by the hexadecimal string s. 89 | // s may be prefixed with "0x". 90 | func FromHex(s string) []byte { 91 | if len(s) > 1 { 92 | if s[0:2] == "0x" || s[0:2] == "0X" { 93 | s = s[2:] 94 | } 95 | } 96 | if len(s)%2 == 1 { 97 | s = "0" + s 98 | } 99 | return Hex2Bytes(s) 100 | } 101 | 102 | // Hex2Bytes returns the bytes represented by the hexadecimal string str. 103 | func Hex2Bytes(str string) []byte { 104 | h, _ := hex.DecodeString(str) 105 | return h 106 | } 107 | 108 | // New a transaction 109 | func newTransaction(nonce uint64, to *types.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, from *types.Address) *types.Transaction { 110 | if len(data) > 0 { 111 | data = CopyBytes(data) 112 | } 113 | d := types.TxData{ 114 | AccountNonce: nonce, 115 | Recipient: to, 116 | From: from, 117 | Payload: data, 118 | Amount: new(big.Int), 119 | GasLimit: gasLimit, 120 | Price: new(big.Int), 121 | V: new(big.Int), 122 | R: new(big.Int), 123 | S: new(big.Int), 124 | } 125 | if amount != nil { 126 | d.Amount.Set(amount) 127 | } 128 | if gasPrice != nil { 129 | d.Price.Set(gasPrice) 130 | } 131 | 132 | return &types.Transaction{Data: d} 133 | } 134 | 135 | func NewTransaction(nonce uint64, to types.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte, from types.Address) *types.Transaction { 136 | return newTransaction(nonce, &to, amount, gasLimit, gasPrice, data, &from) 137 | } 138 | -------------------------------------------------------------------------------- /tools/list_buffer.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "container/list" 5 | "errors" 6 | "github.com/DSiSc/craft/types" 7 | "time" 8 | ) 9 | 10 | var ( 11 | DuplicateError = errors.New("duplicate insert") 12 | BufferIsFullError = errors.New("buffer is full") 13 | ) 14 | 15 | // TimedTransaction contains a transaction with the time added to buffer 16 | type TimedTransaction struct { 17 | Tx *types.Transaction 18 | TimeStamp time.Time 19 | } 20 | 21 | // ListBuffer is a Tx list buffer implementation. 22 | type ListBuffer struct { 23 | limit uint64 24 | maxCacheTime uint64 25 | len int 26 | txs map[types.Hash]*types.Transaction 27 | timedTxGroups map[types.Address]*list.List 28 | } 29 | 30 | // NewListBuffer create a Tx list buffer instance 31 | func NewListBuffer(limit uint64, maxCacheTime uint64) *ListBuffer { 32 | return &ListBuffer{ 33 | limit: limit, 34 | maxCacheTime: maxCacheTime, 35 | len: 0, 36 | timedTxGroups: make(map[types.Address]*list.List), 37 | txs: make(map[types.Hash]*types.Transaction), 38 | } 39 | } 40 | 41 | // AddTx add an element to list buffer 42 | func (self *ListBuffer) AddTx(tx *types.Transaction) error { 43 | hash := tx.Hash.Load().(types.Hash) 44 | if self.txs[hash] != nil { 45 | return DuplicateError 46 | } else { 47 | self.txs[hash] = tx 48 | } 49 | 50 | // insert timedTx into the correct index in self.timedTxGroups 51 | if self.timedTxGroups[*tx.Data.From] == nil { 52 | self.timedTxGroups[*tx.Data.From] = list.New() 53 | } 54 | sameFromTxs := self.timedTxGroups[*tx.Data.From] 55 | if self.insertOrReplace(sameFromTxs, tx) { 56 | return nil 57 | } 58 | 59 | // check limit 60 | if uint64(self.len) <= self.limit { 61 | return nil 62 | } 63 | 64 | // check timeout tx in self group 65 | if self.removeTimeOutTx(sameFromTxs) { 66 | return nil 67 | } 68 | 69 | // delete timeout tx in other group 70 | if self.RemoveTimeOutTx() { 71 | return nil 72 | } 73 | 74 | // remove last tx 75 | backTimedTx := sameFromTxs.Back().Value.(*TimedTransaction) 76 | self.RemoveTx(backTimedTx.Tx.Hash.Load().(types.Hash)) 77 | if backTimedTx.Tx.Data.AccountNonce == tx.Data.AccountNonce { 78 | return BufferIsFullError 79 | } 80 | return nil 81 | } 82 | 83 | // GetTx get an element from list buffer 84 | func (self *ListBuffer) GetTx(hash types.Hash) *types.Transaction { 85 | return self.txs[hash] 86 | } 87 | 88 | // RemoveTx remove an element from list buffer 89 | func (self *ListBuffer) RemoveTx(hash types.Hash) { 90 | if elem := self.txs[hash]; elem != nil { 91 | delete(self.txs, hash) 92 | self.deleteTx(*elem.Data.From, elem.Data.AccountNonce) 93 | self.decLen() 94 | } 95 | } 96 | 97 | // RemoveOlderTx remove tx nonce less than specified nonce from buffer 98 | func (self *ListBuffer) RemoveOlderTx(addr types.Address, nonce uint64) { 99 | if l := self.timedTxGroups[addr]; l != nil { 100 | for firstE := l.Front(); firstE != nil; { 101 | firstTx := firstE.Value.(*TimedTransaction) 102 | firstTxHash := firstTx.Tx.Hash.Load().(types.Hash) 103 | if firstTx.Tx.Data.AccountNonce > nonce { 104 | return 105 | } 106 | 107 | nextE := firstE.Next() 108 | if firstTx.Tx.Data.AccountNonce <= nonce { 109 | delete(self.txs, firstTxHash) 110 | l.Remove(firstE) 111 | self.decLen() 112 | } 113 | firstE = nextE 114 | } 115 | if l.Len() <= 0 { 116 | delete(self.timedTxGroups, addr) 117 | } 118 | } 119 | } 120 | 121 | // RemoveTimeOutTx remove an timeout Tx from list buffer, return true if exists timeout Tx. 122 | func (self *ListBuffer) RemoveTimeOutTx() bool { 123 | for _, timedTxGroup := range self.timedTxGroups { 124 | if self.removeTimeOutTx(timedTxGroup) { 125 | return true 126 | } 127 | } 128 | return false 129 | } 130 | 131 | // TimedTxGroups returns the tx groups of ListBuffer. 132 | func (self *ListBuffer) TimedTxGroups() map[types.Address]*list.List { 133 | return self.timedTxGroups 134 | } 135 | 136 | // NonceInBuffer returns the nonce in buffer. 137 | func (self *ListBuffer) NonceInBuffer(from types.Address) uint64 { 138 | if timedTxGroup := self.timedTxGroups[from]; timedTxGroup != nil { 139 | timedTx := timedTxGroup.Back().Value.(*TimedTransaction) 140 | return timedTx.Tx.Data.AccountNonce 141 | } 142 | return 0 143 | } 144 | 145 | // Len returns the number of txs of ListBuffer. 146 | func (self *ListBuffer) Len() int { 147 | return self.len 148 | } 149 | 150 | // insert into group if not exist same nonce tx, else update the exist tx. return true if exists same nonce tx. 151 | func (self *ListBuffer) insertOrReplace(sameFromTxs *list.List, tx *types.Transaction) bool { 152 | timedTx := &TimedTransaction{ 153 | Tx: tx, 154 | TimeStamp: time.Now(), 155 | } 156 | 157 | for e := sameFromTxs.Back(); e != nil; e = e.Prev() { 158 | eTx := e.Value.(*TimedTransaction) 159 | if eTx.Tx.Data.AccountNonce == tx.Data.AccountNonce { 160 | e.Value = timedTx 161 | 162 | // delete previous tx in txList cache 163 | eHash := eTx.Tx.Hash.Load().(types.Hash) 164 | delete(self.txs, eHash) 165 | return true 166 | } 167 | if eTx.Tx.Data.AccountNonce < tx.Data.AccountNonce { 168 | sameFromTxs.InsertAfter(timedTx, e) 169 | self.incLen() 170 | return false 171 | } 172 | } 173 | 174 | sameFromTxs.PushFront(timedTx) 175 | self.incLen() 176 | return false 177 | } 178 | 179 | // remove an timeout Tx from list buffer, return true if exists timeout Tx. 180 | func (self *ListBuffer) removeTimeOutTx(timedTxGroup *list.List) bool { 181 | fontTx := timedTxGroup.Front().Value.(*TimedTransaction) 182 | if time.Now().After(fontTx.TimeStamp.Add(time.Duration(self.maxCacheTime) * time.Second)) { 183 | lastTx := timedTxGroup.Back().Value.(*TimedTransaction) 184 | self.RemoveTx(lastTx.Tx.Hash.Load().(types.Hash)) 185 | return true 186 | } else { 187 | return false 188 | } 189 | } 190 | 191 | // delete Tx from self.timedTxGroups 192 | func (self *ListBuffer) deleteTx(addr types.Address, nonce uint64) { 193 | if l := self.timedTxGroups[addr]; l != nil { 194 | for firstE := l.Front(); firstE != nil; firstE = firstE.Next() { 195 | firstTx := firstE.Value.(*TimedTransaction) 196 | if firstTx.Tx.Data.AccountNonce > nonce { 197 | return 198 | } 199 | if firstTx.Tx.Data.AccountNonce == nonce { 200 | l.Remove(firstE) 201 | break 202 | } 203 | } 204 | if l.Len() <= 0 { 205 | delete(self.timedTxGroups, addr) 206 | } 207 | } 208 | } 209 | 210 | // increase length of buffer 211 | func (self *ListBuffer) incLen() { 212 | self.len++ 213 | } 214 | 215 | // decrease length of buffer 216 | func (self *ListBuffer) decLen() { 217 | self.len-- 218 | } 219 | -------------------------------------------------------------------------------- /txpool.go: -------------------------------------------------------------------------------- 1 | // TxPool contains all currently known transactions. 2 | // Transactions enter the pool when they are received from the network or submitted locally. 3 | // They exit the pool when they are included in the blockchain. 4 | // The pool seperate processable transactions (which can be applied to the current state) and future transactions. 5 | // Transactions move between those two states over time as they are received and processed. 6 | 7 | package txpool 8 | 9 | import ( 10 | "fmt" 11 | "github.com/DSiSc/craft/log" 12 | "github.com/DSiSc/craft/monitor" 13 | "github.com/DSiSc/craft/types" 14 | "github.com/DSiSc/repository" 15 | "github.com/DSiSc/txpool/common" 16 | "github.com/DSiSc/txpool/tools" 17 | "sync" 18 | ) 19 | 20 | type TxsPool interface { 21 | // AddTx add a transaction to the txpool. 22 | AddTx(tx *types.Transaction) error 23 | 24 | // DelTxs delete the transactions which in processing queue. 25 | // Once a block was committed, transaction contained in the block can be removed. 26 | DelTxs(txs []*types.Transaction) 27 | 28 | // GetTxs gets the transactions which in pending status. 29 | GetTxs() []*types.Transaction 30 | } 31 | 32 | type TxPool struct { 33 | config TxPoolConfig 34 | txBuffer *tools.ListBuffer 35 | chain *repository.Repository 36 | mu sync.RWMutex 37 | eventCenter types.EventCenter 38 | } 39 | 40 | // TxPoolConfig are the configuration parameters of the transaction pool. 41 | type TxPoolConfig struct { 42 | GlobalSlots uint64 // Maximum number of executable transaction slots for txpool 43 | MaxTrsPerBlock uint64 // Maximum num of transactions a block 44 | TxMaxCacheTime uint64 // Maximum cache time(second) of transactions in tx pool 45 | } 46 | 47 | var DefaultTxPoolConfig = TxPoolConfig{ 48 | GlobalSlots: 40960, 49 | MaxTrsPerBlock: 20480, 50 | TxMaxCacheTime: 600, 51 | } 52 | 53 | var GlobalTxsPool *TxPool 54 | 55 | // sanitize checks the provided user configurations and changes anything that's unreasonable or unworkable. 56 | func (config *TxPoolConfig) sanitize() { 57 | if config.GlobalSlots < 1 || config.GlobalSlots > DefaultTxPoolConfig.GlobalSlots { 58 | log.Warn("Sanitizing invalid txs pool global slots %d.", config.GlobalSlots) 59 | config.GlobalSlots = DefaultTxPoolConfig.GlobalSlots 60 | } 61 | if config.MaxTrsPerBlock < 1 || config.MaxTrsPerBlock > DefaultTxPoolConfig.MaxTrsPerBlock { 62 | log.Warn("Sanitizing invalid txs pool max num of transactions a block %d.", config.MaxTrsPerBlock) 63 | config.MaxTrsPerBlock = DefaultTxPoolConfig.MaxTrsPerBlock 64 | } 65 | if config.TxMaxCacheTime <= 0 || config.TxMaxCacheTime > DefaultTxPoolConfig.TxMaxCacheTime { 66 | log.Warn("Sanitizing invalid txs pool max num cache time(%ds) of transactions in tx pool.", config.TxMaxCacheTime) 67 | config.TxMaxCacheTime = DefaultTxPoolConfig.TxMaxCacheTime 68 | } 69 | } 70 | 71 | // NewTxPool creates a new transaction pool to gather, sort and filter inbound transactions from the network and local. 72 | func NewTxPool(config TxPoolConfig, eventCenter types.EventCenter) TxsPool { 73 | config.sanitize() 74 | // Create the transaction pool with its initial settings 75 | pool := &TxPool{ 76 | config: config, 77 | txBuffer: tools.NewListBuffer(config.GlobalSlots, config.TxMaxCacheTime), 78 | eventCenter: eventCenter, 79 | } 80 | GlobalTxsPool = pool 81 | 82 | // subscribe block commit event 83 | pool.eventCenter.Subscribe(types.EventBlockCommitted, pool.updateChainInstance) 84 | pool.eventCenter.Subscribe(types.EventBlockWritten, pool.updateChainInstance) 85 | 86 | return pool 87 | } 88 | 89 | // Get pending txs from txpool. 90 | func (pool *TxPool) GetTxs() []*types.Transaction { 91 | txList := make([]*types.Transaction, 0) 92 | pool.mu.RLock() 93 | defer pool.mu.RUnlock() 94 | log.Debug("total number of tx in pool is: %d", pool.txBuffer.Len()) 95 | for addr, l := range pool.txBuffer.TimedTxGroups() { 96 | startNonce := pool.getChainNonce(addr) 97 | log.Debug("account %x chain nonce %d VS %d", addr, startNonce, pool.txBuffer.NonceInBuffer(addr)) 98 | for elem := l.Front(); elem != nil; { 99 | nextElem := elem.Next() 100 | timedTx := elem.Value.(*tools.TimedTransaction) 101 | if timedTx.Tx.Data.AccountNonce == startNonce { 102 | txList = append(txList, timedTx.Tx) 103 | startNonce++ 104 | if uint64(len(txList)) >= pool.config.MaxTrsPerBlock { 105 | monitor.JTMetrics.TxpoolOutgoingTx.Add(float64(len(txList))) 106 | return txList 107 | } 108 | } else if timedTx.Tx.Data.AccountNonce < startNonce { 109 | pool.txBuffer.RemoveTx(timedTx.Tx.Hash.Load().(types.Hash)) 110 | } 111 | elem = nextElem 112 | } 113 | } 114 | monitor.JTMetrics.TxpoolOutgoingTx.Add(float64(len(txList))) 115 | return txList 116 | } 117 | 118 | // Update processing queue, clean txs from process and all queue. 119 | func (pool *TxPool) DelTxs(txs []*types.Transaction) { 120 | pool.mu.Lock() 121 | defer pool.mu.Unlock() 122 | for _, tx := range txs { 123 | pool.txBuffer.RemoveOlderTx(*tx.Data.From, tx.Data.AccountNonce) 124 | } 125 | } 126 | 127 | // Adding transaction to the txpool 128 | func (pool *TxPool) AddTx(tx *types.Transaction) error { 129 | hash := common.TxHash(tx) 130 | monitor.JTMetrics.TxpoolIngressTx.Add(float64(1)) 131 | pool.mu.Lock() 132 | chainNonce := pool.getChainNonce(*tx.Data.From) 133 | if tx.Data.AccountNonce < chainNonce { 134 | pool.mu.Unlock() 135 | return fmt.Errorf("Tx %x nonce is too low", hash) 136 | } 137 | 138 | preLen := pool.txBuffer.Len() 139 | if err := pool.txBuffer.AddTx(tx); err != nil { 140 | pool.mu.Unlock() 141 | if err == tools.DuplicateError { 142 | monitor.JTMetrics.TxpoolDuplacatedTx.Add(float64(1)) 143 | log.Debug("The tx %x has exist, please confirm.", hash) 144 | return fmt.Errorf("the tx %x has exist", hash) 145 | } else { 146 | return fmt.Errorf("Tx pool is full, will discard tx %x. ", hash) 147 | } 148 | } 149 | 150 | if pool.txBuffer.Len() <= preLen { 151 | log.Error("Tx pool is full, have discard some tx.") 152 | monitor.JTMetrics.TxpoolDiscardedTx.Add(float64(1)) 153 | } else { 154 | monitor.JTMetrics.TxpoolPooledTx.Add(float64(1)) 155 | } 156 | pool.mu.Unlock() 157 | log.Debug("tx num in pool: %d", pool.txBuffer.Len()) 158 | pool.eventCenter.Notify(types.EventAddTxToTxPool, tx) 159 | return nil 160 | } 161 | 162 | func GetTxByHash(hash types.Hash) *types.Transaction { 163 | GlobalTxsPool.mu.RLock() 164 | defer GlobalTxsPool.mu.RUnlock() 165 | if txElem := GlobalTxsPool.txBuffer.GetTx(hash); txElem != nil { 166 | return txElem 167 | } 168 | return nil 169 | } 170 | 171 | func GetPoolNonce(address types.Address) uint64 { 172 | GlobalTxsPool.mu.RLock() 173 | defer GlobalTxsPool.mu.RUnlock() 174 | return GlobalTxsPool.txBuffer.NonceInBuffer(address) 175 | } 176 | 177 | // get account's nonce from chain 178 | func (pool *TxPool) getChainNonce(address types.Address) uint64 { 179 | if nil == pool.chain { 180 | pool.updateChainInstanceWithoutLock() 181 | } 182 | return pool.chain.GetNonce(address) 183 | } 184 | 185 | // update chain instance after committing block 186 | func (pool *TxPool) updateChainInstance(event interface{}) { 187 | pool.mu.Lock() 188 | defer pool.mu.Unlock() 189 | pool.updateChainInstanceWithoutLock() 190 | } 191 | 192 | // update chain instance after committing block 193 | func (pool *TxPool) updateChainInstanceWithoutLock() { 194 | if latestRepo, err := repository.NewLatestStateRepository(); err == nil { 195 | pool.chain = latestRepo 196 | } else { 197 | log.Error("failed to get latest blockchain, as: %v. We will panic tx pool, as error is not recoverable", err) 198 | panic(fmt.Sprintf("failed to get latest blockchain, as: %v", err)) 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /txpool_test.go: -------------------------------------------------------------------------------- 1 | package txpool 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/DSiSc/craft/log" 7 | "github.com/DSiSc/craft/types" 8 | "github.com/DSiSc/monkey" 9 | "github.com/DSiSc/repository" 10 | "github.com/DSiSc/txpool/common" 11 | "github.com/stretchr/testify/assert" 12 | "math/big" 13 | "reflect" 14 | "sync" 15 | "testing" 16 | "time" 17 | ) 18 | 19 | type MockEvent struct { 20 | m sync.RWMutex 21 | Subscribers map[types.EventType]map[types.Subscriber]types.EventFunc 22 | } 23 | 24 | func NewMockEvent() types.EventCenter { 25 | return &MockEvent{ 26 | Subscribers: make(map[types.EventType]map[types.Subscriber]types.EventFunc), 27 | } 28 | } 29 | 30 | // adds a new subscriber to Event. 31 | func (e *MockEvent) Subscribe(eventType types.EventType, eventFunc types.EventFunc) types.Subscriber { 32 | e.m.Lock() 33 | defer e.m.Unlock() 34 | 35 | sub := make(chan interface{}) 36 | _, ok := e.Subscribers[eventType] 37 | if !ok { 38 | e.Subscribers[eventType] = make(map[types.Subscriber]types.EventFunc) 39 | } 40 | e.Subscribers[eventType][sub] = eventFunc 41 | 42 | return sub 43 | } 44 | 45 | // UnSubscribe removes the specified subscriber 46 | func (e *MockEvent) UnSubscribe(eventType types.EventType, subscriber types.Subscriber) (err error) { 47 | e.m.Lock() 48 | defer e.m.Unlock() 49 | 50 | subEvent, ok := e.Subscribers[eventType] 51 | if !ok { 52 | err = errors.New("event type not exist") 53 | return 54 | } 55 | 56 | delete(subEvent, subscriber) 57 | close(subscriber) 58 | 59 | return 60 | } 61 | 62 | // Notify subscribers that Subscribe specified event 63 | func (e *MockEvent) Notify(eventType types.EventType, value interface{}) (err error) { 64 | 65 | e.m.RLock() 66 | defer e.m.RUnlock() 67 | 68 | subs, ok := e.Subscribers[eventType] 69 | if !ok { 70 | err = errors.New("event type not register") 71 | return 72 | } 73 | 74 | switch value.(type) { 75 | case error: 76 | log.Error("Receive errors is [%v].", value) 77 | } 78 | log.Info("Receive eventType is [%d].", eventType) 79 | 80 | for _, event := range subs { 81 | go e.NotifySubscriber(event, value) 82 | } 83 | return nil 84 | } 85 | 86 | func (e *MockEvent) NotifySubscriber(eventFunc types.EventFunc, value interface{}) { 87 | if eventFunc == nil { 88 | return 89 | } 90 | 91 | // invoke subscriber event func 92 | eventFunc(value) 93 | 94 | } 95 | 96 | //Notify all event subscribers 97 | func (e *MockEvent) NotifyAll() (errs []error) { 98 | e.m.RLock() 99 | defer e.m.RUnlock() 100 | 101 | for eventType, _ := range e.Subscribers { 102 | if err := e.Notify(eventType, nil); err != nil { 103 | errs = append(errs, err) 104 | } 105 | } 106 | 107 | return errs 108 | } 109 | 110 | // unsubscribe all event and subscriber elegant 111 | func (e *MockEvent) UnSubscribeAll() { 112 | e.m.Lock() 113 | defer e.m.Unlock() 114 | for eventtype, _ := range e.Subscribers { 115 | subs, ok := e.Subscribers[eventtype] 116 | if !ok { 117 | continue 118 | } 119 | for subscriber, _ := range subs { 120 | delete(subs, subscriber) 121 | close(subscriber) 122 | } 123 | } 124 | // TODO: open it when txswitch and blkswith stop complete 125 | //e.Subscribers = make(map[types.EventType]map[types.Subscriber]types.EventFunc) 126 | return 127 | } 128 | 129 | // mock a config for txpool 130 | func mock_txpool_config(slot uint64) TxPoolConfig { 131 | mock_config := TxPoolConfig{ 132 | GlobalSlots: slot, 133 | } 134 | return mock_config 135 | } 136 | 137 | // mock a transaction 138 | func mock_transactions(num int) []*types.Transaction { 139 | to := make([]types.Address, num) 140 | for m := 0; m < num; m++ { 141 | for j := 0; j < types.AddressLength; j++ { 142 | to[m][j] = byte(m) 143 | } 144 | } 145 | amount := new(big.Int) 146 | txList := make([]*types.Transaction, 0) 147 | for i := 0; i < num; i++ { 148 | tx := common.NewTransaction(0, to[i], amount, uint64(i), amount, nil, common.HexToAddress(fmt.Sprintf("0x%d", i))) 149 | common.TxHash(tx) 150 | txList = append(txList, tx) 151 | } 152 | return txList 153 | } 154 | 155 | // mock same from transaction 156 | func mock_samefrom_transactions(num int) []*types.Transaction { 157 | to := make([]types.Address, num) 158 | for m := 0; m < num; m++ { 159 | for j := 0; j < types.AddressLength; j++ { 160 | to[m][j] = byte(m) 161 | } 162 | } 163 | amount := new(big.Int) 164 | txList := make([]*types.Transaction, 0) 165 | for i := 0; i < num; i++ { 166 | tx := common.NewTransaction(uint64(i), to[i], amount, uint64(i), amount, nil, common.HexToAddress(fmt.Sprintf("0x%d", num))) 167 | common.TxHash(tx) 168 | txList = append(txList, tx) 169 | } 170 | return txList 171 | } 172 | 173 | // Test new a txpool 174 | func Test_NewTxPool(t *testing.T) { 175 | assert := assert.New(t) 176 | 177 | mock_config := mock_txpool_config(DefaultTxPoolConfig.GlobalSlots - 1) 178 | txpool := NewTxPool(mock_config, NewMockEvent()) 179 | assert.NotNil(txpool) 180 | instance := txpool.(*TxPool) 181 | assert.Equal(DefaultTxPoolConfig.GlobalSlots-1, instance.config.GlobalSlots, "they should be equal") 182 | 183 | mock_config = mock_txpool_config(DefaultTxPoolConfig.GlobalSlots + 1) 184 | txpool = NewTxPool(mock_config, NewMockEvent()) 185 | instance = txpool.(*TxPool) 186 | assert.Equal(DefaultTxPoolConfig.GlobalSlots, instance.config.GlobalSlots, "they should be equal") 187 | } 188 | 189 | // Test add a tx to txpool 190 | func Test_AddTx(t *testing.T) { 191 | defer monkey.UnpatchAll() 192 | chain := &repository.Repository{} 193 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 194 | return chain, nil 195 | }) 196 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 197 | return 0 198 | }) 199 | assert := assert.New(t) 200 | 201 | txList := mock_transactions(3) 202 | assert.NotNil(txList) 203 | 204 | events := NewMockEvent() 205 | events.Subscribe(types.EventAddTxToTxPool, func(v interface{}) { 206 | if nil != v { 207 | log.Info("add tx %v success.", v) 208 | } 209 | }) 210 | 211 | var MockTxPoolConfig = TxPoolConfig{ 212 | GlobalSlots: 2, 213 | MaxTrsPerBlock: 2, 214 | TxMaxCacheTime: 1, 215 | } 216 | 217 | txpool := NewTxPool(MockTxPoolConfig, events) 218 | assert.NotNil(txpool) 219 | 220 | err := txpool.AddTx(txList[0]) 221 | assert.Nil(err) 222 | 223 | instance := txpool.(*TxPool) 224 | 225 | // add duplicate tx to txpool 226 | err = txpool.AddTx(txList[0]) 227 | assert.NotNil(err) 228 | assert.Equal(1, instance.txBuffer.Len(), "they should be equal") 229 | 230 | err = txpool.AddTx(txList[1]) 231 | assert.Nil(err) 232 | assert.Equal(2, instance.txBuffer.Len(), "they should be equal") 233 | 234 | err = txpool.AddTx(txList[2]) 235 | assert.NotNil(err) 236 | 237 | instance.config.TxMaxCacheTime = 1 238 | time.Sleep(2 * time.Second) 239 | err = txpool.AddTx(txList[2]) 240 | assert.Nil(err) 241 | assert.Equal(2, instance.txBuffer.Len()) 242 | } 243 | 244 | // Test Get a tx from txpool 245 | func Test_GetTxs(t *testing.T) { 246 | defer monkey.UnpatchAll() 247 | chain := &repository.Repository{} 248 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 249 | return chain, nil 250 | }) 251 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 252 | return 0 253 | }) 254 | assert := assert.New(t) 255 | tx := mock_transactions(1)[0] 256 | assert.NotNil(tx) 257 | 258 | txpool := NewTxPool(DefaultTxPoolConfig, NewMockEvent()) 259 | assert.NotNil(txpool) 260 | assert.Nil(txpool.AddTx(tx)) 261 | 262 | returnedTx := txpool.GetTxs() 263 | assert.NotNil(returnedTx) 264 | assert.Equal(1, len(returnedTx)) 265 | 266 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 267 | return 1 268 | }) 269 | returnedTx = txpool.GetTxs() 270 | assert.Equal(0, len(returnedTx)) 271 | } 272 | 273 | // Test DelTxs txs from txpool 274 | func Test_DelTxs(t *testing.T) { 275 | defer monkey.UnpatchAll() 276 | chain := &repository.Repository{} 277 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 278 | return chain, nil 279 | }) 280 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 281 | return 0 282 | }) 283 | assert := assert.New(t) 284 | txs := mock_transactions(3) 285 | 286 | txpool := NewTxPool(DefaultTxPoolConfig, NewMockEvent()) 287 | assert.NotNil(txpool) 288 | pool := txpool.(*TxPool) 289 | pool.AddTx(txs[0]) 290 | assert.Equal(1, pool.txBuffer.Len()) 291 | 292 | pool.DelTxs([]*types.Transaction{txs[1]}) 293 | assert.Equal(1, pool.txBuffer.Len()) 294 | 295 | pool.DelTxs([]*types.Transaction{txs[0]}) 296 | assert.Equal(0, pool.txBuffer.Len()) 297 | 298 | pool.AddTx(txs[0]) 299 | pool.AddTx(txs[1]) 300 | pool.AddTx(txs[2]) 301 | assert.Equal(3, pool.txBuffer.Len()) 302 | pool.DelTxs([]*types.Transaction{txs[1], txs[2]}) 303 | assert.Equal(1, pool.txBuffer.Len()) 304 | pool.DelTxs([]*types.Transaction{txs[0]}) 305 | assert.Equal(0, pool.txBuffer.Len()) 306 | } 307 | 308 | func TestGetTxByHash(t *testing.T) { 309 | defer monkey.UnpatchAll() 310 | chain := &repository.Repository{} 311 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 312 | return chain, nil 313 | }) 314 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 315 | return 0 316 | }) 317 | assert := assert.New(t) 318 | tx := mock_transactions(10)[9] 319 | assert.NotNil(tx) 320 | txpool := NewTxPool(DefaultTxPoolConfig, NewMockEvent()) 321 | assert.NotNil(txpool) 322 | pool := txpool.(*TxPool) 323 | 324 | exceptTx := GetTxByHash(common.TxHash(tx)) 325 | assert.Nil(exceptTx) 326 | 327 | // try to get exist tx 328 | err := pool.AddTx(tx) 329 | assert.Nil(err) 330 | exceptTx = GetTxByHash(common.TxHash(tx)) 331 | assert.Equal(common.TxHash(tx), common.TxHash(exceptTx)) 332 | } 333 | 334 | func TestGetPoolNonce(t *testing.T) { 335 | defer monkey.UnpatchAll() 336 | assert := assert.New(t) 337 | chain := &repository.Repository{} 338 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 339 | return chain, nil 340 | }) 341 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 342 | return 0 343 | }) 344 | var txs []*types.Transaction 345 | txpool := NewTxPool(DefaultTxPoolConfig, NewMockEvent()) 346 | 347 | mockFromAddress := types.Address{ 348 | 0xb2, 0x6f, 0x2b, 0x34, 0x2a, 0xab, 0x24, 0xbc, 0xf6, 0x3e, 349 | 0xa2, 0x18, 0xc6, 0xa9, 0x27, 0x4d, 0x30, 0xab, 0x9a, 0x15, 350 | } 351 | for i := 0; i < 5; i++ { 352 | tx := common.NewTransaction(uint64(i), mockFromAddress, new(big.Int).SetUint64(uint64(i)), uint64(i), new(big.Int).SetUint64(uint64(i)), nil, mockFromAddress) 353 | txs = append(txs, tx) 354 | } 355 | 356 | txpool.AddTx(txs[0]) 357 | txpool.AddTx(txs[1]) 358 | exceptNonce := GetPoolNonce(mockFromAddress) 359 | assert.Equal(uint64(1), exceptNonce) 360 | 361 | txpool.AddTx(txs[2]) 362 | exceptNonce = GetPoolNonce(mockFromAddress) 363 | assert.Equal(uint64(2), exceptNonce) 364 | } 365 | 366 | func TestNewTxPool(t *testing.T) { 367 | defer monkey.UnpatchAll() 368 | chain := &repository.Repository{} 369 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 370 | return chain, nil 371 | }) 372 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 373 | return 0 374 | }) 375 | var mockTxPoolConfig = TxPoolConfig{ 376 | GlobalSlots: 4096, 377 | MaxTrsPerBlock: 512, 378 | } 379 | 380 | txpool := NewTxPool(mockTxPoolConfig, NewMockEvent()) 381 | transactions := mock_transactions(4096) 382 | for index := 0; index < len(transactions); index++ { 383 | txpool.AddTx(transactions[index]) 384 | } 385 | txs := txpool.(*TxPool) 386 | lens := txs.txBuffer.Len() 387 | assert.Equal(t, 4096, lens) 388 | 389 | mm := txpool.GetTxs() 390 | assert.Equal(t, 512, len(mm)) 391 | } 392 | 393 | func TestNewTxPool1(t *testing.T) { 394 | defer monkey.UnpatchAll() 395 | chain := &repository.Repository{} 396 | monkey.Patch(repository.NewLatestStateRepository, func() (*repository.Repository, error) { 397 | return chain, nil 398 | }) 399 | monkey.PatchInstanceMethod(reflect.TypeOf(chain), "GetNonce", func(*repository.Repository, types.Address) uint64 { 400 | return 0 401 | }) 402 | var mockTxPoolConfig = TxPoolConfig{ 403 | GlobalSlots: 4096, 404 | MaxTrsPerBlock: 512, 405 | } 406 | 407 | txpool := NewTxPool(mockTxPoolConfig, NewMockEvent()) 408 | transactions := mock_samefrom_transactions(4096) 409 | for index := 0; index < len(transactions); index++ { 410 | txpool.AddTx(transactions[index]) 411 | } 412 | txs := txpool.(*TxPool) 413 | lens := txs.txBuffer.Len() 414 | assert.Equal(t, 4096, lens) 415 | 416 | readyTxs := txpool.GetTxs() 417 | assert.Equal(t, 512, len(readyTxs)) 418 | 419 | readyTxs = txpool.GetTxs() 420 | assert.Equal(t, 512, len(readyTxs)) 421 | readyTxs = txpool.GetTxs() 422 | assert.Equal(t, 512, len(readyTxs)) 423 | readyTxs = txpool.GetTxs() 424 | assert.Equal(t, 512, len(readyTxs)) 425 | readyTxs = txpool.GetTxs() 426 | assert.Equal(t, 512, len(readyTxs)) 427 | readyTxs = txpool.GetTxs() 428 | assert.Equal(t, 512, len(readyTxs)) 429 | } 430 | --------------------------------------------------------------------------------