mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 21:31:37 +00:00
Merge pull request #295 from XinFinOrg/dev-upgrade
consensus version 2 and cicd pipeline
This commit is contained in:
commit
ef5d064553
136 changed files with 13180 additions and 32594 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -51,4 +51,4 @@ profile.cov
|
|||
coverage.txt
|
||||
go.sum
|
||||
cicd/devnet/terraform/.terraform
|
||||
cicd/devnet/tmp
|
||||
cicd/devnet/tmp
|
||||
|
|
|
|||
81
.travis.yml
81
.travis.yml
|
|
@ -1,11 +1,25 @@
|
|||
sudo: required
|
||||
language: go
|
||||
go_import_path: github.com/XinFinOrg/XDPoSChain
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- dev-upgrade
|
||||
|
||||
env:
|
||||
global:
|
||||
- GOPROXY=https://proxy.golang.org
|
||||
- GO111MODULE=on
|
||||
# Terraform env
|
||||
- tf_version=1.3.0
|
||||
# Setting terraform init CLI options - https://www.terraform.io/docs/commands/init.html
|
||||
- tf_init_cli_options=" -input=false"
|
||||
# Set terraform validation CLI options - https://www.terraform.io/docs/commands/validate.html
|
||||
- tf_validation_cli_options=""
|
||||
# Set terraform plan CLI options - https://www.terraform.io/docs/commands/plan.html
|
||||
- tf_plan_cli_options=" -lock=false -input=false"
|
||||
# Set terraform apply CLI options - https://www.terraform.io/docs/commands/apply.html
|
||||
- tf_apply_cli_options=" -auto-approve -input=false"
|
||||
|
||||
|
||||
jobs:
|
||||
|
|
@ -73,4 +87,69 @@ jobs:
|
|||
go: 1.14.x
|
||||
env:
|
||||
- GO111MODULE=auto
|
||||
name: T-Z tests
|
||||
name: T-Z tests
|
||||
|
||||
- stage: (Devnet)Terraform plan
|
||||
if: branch = dev-upgrade AND type = pull_request
|
||||
dist: xenial
|
||||
language: bash
|
||||
install:
|
||||
- wget https://releases.hashicorp.com/terraform/"$tf_version"/terraform_"$tf_version"_linux_amd64.zip
|
||||
- unzip terraform_"$tf_version"_linux_amd64.zip
|
||||
- sudo mv terraform /usr/local/bin/
|
||||
- rm terraform_"$tf_version"_linux_amd64.zip
|
||||
script:
|
||||
- echo "Pull request detected, creating change plan(Devnet)"
|
||||
- cd cicd/devnet/terraform
|
||||
# Terraform init, validate, then create change plan. If any fail, fail validation
|
||||
- terraform init $tf_init_cli_options
|
||||
- terraform validate $tf_validation_cli_options
|
||||
- terraform plan $tf_plan_cli_options
|
||||
|
||||
- stage: (Devnet) Build, and push images
|
||||
if: branch = dev-upgrade AND type = push AND tag IS blank
|
||||
services:
|
||||
- docker
|
||||
install: skip
|
||||
before_script:
|
||||
- echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin
|
||||
- docker --version # document the version travis is using
|
||||
- docker build -t xdc-devnet -f cicd/devnet/Dockerfile .
|
||||
script:
|
||||
- docker tag xdc-devnet:latest xinfinorg/devnet:latest # Always push to the latest
|
||||
- docker push xinfinorg/devnet:latest
|
||||
|
||||
- stage: Terraform apply
|
||||
if: branch = dev-upgrade AND type = push AND tag IS blank
|
||||
dist: xenial
|
||||
language: bash
|
||||
install:
|
||||
# Download and install terraform before each run
|
||||
- wget https://releases.hashicorp.com/terraform/"$tf_version"/terraform_"$tf_version"_linux_amd64.zip
|
||||
- unzip terraform_"$tf_version"_linux_amd64.zip
|
||||
- sudo mv terraform /usr/local/bin/
|
||||
- rm terraform_"$tf_version"_linux_amd64.zip
|
||||
- pip install --user awscli # install aws cli w/o sudo
|
||||
- export PATH=$PATH:$HOME/.local/bin # put aws in the path
|
||||
script:
|
||||
- echo "Merge detected, executing changes(Devnet)"
|
||||
- cd cicd/devnet/terraform
|
||||
# Terraform init and then apply changes to environment
|
||||
- terraform init $tf_init_cli_options
|
||||
- terraform apply $tf_apply_cli_options
|
||||
- sleep 20
|
||||
- |
|
||||
source .env
|
||||
for ((i=$us_east_2_start;i<$us_east_2_end;i++)); do
|
||||
echo "Force deploy xdc-$i"
|
||||
sleep 5 && aws ecs update-service --region us-east-2 --cluster devnet-xdcnode-cluster --service ecs-service-xdc$i --force-new-deployment;
|
||||
done
|
||||
for ((i=$eu_west_1_start;i<$eu_west_1_end;i++)); do
|
||||
echo "Force deploy xdc-$i"
|
||||
sleep 5 && aws ecs update-service --region eu-west-1 --cluster devnet-xdcnode-cluster --service ecs-service-xdc$i --force-new-deployment;
|
||||
done
|
||||
for ((i=$ap_southeast_2_start;i<$ap_southeast_2_end;i++)); do
|
||||
echo "Force deploy xdc-$i"
|
||||
sleep 5 && aws ecs update-service --region ap-southeast-2 --cluster devnet-xdcnode-cluster --service ecs-service-xdc$i --force-new-deployment;
|
||||
done
|
||||
|
||||
|
|
|
|||
11
Makefile
11
Makefile
|
|
@ -15,6 +15,17 @@ XDC:
|
|||
@echo "Done building."
|
||||
@echo "Run \"$(GOBIN)/XDC\" to launch XDC."
|
||||
|
||||
XDC-devnet-local:
|
||||
@echo "Rebuild the XDC first"
|
||||
mv common/constants.go common/constants.go.tmp
|
||||
cp common/constants/constants.go.devnet common/constants.go
|
||||
make XDC
|
||||
rm -rf common/constants.go
|
||||
mv common/constants.go.tmp common/constants.go
|
||||
|
||||
@echo "Run the devnet script in local"
|
||||
cd cicd/devnet && ./start-local-devnet.sh
|
||||
|
||||
gc:
|
||||
go run build/ci.go install ./cmd/gc
|
||||
@echo "Done building."
|
||||
|
|
|
|||
|
|
@ -54,6 +54,10 @@ If at a later stage if some predecided amount of owners ( investors ) vote that
|
|||
|
||||
### For developers
|
||||
|
||||
#### Continues integration & delivery
|
||||
See https://github.com/XinFinOrg/XDPoSChain/tree/dev-upgrade/cicd
|
||||
|
||||
|
||||
### To contribute
|
||||
|
||||
Simple create a pull request along with proper reasoning, we'll get back to you.
|
||||
|
|
|
|||
|
|
@ -20,7 +20,9 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
|
@ -29,7 +31,9 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
|
|
@ -69,6 +73,28 @@ type SimulatedBackend struct {
|
|||
config *params.ChainConfig
|
||||
}
|
||||
|
||||
func SimulateWalletAddressAndSignFn() (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", "eth-SimulateWalletAddressAndSignFn-test")
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.NewAccount(pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
// XDC simulated backend for testing purpose.
|
||||
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
|
||||
// database := ethdb.NewMemDatabase()
|
||||
|
|
|
|||
52
cicd/README.md
Normal file
52
cicd/README.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# CI/CD pipeline for XDC
|
||||
This directory contains CI/CD scripts used for each of the XDC environments.
|
||||
|
||||
## How to deploy more nodes
|
||||
Adjust the number of variable `num_of_nodes` under file `.env`. (**Maximum supported is 58**)
|
||||
|
||||
## Devnet
|
||||
Each PR merged into `dev-upgrade` will trigger below actions:
|
||||
- Tests
|
||||
- Terraform to apply infrascture changes(if any)
|
||||
- Docker build of XDC with devnet configurations with tag of `:latest`
|
||||
- Docker push to docker hub. https://hub.docker.com/repository/docker/xinfinorg/devnet
|
||||
- Deployment of the latest XDC image(from above) to devnet run by AWS ECS
|
||||
|
||||
### First time set up an new environment
|
||||
1. Pre-generate a list of node private keys in below format
|
||||
```
|
||||
{
|
||||
"xdc0": {
|
||||
"pk": {{PRIVATE KEY}},
|
||||
"address": {{XDC wallet address}},
|
||||
"imageTag": {{Optional field to run different version of XDC}},
|
||||
"logLevel": {{Optional field to adjust the log level for the container}}
|
||||
},
|
||||
"xdc1": {...},
|
||||
"xdc{{NUMBER}}: {...}
|
||||
}
|
||||
```
|
||||
2. Access to aws console, create a bucket with name `tf-devnet-bucket`:
|
||||
- You can choose any name, just make sure update the name in the s3 bucket name variable in `variables.tf`
|
||||
- And update the name of the terraform.backend.s3.bucket from `s3.tf`
|
||||
3. Upload the file from step 1 into the above bucket with name `node-config.json`
|
||||
4. In order to allow pipeline able to push and deploy via ECR and ECS, we require below environment variables to be injected into the CI pipeline:
|
||||
1. DOCKER_USERNAME
|
||||
2. DOCKER_PASSWORD
|
||||
3. AWS_ACCESS_KEY_ID
|
||||
4. AWS_SECRET_ACCESS_KEY
|
||||
|
||||
You are all set!
|
||||
|
||||
## How to run different version of XDC on selected nodes
|
||||
1. Create a new image tag:
|
||||
- Check out the repo
|
||||
- Run docker build `docker build -t xdc-devnet -f cicd/devnet/Dockerfile .`
|
||||
- Run docker tag `docker tag xdc-devnet:latest xinfinorg/devnet:test-{{put your version number here}}`
|
||||
- Run docker push `docker push xinfinorg/devnet:test-{{Version number from step above}}`
|
||||
2. Adjust node-config.json
|
||||
- Download the node-config.json from s3
|
||||
- Add/update the `imageTag` field with value of `test-{{version number you defined in step 1}}` for the selected number of nodes you want to test with
|
||||
- Optional: Adjust the log level by add/updating the field of `logLevel`
|
||||
- Save and upload to s3
|
||||
3. Make a dummy PR and get merged. Wait it to be updated.
|
||||
36
cicd/devnet/Dockerfile
Normal file
36
cicd/devnet/Dockerfile
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
FROM golang:1.14 as builder
|
||||
|
||||
RUN apt-get update && apt-get install -y git build-essential
|
||||
|
||||
COPY . /builder
|
||||
RUN mv /builder/common/constants/constants.go.devnet /builder/common/constants.go
|
||||
|
||||
RUN cd /builder && make
|
||||
|
||||
|
||||
# The actual image for devnet containers
|
||||
FROM golang:1.14
|
||||
|
||||
RUN apt-get update && apt-get install -y git build-essential
|
||||
|
||||
WORKDIR /work
|
||||
|
||||
COPY --from=builder /builder/build/bin/XDC /usr/bin
|
||||
RUN chmod +x /usr/bin/XDC
|
||||
|
||||
# Copy over files
|
||||
ADD cicd/devnet/genesis.json /work/genesis.json
|
||||
ADD cicd/devnet/bootnodes.list /work/bootnodes.list
|
||||
ADD cicd/devnet/start.sh /work/start.sh
|
||||
|
||||
# Create an empty pwd file
|
||||
RUN touch /work/.pwd
|
||||
|
||||
# rpc
|
||||
EXPOSE 8545
|
||||
# ws
|
||||
EXPOSE 8555
|
||||
# port
|
||||
EXPOSE 30303
|
||||
|
||||
ENTRYPOINT ["bash","/work/start.sh"]
|
||||
5
cicd/devnet/bootnodes.list
Normal file
5
cicd/devnet/bootnodes.list
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
enode://207f812067141e038439acbd18a6e3b5f38fcff7de362d8fe0eb5f153f828ae99cae3270ed653beaa197e3946223d9e2f4a22a5e948177209d046fd095b89626@66.94.121.151:30301
|
||||
enode://ec569f5d52cefee5c5405a0c5db720dc7061f3085e0682dd8321413430ddda6a177b85db75b0daf83d2e68760ba3f5beb4ba9e333e7d52072fba4d39b05a0451@194.233.77.19:30301
|
||||
enode://cab457c58bc9b94ea9afa428d018179b4773fe90fec16ef958951f5a23adb9627350cea8b56709351766183d85574fc54ba504816b200fa31675a04c25226e9f@66.94.98.186:30301
|
||||
enode://751de9e39fe0b8f18585bb4a8b50aa5631fe262e14f579753709631eddcac30be901ca0b5a3878b7686838da7ddca2c61e6d2c5eb3745e6b855289e692068e8d@144.126.140.3:30301
|
||||
enode://6bd073ee1085c1dc09427d9f65f0125a75393ac89f0db36f884cc22d61f441403a4c06d8f9a9d3c8e8c08368122bceeeac5f48c83f3d2e87c0da6d5c0eb7cd7e@194.163.167.177:30301
|
||||
129
cicd/devnet/genesis.json
Normal file
129
cicd/devnet/genesis.json
Normal file
File diff suppressed because one or more lines are too long
62
cicd/devnet/start-local-devnet.sh
Executable file
62
cicd/devnet/start-local-devnet.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [ ! -d ./tmp/xdcchain ]
|
||||
then
|
||||
echo "Creating a temporary directory for storing the xdcchain"
|
||||
mkdir tmp
|
||||
mkdir -p ./tmp/xdcchain
|
||||
touch ./tmp/.pwd
|
||||
|
||||
# Randomly select a key from environment variable, seperated by ','
|
||||
if test -z "$PRIVATE_KEYS"
|
||||
then
|
||||
echo "PRIVATE_KEYS environment variable has not been set. Please run again with `export PRIVATE_KEYS={{your key}} && make XDC-devnet-local`"
|
||||
exit 1
|
||||
fi
|
||||
IFS=', ' read -r -a private_keys <<< "$PRIVATE_KEYS"
|
||||
private_key=${private_keys[ $RANDOM % ${#private_keys[@]} ]}
|
||||
|
||||
echo "${private_key}" >> ./tmp/key
|
||||
echo "Creating a new wallet"
|
||||
wallet=$(../../build/bin/XDC account import --password ./tmp/.pwd --datadir ./tmp/xdcchain ./tmp/key | awk -v FS="({|})" '{print $2}')
|
||||
../../build/bin/XDC --datadir ./tmp/xdcchain init ./genesis.json
|
||||
else
|
||||
echo "Wallet already exist, re-use the same one. If you have changed the private key, please manually inspect the key if matches. Otherwise, delete the 'tmp' directory and start again!"
|
||||
wallet=$(../../build/bin/XDC account list --datadir ./tmp/xdcchain | head -n 1 | awk -v FS="({|})" '{print $2}')
|
||||
fi
|
||||
|
||||
input="./bootnodes.list"
|
||||
bootnodes=""
|
||||
while IFS= read -r line
|
||||
do
|
||||
if [ -z "${bootnodes}" ]
|
||||
then
|
||||
bootnodes=$line
|
||||
else
|
||||
bootnodes="${bootnodes},$line"
|
||||
fi
|
||||
done < "$input"
|
||||
|
||||
log_level=3
|
||||
if test -z "$LOG_LEVEL"
|
||||
then
|
||||
echo "Log level not set, default to verbosity of 3"
|
||||
else
|
||||
echo "Log level found, set to $LOG_LEVEL"
|
||||
log_level=$LOG_LEVEL
|
||||
fi
|
||||
|
||||
netstats="${NODE_NAME}-${wallet}-local:xinfin_xdpos_hybrid_network_stats@devnetstats.apothem.network:2000"
|
||||
|
||||
echo "Running a node with wallet: ${wallet} at local"
|
||||
|
||||
../../build/bin/XDC --ethstats ${netstats} --gcmode=archive \
|
||||
--bootnodes ${bootnodes} --syncmode full \
|
||||
--datadir ./tmp/xdcchain --networkid 551 \
|
||||
-port 30303 --rpc --rpccorsdomain "*" --rpcaddr 0.0.0.0 \
|
||||
--rpcport 8545 \
|
||||
--rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,XDPoS \
|
||||
--rpcvhosts "*" --unlock "${wallet}" --password ./tmp/.pwd --mine \
|
||||
--gasprice "1" --targetgaslimit "420000000" --verbosity ${log_level} \
|
||||
--ws --wsaddr=0.0.0.0 --wsport 8555 \
|
||||
--wsorigins "*" 2>&1 >>./tmp/xdc.log
|
||||
67
cicd/devnet/start.sh
Executable file
67
cicd/devnet/start.sh
Executable file
|
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo "Preparing to start the XDC chain, it's likely to take up to 1 minute"
|
||||
# Sleep for > 30 as we need to wait for the ECS tasks container being killed by fargate. Otherwise it will ended up with two same nodes running on a single /work/xdcchain directory
|
||||
sleep 60
|
||||
|
||||
if [ ! -d /work/xdcchain/XDC/chaindata ]
|
||||
then
|
||||
# Randomly select a key from environment variable, seperated by ','
|
||||
if test -z "$PRIVATE_KEYS"
|
||||
then
|
||||
echo "PRIVATE_KEYS environment variable has not been set. You need to pass at least one PK, or you can pass multiple PK seperated by ',', we will randomly choose one for you"
|
||||
exit 1
|
||||
fi
|
||||
IFS=', ' read -r -a private_keys <<< "$PRIVATE_KEYS"
|
||||
private_key=${private_keys[ $RANDOM % ${#private_keys[@]} ]}
|
||||
|
||||
echo "${private_key}" >> /tmp/key
|
||||
echo "Creating a new wallet"
|
||||
wallet=$(XDC account import --password .pwd --datadir /work/xdcchain /tmp/key | awk -v FS="({|})" '{print $2}')
|
||||
XDC --datadir /work/xdcchain init /work/genesis.json
|
||||
else
|
||||
echo "Wallet already exist, re-use the same one"
|
||||
wallet=$(XDC account list --datadir /work/xdcchain | head -n 1 | awk -v FS="({|})" '{print $2}')
|
||||
fi
|
||||
|
||||
input="/work/bootnodes.list"
|
||||
bootnodes=""
|
||||
while IFS= read -r line
|
||||
do
|
||||
if [ -z "${bootnodes}" ]
|
||||
then
|
||||
bootnodes=$line
|
||||
else
|
||||
bootnodes="${bootnodes},$line"
|
||||
fi
|
||||
done < "$input"
|
||||
|
||||
log_level=3
|
||||
if test -z "$LOG_LEVEL"
|
||||
then
|
||||
echo "Log level not set, default to verbosity of 3"
|
||||
else
|
||||
echo "Log level found, set to $LOG_LEVEL"
|
||||
log_level=$LOG_LEVEL
|
||||
fi
|
||||
|
||||
INSTANCE_IP=$(curl https://checkip.amazonaws.com)
|
||||
netstats="${NODE_NAME}-${wallet}-${INSTANCE_IP}:xinfin_xdpos_hybrid_network_stats@devnetstats.apothem.network:2000"
|
||||
|
||||
|
||||
echo "Running a node with wallet: ${wallet} at IP: ${INSTANCE_IP}"
|
||||
echo "Starting nodes with $bootnodes ..."
|
||||
|
||||
# Note: --gcmode=archive means node will store all historical data. This will lead to high memory usage. Only needed if you need the node to perform historical operations
|
||||
XDC --ethstats ${netstats} --gcmode=full \
|
||||
--nat extip:${INSTANCE_IP} \
|
||||
--bootnodes ${bootnodes} --syncmode fast \
|
||||
--datadir /work/xdcchain --networkid 551 \
|
||||
-port 30303 --rpc --rpccorsdomain "*" --rpcaddr 0.0.0.0 \
|
||||
--rpcport 8545 \
|
||||
--rpcapi admin,db,eth,debug,miner,net,shh,txpool,personal,web3,XDPoS \
|
||||
--rpcvhosts "*" --unlock "${wallet}" --password /work/.pwd --mine \
|
||||
--gasprice "1" --targetgaslimit "420000000" --verbosity ${log_level} \
|
||||
--periodicprofile --debugdatadir /work/xdcchain \
|
||||
--ws --wsaddr=0.0.0.0 --wsport 8555 \
|
||||
--wsorigins "*" 2>&1 >>/work/xdcchain/xdc.log | tee --append /work/xdcchain/xdc.log
|
||||
13
cicd/devnet/terraform/.env
Normal file
13
cicd/devnet/terraform/.env
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
log_level=3
|
||||
|
||||
# Ohio
|
||||
us_east_2_start=0
|
||||
us_east_2_end=36
|
||||
|
||||
# Ireland
|
||||
eu_west_1_start=37
|
||||
eu_west_1_end=72
|
||||
|
||||
# Sydney
|
||||
ap_southeast_2_start=73
|
||||
ap_southeast_2_end=110
|
||||
43
cicd/devnet/terraform/container-definition.tpl
Normal file
43
cicd/devnet/terraform/container-definition.tpl
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[
|
||||
{
|
||||
"name": "tfXdcNode",
|
||||
"image": "xinfinorg/${xdc_environment}:${image_tag}",
|
||||
"environment": [
|
||||
{"name": "PRIVATE_KEYS", "value": "${private_keys}"},
|
||||
{"name": "LOG_LEVEL", "value": "${log_level}"},
|
||||
{"name": "NODE_NAME", "value": "${node_name}"}
|
||||
],
|
||||
"essential": true,
|
||||
"logConfiguration": {
|
||||
"logDriver": "awslogs",
|
||||
"options": {
|
||||
"awslogs-group": "${cloudwatch_group}",
|
||||
"awslogs-region": "us-east-1",
|
||||
"awslogs-stream-prefix": "ecs"
|
||||
}
|
||||
},
|
||||
"portMappings": [
|
||||
{
|
||||
"hostPort": 8555,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 8555
|
||||
},
|
||||
{
|
||||
"hostPort": 8545,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 8545
|
||||
},
|
||||
{
|
||||
"hostPort": 30303,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 30303
|
||||
}
|
||||
],
|
||||
"mountPoints": [
|
||||
{
|
||||
"containerPath": "/work/xdcchain",
|
||||
"sourceVolume": "efs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
28
cicd/devnet/terraform/iam.tf
Normal file
28
cicd/devnet/terraform/iam.tf
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# IAM policies
|
||||
data "aws_iam_policy_document" "xdc_ecs_tasks_execution_role" {
|
||||
statement {
|
||||
actions = ["sts:AssumeRole"]
|
||||
|
||||
principals {
|
||||
type = "Service"
|
||||
identifiers = ["ecs-tasks.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create the role
|
||||
resource "aws_iam_role" "devnet_xdc_ecs_tasks_execution_role" {
|
||||
name = "devnet-xdc-ecs-task-execution-role"
|
||||
assume_role_policy = "${data.aws_iam_policy_document.xdc_ecs_tasks_execution_role.json}"
|
||||
}
|
||||
|
||||
# Attached the AWS managed policies to the new role
|
||||
resource "aws_iam_role_policy_attachment" "devnet_xdc_ecs_tasks_execution_role" {
|
||||
for_each = toset([
|
||||
"arn:aws:iam::aws:policy/AmazonElasticFileSystemClientFullAccess",
|
||||
"arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
|
||||
"arn:aws:iam::aws:policy/AmazonElasticFileSystemsUtils"
|
||||
])
|
||||
role = aws_iam_role.devnet_xdc_ecs_tasks_execution_role.name
|
||||
policy_arn = each.value
|
||||
}
|
||||
66
cicd/devnet/terraform/main.tf
Normal file
66
cicd/devnet/terraform/main.tf
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 4.16"
|
||||
}
|
||||
}
|
||||
|
||||
required_version = ">= 1.2.0"
|
||||
}
|
||||
|
||||
# Default
|
||||
provider "aws" {
|
||||
region = "us-east-1"
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
alias = "us-east-2"
|
||||
region = "us-east-2"
|
||||
}
|
||||
|
||||
module "us-east-2" {
|
||||
source = "./module/region"
|
||||
region = "us-east-2"
|
||||
devnetNodeKeys = local.devnetNodeKeys["us-east-2"]
|
||||
logLevel = local.logLevel
|
||||
devnet_xdc_ecs_tasks_execution_role_arn = aws_iam_role.devnet_xdc_ecs_tasks_execution_role.arn
|
||||
|
||||
providers = {
|
||||
aws = aws.us-east-2
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
alias = "eu-west-1"
|
||||
region = "eu-west-1"
|
||||
}
|
||||
|
||||
module "eu-west-1" {
|
||||
source = "./module/region"
|
||||
region = "eu-west-1"
|
||||
devnetNodeKeys = local.devnetNodeKeys["eu-west-1"]
|
||||
logLevel = local.logLevel
|
||||
devnet_xdc_ecs_tasks_execution_role_arn = aws_iam_role.devnet_xdc_ecs_tasks_execution_role.arn
|
||||
|
||||
providers = {
|
||||
aws = aws.eu-west-1
|
||||
}
|
||||
}
|
||||
|
||||
provider "aws" {
|
||||
alias = "ap-southeast-2"
|
||||
region = "ap-southeast-2"
|
||||
}
|
||||
|
||||
module "ap-southeast-2" {
|
||||
source = "./module/region"
|
||||
region = "ap-southeast-2"
|
||||
devnetNodeKeys = local.devnetNodeKeys["ap-southeast-2"]
|
||||
logLevel = local.logLevel
|
||||
devnet_xdc_ecs_tasks_execution_role_arn = aws_iam_role.devnet_xdc_ecs_tasks_execution_role.arn
|
||||
|
||||
providers = {
|
||||
aws = aws.ap-southeast-2
|
||||
}
|
||||
}
|
||||
43
cicd/devnet/terraform/module/region/container-definition.tpl
Normal file
43
cicd/devnet/terraform/module/region/container-definition.tpl
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[
|
||||
{
|
||||
"name": "tfXdcNode",
|
||||
"image": "xinfinorg/${xdc_environment}:${image_tag}",
|
||||
"environment": [
|
||||
{"name": "PRIVATE_KEYS", "value": "${private_keys}"},
|
||||
{"name": "LOG_LEVEL", "value": "${log_level}"},
|
||||
{"name": "NODE_NAME", "value": "${node_name}"}
|
||||
],
|
||||
"essential": true,
|
||||
"logConfiguration": {
|
||||
"logDriver": "awslogs",
|
||||
"options": {
|
||||
"awslogs-group": "${cloudwatch_group}",
|
||||
"awslogs-region": "${cloudwatch_region}",
|
||||
"awslogs-stream-prefix": "ecs"
|
||||
}
|
||||
},
|
||||
"portMappings": [
|
||||
{
|
||||
"hostPort": 8555,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 8555
|
||||
},
|
||||
{
|
||||
"hostPort": 8545,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 8545
|
||||
},
|
||||
{
|
||||
"hostPort": 30303,
|
||||
"protocol": "tcp",
|
||||
"containerPort": 30303
|
||||
}
|
||||
],
|
||||
"mountPoints": [
|
||||
{
|
||||
"containerPath": "/work/xdcchain",
|
||||
"sourceVolume": "efs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
93
cicd/devnet/terraform/module/region/ecs.tf
Normal file
93
cicd/devnet/terraform/module/region/ecs.tf
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
data template_file devnet_container_definition {
|
||||
for_each = var.devnetNodeKeys
|
||||
template = "${file("${path.module}/container-definition.tpl")}"
|
||||
|
||||
vars = {
|
||||
xdc_environment = "devnet"
|
||||
image_tag = "${lookup(each.value, "imageTag", "latest")}"
|
||||
node_name = "${each.key}"
|
||||
private_keys = "${each.value.pk}"
|
||||
cloudwatch_group = "tf-${each.key}"
|
||||
cloudwatch_region = "${var.region}"
|
||||
log_level = "${lookup(each.value, "logLevel", "${var.logLevel}")}"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_task_definition" "devnet_task_definition_group" {
|
||||
for_each = var.devnetNodeKeys
|
||||
|
||||
family = "devnet-${each.key}"
|
||||
requires_compatibilities = ["FARGATE"]
|
||||
network_mode = "awsvpc"
|
||||
container_definitions = data.template_file.devnet_container_definition[each.key].rendered
|
||||
execution_role_arn = var.devnet_xdc_ecs_tasks_execution_role_arn
|
||||
task_role_arn = var.devnet_xdc_ecs_tasks_execution_role_arn
|
||||
|
||||
# New nodes will consume a lot more CPU usage than existing nodes.
|
||||
# This is due to sync is resource heavy. Recommending set to below if doing sync:
|
||||
# CPU = 2048, Memory = 4096
|
||||
# Please set it back to cpu 256 and memory of 2048 after sync is done to save the cost
|
||||
# cpu = 256
|
||||
# memory = 2048
|
||||
cpu = 512
|
||||
memory = 3072
|
||||
volume {
|
||||
name = "efs"
|
||||
|
||||
efs_volume_configuration {
|
||||
file_system_id = aws_efs_file_system.devnet_efs[each.key].id
|
||||
root_directory = "/"
|
||||
transit_encryption = "ENABLED"
|
||||
authorization_config {
|
||||
access_point_id = aws_efs_access_point.devnet_efs_access_point[each.key].id
|
||||
iam = "DISABLED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetEcs-${each.key}"
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_ecs_task_definition" "devnet_ecs_task_definition" {
|
||||
for_each = var.devnetNodeKeys
|
||||
task_definition = aws_ecs_task_definition.devnet_task_definition_group[each.key].family
|
||||
}
|
||||
|
||||
resource "aws_ecs_cluster" "devnet_ecs_cluster" {
|
||||
name = "devnet-xdcnode-cluster"
|
||||
tags = {
|
||||
Name = "TfDevnetEcsCluster"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_ecs_service" "devnet_ecs_service" {
|
||||
for_each = var.devnetNodeKeys
|
||||
name = "ecs-service-${each.key}"
|
||||
cluster = aws_ecs_cluster.devnet_ecs_cluster.id
|
||||
task_definition = "${aws_ecs_task_definition.devnet_task_definition_group[each.key].family}:${max(aws_ecs_task_definition.devnet_task_definition_group[each.key].revision, data.aws_ecs_task_definition.devnet_ecs_task_definition[each.key].revision)}"
|
||||
launch_type = "FARGATE"
|
||||
scheduling_strategy = "REPLICA"
|
||||
desired_count = 1
|
||||
force_new_deployment = true
|
||||
deployment_minimum_healthy_percent = 0
|
||||
deployment_maximum_percent = 100
|
||||
|
||||
network_configuration {
|
||||
subnets = [aws_subnet.devnet_subnet.id]
|
||||
assign_public_ip = true
|
||||
security_groups = [
|
||||
aws_default_security_group.devnet_xdcnode_security_group.id
|
||||
]
|
||||
}
|
||||
|
||||
deployment_circuit_breaker {
|
||||
enable = true
|
||||
rollback = false
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetEcsService-${each.key}"
|
||||
}
|
||||
}
|
||||
67
cicd/devnet/terraform/module/region/efs.tf
Normal file
67
cicd/devnet/terraform/module/region/efs.tf
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
|
||||
# EFS
|
||||
resource "aws_security_group" "devnet_efs_security_group" {
|
||||
name = "TfDevnetEfsSecurityGroup"
|
||||
description = "Allow HTTP in and out of devnet EFS"
|
||||
vpc_id = aws_vpc.devnet_vpc.id
|
||||
|
||||
ingress {
|
||||
from_port = 2049
|
||||
to_port = 2049
|
||||
protocol = "TCP"
|
||||
security_groups = [aws_default_security_group.devnet_xdcnode_security_group.id]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
tags = {
|
||||
Name = "TfDevnetEfs"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_efs_file_system" "devnet_efs" {
|
||||
for_each = var.devnetNodeKeys
|
||||
creation_token = "efs-${each.key}"
|
||||
performance_mode = "generalPurpose"
|
||||
throughput_mode = "bursting"
|
||||
encrypted = "true"
|
||||
lifecycle_policy {
|
||||
transition_to_ia = "AFTER_30_DAYS"
|
||||
}
|
||||
tags = {
|
||||
Name = "TfDevnetEfs${each.key}"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_efs_mount_target" "devnet_efs_efs_mount_target" {
|
||||
for_each = var.devnetNodeKeys
|
||||
file_system_id = aws_efs_file_system.devnet_efs[each.key].id
|
||||
subnet_id = aws_subnet.devnet_subnet.id
|
||||
security_groups = [aws_security_group.devnet_efs_security_group.id]
|
||||
}
|
||||
|
||||
resource "aws_efs_access_point" "devnet_efs_access_point" {
|
||||
for_each = var.devnetNodeKeys
|
||||
file_system_id = aws_efs_file_system.devnet_efs[each.key].id
|
||||
root_directory {
|
||||
path = "/${each.key}/database"
|
||||
creation_info {
|
||||
owner_gid = 1001
|
||||
owner_uid = 1001
|
||||
permissions = 777
|
||||
}
|
||||
}
|
||||
posix_user {
|
||||
gid = 1001
|
||||
uid = 1001
|
||||
secondary_gids = [0]
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetEfsAccessPoint${each.key}"
|
||||
}
|
||||
}
|
||||
97
cicd/devnet/terraform/module/region/main.tf
Normal file
97
cicd/devnet/terraform/module/region/main.tf
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
aws = {
|
||||
source = "hashicorp/aws"
|
||||
version = "~> 4.16"
|
||||
}
|
||||
}
|
||||
|
||||
required_version = ">= 1.2.0"
|
||||
}
|
||||
|
||||
resource "aws_vpc" "devnet_vpc" {
|
||||
cidr_block = "10.0.0.0/16"
|
||||
instance_tenancy = "default"
|
||||
enable_dns_hostnames = true
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetVpc"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_subnet" "devnet_subnet" {
|
||||
vpc_id = aws_vpc.devnet_vpc.id
|
||||
cidr_block = "10.0.0.0/20"
|
||||
map_public_ip_on_launch = true
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetVpcSubnet"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_internet_gateway" "devnet_gatewat" {
|
||||
vpc_id = aws_vpc.devnet_vpc.id
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetGateway"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route_table" "devnet_route_table" {
|
||||
vpc_id = aws_vpc.devnet_vpc.id
|
||||
|
||||
route {
|
||||
cidr_block = "0.0.0.0/0"
|
||||
gateway_id = aws_internet_gateway.devnet_gatewat.id
|
||||
}
|
||||
|
||||
tags = {
|
||||
Name = "TfDevnetVpcRoutingTable"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_route_table_association" "devnet_route_table_association" {
|
||||
subnet_id = aws_subnet.devnet_subnet.id
|
||||
route_table_id = aws_route_table.devnet_route_table.id
|
||||
}
|
||||
|
||||
resource "aws_default_security_group" "devnet_xdcnode_security_group" {
|
||||
vpc_id = aws_vpc.devnet_vpc.id
|
||||
|
||||
ingress {
|
||||
description = "listener port"
|
||||
from_port = 30303
|
||||
to_port = 30303
|
||||
protocol = "tcp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
ingress {
|
||||
description = "discovery port"
|
||||
from_port = 30303
|
||||
to_port = 30303
|
||||
protocol = "udp"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
|
||||
egress {
|
||||
from_port = 0
|
||||
to_port = 0
|
||||
protocol = "-1"
|
||||
cidr_blocks = ["0.0.0.0/0"]
|
||||
}
|
||||
tags = {
|
||||
Name = "TfDevnetNode"
|
||||
}
|
||||
}
|
||||
|
||||
# Logs
|
||||
resource "aws_cloudwatch_log_group" "devnet_cloud_watch_group" {
|
||||
for_each = var.devnetNodeKeys
|
||||
|
||||
name = "tf-${each.key}"
|
||||
retention_in_days = 14 # Logs are only kept for 14 days
|
||||
tags = {
|
||||
Name = "TfDevnetCloudWatchGroup${each.key}"
|
||||
}
|
||||
}
|
||||
19
cicd/devnet/terraform/module/region/variables.tf
Normal file
19
cicd/devnet/terraform/module/region/variables.tf
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
variable "region" {
|
||||
description = "AWS region"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "devnetNodeKeys" {
|
||||
description = "each miner's key"
|
||||
type = map
|
||||
}
|
||||
|
||||
variable "logLevel" {
|
||||
description = "containers log level"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "devnet_xdc_ecs_tasks_execution_role_arn" {
|
||||
description = "aws iam role resource arn"
|
||||
type = string
|
||||
}
|
||||
14
cicd/devnet/terraform/s3.tf
Normal file
14
cicd/devnet/terraform/s3.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Bucket need to be created first. If first time run terraform init, need to comment out the below section
|
||||
terraform {
|
||||
backend "s3" {
|
||||
bucket = "tf-devnet-bucket" // This name need to be updated to be the same as local.s3BucketName. We can't use variable here.
|
||||
key = "tf/terraform_new.tfstate"
|
||||
region = "us-east-1"
|
||||
encrypt = true
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_s3_object" "devnet_xdc_node_config" {
|
||||
bucket = local.s3BucketName
|
||||
key = "node-config.json"
|
||||
}
|
||||
44
cicd/devnet/terraform/variables.tf
Normal file
44
cicd/devnet/terraform/variables.tf
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
locals {
|
||||
/**
|
||||
Load the nodes data from s3
|
||||
Below is the the format the config needs to follow:
|
||||
{{Name of the node, in a pattern of 'xdc'+ number. i.e xdc50}}: {
|
||||
pk: {{Value of the node private key}},
|
||||
... any other configuration we want to pass.
|
||||
}
|
||||
Note: No `n` is allowed in the node name
|
||||
**/
|
||||
predefinedNodesConfig = jsondecode(data.aws_s3_object.devnet_xdc_node_config.body)
|
||||
envs = { for tuple in regexall("(.*)=(.*)", file(".env")) : tuple[0] => tuple[1] }
|
||||
logLevel = local.envs["log_level"]
|
||||
|
||||
regions = [
|
||||
{
|
||||
"name": "us-east-2", // Ohio
|
||||
"start": local.envs["us_east_2_start"],
|
||||
"end": local.envs["us_east_2_end"],
|
||||
},
|
||||
{
|
||||
"name": "eu-west-1", // Ireland
|
||||
"start": local.envs["eu_west_1_start"],
|
||||
"end": local.envs["eu_west_1_end"],
|
||||
},
|
||||
{
|
||||
"name": "ap-southeast-2", // Sydney
|
||||
"start": local.envs["ap_southeast_2_start"],
|
||||
"end": local.envs["ap_southeast_2_end"],
|
||||
}
|
||||
]
|
||||
|
||||
keyNames = {
|
||||
for r in local.regions :
|
||||
r.name => [for i in range(r.start, r.end+1) : "xdc${i}"]
|
||||
}
|
||||
|
||||
devnetNodeKeys = {
|
||||
for r in local.regions :
|
||||
r.name => { for i in local.keyNames[r.name]: i => local.predefinedNodesConfig[i] }
|
||||
}
|
||||
|
||||
s3BucketName = "tf-devnet-bucket"
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/eth/downloader"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||
"gopkg.in/urfave/cli.v1"
|
||||
)
|
||||
|
||||
|
|
@ -220,6 +221,12 @@ func importChain(ctx *cli.Context) error {
|
|||
if len(ctx.Args()) < 1 {
|
||||
utils.Fatalf("This command requires an argument.")
|
||||
}
|
||||
|
||||
// Start metrics export if enabled
|
||||
utils.SetupMetrics(ctx)
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
stack, _ := makeFullNode(ctx)
|
||||
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||
defer chainDb.Close()
|
||||
|
|
@ -381,7 +388,7 @@ func copyDb(ctx *cli.Context) error {
|
|||
chain, chainDb := utils.MakeChain(ctx, stack)
|
||||
|
||||
syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode)
|
||||
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil)
|
||||
dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil, nil)
|
||||
|
||||
// Create a source peer to satisfy downloader requests from
|
||||
db, err := rawdb.NewLevelDBDatabase(ctx.Args().First(), ctx.GlobalInt(utils.CacheFlag.Name), 256, "")
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func TestConsoleWelcome(t *testing.T) {
|
|||
XDC.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
|
||||
XDC.SetTemplateFunc("gover", runtime.Version)
|
||||
XDC.SetTemplateFunc("XDCver", func() string { return params.Version })
|
||||
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
|
||||
XDC.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
|
||||
XDC.SetTemplateFunc("apis", func() string { return ipcAPIs })
|
||||
|
||||
// Verify the actual welcome message to the required template
|
||||
|
|
@ -137,7 +137,7 @@ func testAttachWelcome(t *testing.T, XDC *testXDC, endpoint, apis string) {
|
|||
attach.SetTemplateFunc("gover", runtime.Version)
|
||||
attach.SetTemplateFunc("XDCver", func() string { return params.Version })
|
||||
attach.SetTemplateFunc("etherbase", func() string { return XDC.Etherbase })
|
||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1544771829, 0).Format(time.RFC1123) })
|
||||
attach.SetTemplateFunc("niltime", func() string { return time.Unix(1559211559, 0).Format(time.RFC1123) })
|
||||
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
|
||||
attach.SetTemplateFunc("datadir", func() string { return XDC.Datadir })
|
||||
attach.SetTemplateFunc("apis", func() string { return apis })
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ var (
|
|||
utils.RPCVirtualHostsFlag,
|
||||
utils.EthStatsURLFlag,
|
||||
utils.MetricsEnabledFlag,
|
||||
utils.MetricsHTTPFlag,
|
||||
utils.MetricsPortFlag,
|
||||
//utils.FakePoWFlag,
|
||||
//utils.NoCompactionFlag,
|
||||
//utils.GpoBlocksFlag,
|
||||
|
|
@ -291,11 +293,16 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
|
|||
if ctx.GlobalBool(utils.LightModeFlag.Name) || ctx.GlobalString(utils.SyncModeFlag.Name) == "light" {
|
||||
utils.Fatalf("Light clients do not support staking")
|
||||
}
|
||||
// Start metrics export if enabled
|
||||
utils.SetupMetrics(ctx)
|
||||
// Start system runtime metrics collection
|
||||
go metrics.CollectProcessMetrics(3 * time.Second)
|
||||
|
||||
var ethereum *eth.Ethereum
|
||||
if err := stack.Service(ðereum); err != nil {
|
||||
utils.Fatalf("Ethereum service not running: %v", err)
|
||||
}
|
||||
if _, ok := ethereum.Engine().(*XDPoS.XDPoS); ok {
|
||||
if engine, ok := ethereum.Engine().(*XDPoS.XDPoS); ok {
|
||||
go func() {
|
||||
started := false
|
||||
ok := false
|
||||
|
|
@ -339,6 +346,9 @@ func startNode(ctx *cli.Context, stack *node.Node, cfg XDCConfig) {
|
|||
defer close(core.CheckpointCh)
|
||||
for range core.CheckpointCh {
|
||||
log.Info("Checkpoint!!! It's time to reconcile node's state...")
|
||||
log.Info("Update consensus parameters")
|
||||
chain := ethereum.BlockChain()
|
||||
engine.UpdateParams(chain.CurrentHeader())
|
||||
if common.IsTestnet {
|
||||
ok, err = ethereum.ValidateMasternodeTestnet()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ func (c config) servers() []string {
|
|||
func (c config) flush() {
|
||||
os.MkdirAll(filepath.Dir(c.path), 0755)
|
||||
|
||||
out, _ := json.MarshalIndent(c, "", " ")
|
||||
out, _ := json.MarshalIndent(c.Genesis, "", " ")
|
||||
if err := ioutil.WriteFile(c.path, out, 0644); err != nil {
|
||||
log.Warn("Failed to save puppeth configs", "file", c.path, "err", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -116,15 +116,40 @@ func (w *wizard) makeGenesis() {
|
|||
Period: 15,
|
||||
Epoch: 30000,
|
||||
Reward: 0,
|
||||
V2: ¶ms.V2{
|
||||
SwitchBlock: big.NewInt(0),
|
||||
CurrentConfig: ¶ms.V2Config{},
|
||||
AllConfigs: make(map[uint64]*params.V2Config),
|
||||
},
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("How many seconds should blocks take? (default = 2)")
|
||||
genesis.Config.XDPoS.Period = uint64(w.readDefaultInt(2))
|
||||
genesis.Config.XDPoS.V2.CurrentConfig.MinePeriod = int(genesis.Config.XDPoS.Period)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("How many Ethers should be rewarded to masternode? (default = 10)")
|
||||
genesis.Config.XDPoS.Reward = uint64(w.readDefaultInt(10))
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Which block number start v2 consesus? (default = 0)")
|
||||
genesis.Config.XDPoS.V2.SwitchBlock = w.readDefaultBigInt(genesis.Config.XDPoS.V2.SwitchBlock)
|
||||
genesis.Config.XDPoS.V2.CurrentConfig.SwitchRound = 0
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("How long is the v2 timeout period? (default = 10)")
|
||||
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutPeriod = w.readDefaultInt(10)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("How many v2 timeout reach to send Synchronize message? (default = 3)")
|
||||
genesis.Config.XDPoS.V2.CurrentConfig.TimeoutSyncThreshold = w.readDefaultInt(3)
|
||||
|
||||
fmt.Println()
|
||||
fmt.Printf("How many v2 vote collection to generate a QC, should be two thirds of masternodes? (default = %d)\n", common.MaxMasternodesV2/3*2+1)
|
||||
genesis.Config.XDPoS.V2.CurrentConfig.CertThreshold = w.readDefaultInt(common.MaxMasternodesV2)/3*2 + 1
|
||||
|
||||
genesis.Config.XDPoS.V2.AllConfigs[0] = genesis.Config.XDPoS.V2.CurrentConfig
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("Who own the first masternodes? (mandatory)")
|
||||
owner := *w.readAddress()
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics"
|
||||
"github.com/XinFinOrg/XDPoSChain/metrics/exp"
|
||||
"github.com/XinFinOrg/XDPoSChain/node"
|
||||
"github.com/XinFinOrg/XDPoSChain/p2p"
|
||||
"github.com/XinFinOrg/XDPoSChain/p2p/discover"
|
||||
|
|
@ -359,6 +360,20 @@ var (
|
|||
Name: "ethstats",
|
||||
Usage: "Reporting URL of a ethstats service (nodename:secret@host:port)",
|
||||
}
|
||||
// MetricsHTTPFlag defines the endpoint for a stand-alone metrics HTTP endpoint.
|
||||
// Since the pprof service enables sensitive/vulnerable behavior, this allows a user
|
||||
// to enable a public-OK metrics endpoint without having to worry about ALSO exposing
|
||||
// other profiling behavior or information.
|
||||
MetricsHTTPFlag = cli.StringFlag{
|
||||
Name: "metrics.addr",
|
||||
Usage: "Enable stand-alone metrics HTTP server listening interface",
|
||||
Value: metrics.DefaultConfig.HTTP,
|
||||
}
|
||||
MetricsPortFlag = cli.IntFlag{
|
||||
Name: "metrics.port",
|
||||
Usage: "Metrics HTTP server listening port",
|
||||
Value: metrics.DefaultConfig.Port,
|
||||
}
|
||||
MetricsEnabledFlag = cli.BoolFlag{
|
||||
Name: metrics.MetricsEnabledFlag,
|
||||
Usage: "Enable metrics collection and reporting",
|
||||
|
|
@ -1237,7 +1252,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
|
|||
}
|
||||
var engine consensus.Engine
|
||||
if config.XDPoS != nil {
|
||||
engine = XDPoS.New(config.XDPoS, chainDb)
|
||||
engine = XDPoS.New(config, chainDb)
|
||||
} else {
|
||||
engine = ethash.NewFaker()
|
||||
if !ctx.GlobalBool(FakePoWFlag.Name) {
|
||||
|
|
@ -1334,3 +1349,15 @@ func WalkMatch(root, pattern string) ([]string, error) {
|
|||
}
|
||||
return matches, nil
|
||||
}
|
||||
|
||||
func SetupMetrics(ctx *cli.Context) {
|
||||
if metrics.Enabled {
|
||||
log.Info("Enabling metrics collection")
|
||||
|
||||
if ctx.GlobalIsSet(MetricsHTTPFlag.Name) {
|
||||
address := fmt.Sprintf("%s:%d", ctx.GlobalString(MetricsHTTPFlag.Name), ctx.GlobalInt(MetricsPortFlag.Name))
|
||||
log.Info("Enabling stand-alone metrics HTTP endpoint", "address", address)
|
||||
exp.Setup(address)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const (
|
|||
MaxMasternodes = 18
|
||||
MaxMasternodesV2 = 108
|
||||
LimitPenaltyEpoch = 4
|
||||
LimitPenaltyEpochV2 = 0
|
||||
BlocksPerYearTest = uint64(200000)
|
||||
BlocksPerYear = uint64(15768000)
|
||||
LimitThresholdNonceInQueue = 10
|
||||
|
|
@ -35,6 +36,8 @@ var TIP2019Block = big.NewInt(1)
|
|||
var TIPSigning = big.NewInt(3000000)
|
||||
var TIPRandomize = big.NewInt(3464000)
|
||||
|
||||
var TIPV2SwitchBlock = big.NewInt(99999999999)
|
||||
|
||||
var TIPIncreaseMasternodes = big.NewInt(5000000) // Upgrade MN Count at Block.
|
||||
var TIPNoHalvingMNReward = big.NewInt(38383838) // hardfork no halving masternodes reward
|
||||
var BlackListHFNumber = uint64(38383838)
|
||||
|
|
|
|||
5
common/constants/README.md
Normal file
5
common/constants/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Constants
|
||||
This directory is used by dockerfile when builing the per environment constants.go
|
||||
The benefit of this structure is to allow devnet, testnet and mainnet to have different constants configuration setup.
|
||||
|
||||
The default file under `common` directory is for the mainnet, whereas all files under this `constants` directory will override the `constants.go` when building image. For example, when building devnet images, we will do `ADD common/constants/constants.go.devnet /work/common/constants.go`
|
||||
150
common/constants/constants.go.devnet
Normal file
150
common/constants/constants.go.devnet
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
package common
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const (
|
||||
RewardMasterPercent = 90
|
||||
RewardVoterPercent = 0
|
||||
RewardFoundationPercent = 10
|
||||
HexSignMethod = "e341eaa4"
|
||||
HexSetSecret = "34d38600"
|
||||
HexSetOpening = "e11f5ba2"
|
||||
EpocBlockSecret = 800
|
||||
EpocBlockOpening = 850
|
||||
EpocBlockRandomize = 900
|
||||
MaxMasternodes = 18
|
||||
MaxMasternodesV2 = 108
|
||||
LimitPenaltyEpoch = 4
|
||||
LimitPenaltyEpochV2 = 0
|
||||
BlocksPerYearTest = uint64(200000)
|
||||
BlocksPerYear = uint64(15768000)
|
||||
LimitThresholdNonceInQueue = 10
|
||||
DefaultMinGasPrice = 250000000
|
||||
MergeSignRange = 15
|
||||
RangeReturnSigner = 150
|
||||
MinimunMinerBlockPerEpoch = 1
|
||||
|
||||
OneYear = uint64(365 * 86400)
|
||||
LiquidateLendingTradeBlock = uint64(100)
|
||||
)
|
||||
|
||||
var Rewound = uint64(0)
|
||||
|
||||
var TIP2019Block = big.NewInt(1)
|
||||
var TIPSigning = big.NewInt(225000)
|
||||
var TIPRandomize = big.NewInt(225000)
|
||||
|
||||
var TIPV2SwitchBlock = big.NewInt(7074000)
|
||||
|
||||
var TIPIncreaseMasternodes = big.NewInt(225000) // Upgrade MN Count at Block.
|
||||
var TIPNoHalvingMNReward = big.NewInt(429987) // hardfork no halving masternodes reward
|
||||
var BlackListHFNumber = uint64(225000)
|
||||
var TIPXDCX = big.NewInt(225000)
|
||||
var TIPXDCXLending = big.NewInt(225000)
|
||||
var TIPXDCXCancellationFee = big.NewInt(225000)
|
||||
var TIPXDCXCancellationFeeTestnet = big.NewInt(225000)
|
||||
|
||||
var TIPXDCXTestnet = big.NewInt(0)
|
||||
var IsTestnet bool = false
|
||||
var Enable0xPrefix bool = false
|
||||
var StoreRewardFolder string
|
||||
var RollbackHash Hash
|
||||
var BasePrice = big.NewInt(1000000000000000000) // 1
|
||||
var RelayerLockedFund = big.NewInt(20000) // 20000 XDC
|
||||
var RelayerFee = big.NewInt(1000000000000000) // 0.001
|
||||
var XDCXBaseFee = big.NewInt(10000) // 1 / XDCXBaseFee
|
||||
var RelayerCancelFee = big.NewInt(100000000000000) // 0.0001
|
||||
var XDCXBaseCancelFee = new(big.Int).Mul(XDCXBaseFee, big.NewInt(10)) // 1/ (XDCXBaseFee *10)
|
||||
var RelayerLendingFee = big.NewInt(10000000000000000) // 0.01
|
||||
var RelayerLendingCancelFee = big.NewInt(1000000000000000) // 0.001
|
||||
var BaseLendingInterest = big.NewInt(100000000) // 1e8
|
||||
|
||||
var MinGasPrice = big.NewInt(DefaultMinGasPrice)
|
||||
var RelayerRegistrationSMC = "0x16c63b79f9C8784168103C0b74E6A59EC2de4a02"
|
||||
var RelayerRegistrationSMCTestnet = "0xA1996F69f47ba14Cb7f661010A7C31974277958c"
|
||||
var LendingRegistrationSMC = "0x7d761afd7ff65a79e4173897594a194e3c506e57"
|
||||
var LendingRegistrationSMCTestnet = "0x28d7fC2Cf5c18203aaCD7459EFC6Af0643C97bE8"
|
||||
var TRC21IssuerSMCTestNet = HexToAddress("0x0E2C88753131CE01c7551B726b28BFD04e44003F")
|
||||
var TRC21IssuerSMC = HexToAddress("0x8c0faeb5C6bEd2129b8674F262Fd45c4e9468bee")
|
||||
var XDCXListingSMC = HexToAddress("0xDE34dD0f536170993E8CFF639DdFfCF1A85D3E53")
|
||||
var XDCXListingSMCTestNet = HexToAddress("0x14B2Bf043b9c31827A472CE4F94294fE9a6277e0")
|
||||
var TRC21GasPriceBefore = big.NewInt(2500)
|
||||
var TRC21GasPrice = big.NewInt(250000000)
|
||||
var RateTopUp = big.NewInt(90) // 90%
|
||||
var BaseTopUp = big.NewInt(100)
|
||||
var BaseRecall = big.NewInt(100)
|
||||
var TIPTRC21Fee = big.NewInt(13523400)
|
||||
var TIPTRC21FeeTestnet = big.NewInt(225000)
|
||||
var LimitTimeFinality = uint64(30) // limit in 30 block
|
||||
|
||||
var IgnoreSignerCheckBlockArray = map[uint64]bool{
|
||||
uint64(1032300): true,
|
||||
uint64(1033200): true,
|
||||
uint64(27307800): true,
|
||||
uint64(28270800): true,
|
||||
}
|
||||
var Blacklist = map[Address]bool{
|
||||
HexToAddress("0x5248bfb72fd4f234e062d3e9bb76f08643004fcd"): true,
|
||||
HexToAddress("0x5ac26105b35ea8935be382863a70281ec7a985e9"): true,
|
||||
HexToAddress("0x09c4f991a41e7ca0645d7dfbfee160b55e562ea4"): true,
|
||||
HexToAddress("0xb3157bbc5b401a45d6f60b106728bb82ebaa585b"): true,
|
||||
HexToAddress("0x741277a8952128d5c2ffe0550f5001e4c8247674"): true,
|
||||
HexToAddress("0x10ba49c1caa97d74b22b3e74493032b180cebe01"): true,
|
||||
HexToAddress("0x07048d51d9e6179578a6e3b9ee28cdc183b865e4"): true,
|
||||
HexToAddress("0x4b899001d73c7b4ec404a771d37d9be13b8983de"): true,
|
||||
HexToAddress("0x85cb320a9007f26b7652c19a2a65db1da2d0016f"): true,
|
||||
HexToAddress("0x06869dbd0e3a2ea37ddef832e20fa005c6f0ca39"): true,
|
||||
HexToAddress("0x82e48bc7e2c93d89125428578fb405947764ad7c"): true,
|
||||
HexToAddress("0x1f9a78534d61732367cbb43fc6c89266af67c989"): true,
|
||||
HexToAddress("0x7c3b1fa91df55ff7af0cad9e0399384dc5c6641b"): true,
|
||||
HexToAddress("0x5888dc1ceb0ff632713486b9418e59743af0fd20"): true,
|
||||
HexToAddress("0xa512fa1c735fc3cc635624d591dd9ea1ce339ca5"): true,
|
||||
HexToAddress("0x0832517654c7b7e36b1ef45d76de70326b09e2c7"): true,
|
||||
HexToAddress("0xca14e3c4c78bafb60819a78ff6e6f0f709d2aea7"): true,
|
||||
HexToAddress("0x652ce195a23035114849f7642b0e06647d13e57a"): true,
|
||||
HexToAddress("0x29a79f00f16900999d61b6e171e44596af4fb5ae"): true,
|
||||
HexToAddress("0xf9fd1c2b0af0d91b0b6754e55639e3f8478dd04a"): true,
|
||||
HexToAddress("0xb835710c9901d5fe940ef1b99ed918902e293e35"): true,
|
||||
HexToAddress("0x04dd29ce5c253377a9a3796103ea0d9a9e514153"): true,
|
||||
HexToAddress("0x2b4b56846eaf05c1fd762b5e1ac802efd0ab871c"): true,
|
||||
HexToAddress("0x1d1f909f6600b23ce05004f5500ab98564717996"): true,
|
||||
HexToAddress("0x0dfdcebf80006dc9ab7aae8c216b51c6b6759e86"): true,
|
||||
HexToAddress("0x2b373890a28e5e46197fbc04f303bbfdd344056f"): true,
|
||||
HexToAddress("0xa8a3ef3dc5d8e36aee76f3671ec501ec31e28254"): true,
|
||||
HexToAddress("0x4f3d18136fe2b5665c29bdaf74591fc6625ef427"): true,
|
||||
HexToAddress("0x175d728b0e0f1facb5822a2e0c03bde93596e324"): true,
|
||||
HexToAddress("0xd575c2611984fcd79513b80ab94f59dc5bab4916"): true,
|
||||
HexToAddress("0x0579337873c97c4ba051310236ea847f5be41bc0"): true,
|
||||
HexToAddress("0xed12a519cc15b286920fc15fd86106b3e6a16218"): true,
|
||||
HexToAddress("0x492d26d852a0a0a2982bb40ec86fe394488c419e"): true,
|
||||
HexToAddress("0xce5c7635d02dc4e1d6b46c256cae6323be294a32"): true,
|
||||
HexToAddress("0x8b94db158b5e78a6c032c7e7c9423dec62c8b11c"): true,
|
||||
HexToAddress("0x0e7c48c085b6b0aa7ca6e4cbcc8b9a92dc270eb4"): true,
|
||||
HexToAddress("0x206e6508462033ef8425edc6c10789d241d49acb"): true,
|
||||
HexToAddress("0x7710e7b7682f26cb5a1202e1cff094fbf7777758"): true,
|
||||
HexToAddress("0xcb06f949313b46bbf53b8e6b2868a0c260ff9385"): true,
|
||||
HexToAddress("0xf884e43533f61dc2997c0e19a6eff33481920c00"): true,
|
||||
HexToAddress("0x8b635ef2e4c8fe21fc2bda027eb5f371d6aa2fc1"): true,
|
||||
HexToAddress("0x10f01a27cf9b29d02ce53497312b96037357a361"): true,
|
||||
HexToAddress("0x693dd49b0ed70f162d733cf20b6c43dc2a2b4d95"): true,
|
||||
HexToAddress("0xe0bec72d1c2a7a7fb0532cdfac44ebab9f6f41ee"): true,
|
||||
HexToAddress("0xc8793633a537938cb49cdbbffd45428f10e45b64"): true,
|
||||
HexToAddress("0x0d07a6cbbe9fa5c4f154e5623bfe47fb4d857d8e"): true,
|
||||
HexToAddress("0xd4080b289da95f70a586610c38268d8d4cf1e4c4"): true,
|
||||
HexToAddress("0x8bcfb0caf41f0aa1b548cae76dcdd02e33866a1b"): true,
|
||||
HexToAddress("0xabfef22b92366d3074676e77ea911ccaabfb64c1"): true,
|
||||
HexToAddress("0xcc4df7a32faf3efba32c9688def5ccf9fefe443d"): true,
|
||||
HexToAddress("0x7ec1e48a582475f5f2b7448a86c4ea7a26ea36f8"): true,
|
||||
HexToAddress("0xe3de67289080f63b0c2612844256a25bb99ac0ad"): true,
|
||||
HexToAddress("0x3ba623300cf9e48729039b3c9e0dee9b785d636e"): true,
|
||||
HexToAddress("0x402f2cfc9c8942f5e7a12c70c625d07a5d52fe29"): true,
|
||||
HexToAddress("0xd62358d42afbde095a4ca868581d85f9adcc3d61"): true,
|
||||
HexToAddress("0x3969f86acb733526cd61e3c6e3b4660589f32bc6"): true,
|
||||
HexToAddress("0x67615413d7cdadb2c435a946aec713a9a9794d39"): true,
|
||||
HexToAddress("0xfe685f43acc62f92ab01a8da80d76455d39d3cb3"): true,
|
||||
HexToAddress("0x3538a544021c07869c16b764424c5987409cba48"): true,
|
||||
HexToAddress("0xe187cf86c2274b1f16e8225a7da9a75aba4f1f5f"): true,
|
||||
HexToAddress("0x0000000000000000000000000000000000000011"): true,
|
||||
}
|
||||
91
common/countdown/countdown.go
Normal file
91
common/countdown/countdown.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// A countdown timer that will mostly be used by XDPoS v2 consensus engine
|
||||
package countdown
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
type CountdownTimer struct {
|
||||
lock sync.RWMutex // Protects the Initilised field
|
||||
resetc chan int
|
||||
quitc chan chan struct{}
|
||||
initilised bool
|
||||
timeoutDuration time.Duration
|
||||
// Triggered when the countdown timer timeout for the `timeoutDuration` period, it will pass current timestamp to the callback function
|
||||
OnTimeoutFn func(time time.Time, i interface{}) error
|
||||
}
|
||||
|
||||
func NewCountDown(duration time.Duration) *CountdownTimer {
|
||||
return &CountdownTimer{
|
||||
resetc: make(chan int),
|
||||
quitc: make(chan chan struct{}),
|
||||
initilised: false,
|
||||
timeoutDuration: duration,
|
||||
}
|
||||
}
|
||||
|
||||
// Completely stop the countdown timer from running.
|
||||
func (t *CountdownTimer) StopTimer() {
|
||||
q := make(chan struct{})
|
||||
t.quitc <- q
|
||||
<-q
|
||||
}
|
||||
|
||||
func (t *CountdownTimer) SetTimeoutDuration(duration time.Duration) {
|
||||
t.timeoutDuration = duration
|
||||
}
|
||||
|
||||
// Reset will start the countdown timer if it's already stopped, or simply reset the countdown time back to the defual `duration`
|
||||
func (t *CountdownTimer) Reset(i interface{}) {
|
||||
if !t.isInitilised() {
|
||||
t.setInitilised(true)
|
||||
go t.startTimer(i)
|
||||
} else {
|
||||
t.resetc <- 0
|
||||
}
|
||||
}
|
||||
|
||||
// A long running process that
|
||||
func (t *CountdownTimer) startTimer(i interface{}) {
|
||||
// Make sure we mark Initilised to false when we quit the countdown
|
||||
defer t.setInitilised(false)
|
||||
timer := time.NewTimer(t.timeoutDuration)
|
||||
// We start with a inf loop
|
||||
for {
|
||||
select {
|
||||
case q := <-t.quitc:
|
||||
log.Debug("Quit countdown timer")
|
||||
close(q)
|
||||
return
|
||||
case <-timer.C:
|
||||
log.Debug("Countdown time reached!")
|
||||
go func() {
|
||||
err := t.OnTimeoutFn(time.Now(), i)
|
||||
if err != nil {
|
||||
log.Error("OnTimeoutFn error", "error", err)
|
||||
}
|
||||
log.Debug("OnTimeoutFn processed")
|
||||
}()
|
||||
timer.Reset(t.timeoutDuration)
|
||||
case <-t.resetc:
|
||||
log.Debug("Reset countdown timer")
|
||||
timer.Reset(t.timeoutDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set the desired value to Initilised with lock to avoid race condition
|
||||
func (t *CountdownTimer) setInitilised(value bool) {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
t.initilised = value
|
||||
}
|
||||
|
||||
func (t *CountdownTimer) isInitilised() bool {
|
||||
t.lock.Lock()
|
||||
defer t.lock.Unlock()
|
||||
return t.initilised
|
||||
}
|
||||
157
common/countdown/countdown_test.go
Normal file
157
common/countdown/countdown_test.go
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
package countdown
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountdownWillCallback(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(1000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
countdown.Reset(fakeI)
|
||||
<-called
|
||||
t.Log("Times up, successfully called OnTimeoutFn")
|
||||
}
|
||||
|
||||
func TestCountdownShouldReset(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
|
||||
resetTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
|
||||
firstReset:
|
||||
for {
|
||||
select {
|
||||
case <-called:
|
||||
if time.Now().After(expectedCalledTime) {
|
||||
// Make sure the countdown runs forever
|
||||
assert.True(t, countdown.isInitilised())
|
||||
t.Log("Correctly reset the countdown once")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly first time")
|
||||
}
|
||||
break firstReset
|
||||
case <-resetTimer.C:
|
||||
countdown.Reset(fakeI)
|
||||
}
|
||||
}
|
||||
|
||||
// Now the countdown is paused after calling the callback function, let's reset it again
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
|
||||
<-called
|
||||
// Always initilised
|
||||
assert.True(t, countdown.isInitilised())
|
||||
if time.Now().After(expectedTimeAfterReset) {
|
||||
t.Log("Correctly reset the countdown second time")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly second time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountdownShouldResetEvenIfErrored(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return fmt.Errorf("ERROR!")
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedCalledTime := time.Now().Add(9000 * time.Millisecond)
|
||||
resetTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
|
||||
firstReset:
|
||||
for {
|
||||
select {
|
||||
case <-called:
|
||||
if time.Now().After(expectedCalledTime) {
|
||||
// Make sure the countdown runs forever
|
||||
assert.True(t, countdown.isInitilised())
|
||||
t.Log("Correctly reset the countdown once")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly first time")
|
||||
}
|
||||
break firstReset
|
||||
case <-resetTimer.C:
|
||||
countdown.Reset(fakeI)
|
||||
}
|
||||
}
|
||||
|
||||
// Now the countdown is paused after calling the callback function, let's reset it again
|
||||
assert.True(t, countdown.isInitilised())
|
||||
expectedTimeAfterReset := time.Now().Add(5000 * time.Millisecond)
|
||||
<-called
|
||||
// Always initilised
|
||||
assert.True(t, countdown.isInitilised())
|
||||
if time.Now().After(expectedTimeAfterReset) {
|
||||
t.Log("Correctly reset the countdown second time")
|
||||
} else {
|
||||
t.Fatalf("Countdown did not reset correctly second time")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCountdownShouldBeAbleToStop(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
// Check countdown did not start
|
||||
assert.False(t, countdown.isInitilised())
|
||||
countdown.Reset(fakeI)
|
||||
// Now the countdown should already started
|
||||
assert.True(t, countdown.isInitilised())
|
||||
// Try manually stop the timer before it triggers the callback
|
||||
stopTimer := time.NewTimer(4000 * time.Millisecond)
|
||||
<-stopTimer.C
|
||||
countdown.StopTimer()
|
||||
assert.False(t, countdown.isInitilised())
|
||||
}
|
||||
|
||||
func TestCountdownShouldAvoidDeadlock(t *testing.T) {
|
||||
var fakeI interface{}
|
||||
called := make(chan int)
|
||||
countdown := NewCountDown(5000 * time.Millisecond)
|
||||
OnTimeoutFn := func(time.Time, interface{}) error {
|
||||
countdown.Reset(fakeI)
|
||||
called <- 1
|
||||
return nil
|
||||
}
|
||||
|
||||
countdown.OnTimeoutFn = OnTimeoutFn
|
||||
countdown.Reset(fakeI)
|
||||
<-called
|
||||
}
|
||||
|
|
@ -282,19 +282,23 @@ func (a UnprefixedAddress) MarshalText() ([]byte, error) {
|
|||
|
||||
// Extract validators from byte array.
|
||||
func RemoveItemFromArray(array []Address, items []Address) []Address {
|
||||
// Create newArray to stop append change array value
|
||||
newArray := make([]Address, len(array))
|
||||
copy(newArray, array)
|
||||
|
||||
if len(items) == 0 {
|
||||
return array
|
||||
return newArray
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
for i := len(array) - 1; i >= 0; i-- {
|
||||
if array[i] == item {
|
||||
array = append(array[:i], array[i+1:]...)
|
||||
for i := len(newArray) - 1; i >= 0; i-- {
|
||||
if newArray[i] == item {
|
||||
newArray = append(newArray[:i], newArray[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array
|
||||
return newArray
|
||||
}
|
||||
|
||||
// Extract validators from byte array.
|
||||
|
|
|
|||
|
|
@ -158,8 +158,12 @@ func BenchmarkAddressHex(b *testing.B) {
|
|||
func TestRemoveItemInArray(t *testing.T) {
|
||||
array := []Address{HexToAddress("0x0000003"), HexToAddress("0x0000001"), HexToAddress("0x0000002"), HexToAddress("0x0000003")}
|
||||
remove := []Address{HexToAddress("0x0000002"), HexToAddress("0x0000004"), HexToAddress("0x0000003")}
|
||||
array = RemoveItemFromArray(array, remove)
|
||||
if len(array) != 1 {
|
||||
newArray := RemoveItemFromArray(array, remove)
|
||||
|
||||
if array[0] != HexToAddress("0x0000003") || array[2] != HexToAddress("0x0000002") {
|
||||
t.Error("should keep the original item from array address")
|
||||
}
|
||||
if len(newArray) != 1 {
|
||||
t.Error("fail remove item from array address")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v1"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/clique"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
|
|
@ -35,8 +36,18 @@ import (
|
|||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
func SigHash(header *types.Header) (hash common.Hash) {
|
||||
return utils.SigHash(header)
|
||||
const (
|
||||
ExtraFieldCheck = true
|
||||
SkipExtraFieldCheck = false
|
||||
)
|
||||
|
||||
func (x *XDPoS) SigHash(header *types.Header) (hash common.Hash) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.SignHash(header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.SigHash(header)
|
||||
}
|
||||
}
|
||||
|
||||
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
||||
|
|
@ -48,34 +59,58 @@ type XDPoS struct {
|
|||
// Transaction cache, only make sense for adaptor level
|
||||
signingTxsCache *lru.Cache
|
||||
|
||||
// Share Channel
|
||||
MinePeriodCh chan int // Miner wait Period Channel
|
||||
|
||||
// Trading and lending service
|
||||
GetXDCXService func() utils.TradingService
|
||||
GetLendingService func() utils.LendingService
|
||||
|
||||
// The exact consensus engine with different versions
|
||||
EngineV1 engine_v1.XDPoS_v1
|
||||
EngineV2 engine_v2.XDPoS_v2
|
||||
EngineV1 *engine_v1.XDPoS_v1
|
||||
EngineV2 *engine_v2.XDPoS_v2
|
||||
}
|
||||
|
||||
// Subscribe to consensus engines forensics events. Currently only exist for engine v2
|
||||
func (x *XDPoS) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
|
||||
return x.EngineV2.ForensicsProcessor.SubscribeForensicsEvent(ch)
|
||||
}
|
||||
|
||||
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||
// signers set to the ones provided by the user.
|
||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS {
|
||||
func New(chainConfig *params.ChainConfig, db ethdb.Database) *XDPoS {
|
||||
log.Info("[New] initialise consensus engines")
|
||||
config := chainConfig.XDPoS
|
||||
// Set any missing consensus parameters to their defaults
|
||||
conf := *config
|
||||
if conf.Epoch == 0 {
|
||||
conf.Epoch = utils.EpochLength
|
||||
if config.Epoch == 0 {
|
||||
config.Epoch = utils.EpochLength
|
||||
}
|
||||
|
||||
// For testing and testing project, default to mainnet config
|
||||
if config.V2 == nil {
|
||||
config.V2 = ¶ms.V2{
|
||||
SwitchBlock: params.XDCMainnetChainConfig.XDPoS.V2.SwitchBlock,
|
||||
CurrentConfig: params.MainnetV2Configs[0],
|
||||
AllConfigs: params.MainnetV2Configs,
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("xdc config loading", "config", config)
|
||||
|
||||
minePeriodCh := make(chan int)
|
||||
|
||||
// Allocate the snapshot caches and create the engine
|
||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||
|
||||
return &XDPoS{
|
||||
config: &conf,
|
||||
config: config,
|
||||
db: db,
|
||||
|
||||
MinePeriodCh: minePeriodCh,
|
||||
|
||||
signingTxsCache: signingTxsCache,
|
||||
EngineV1: *engine_v1.New(&conf, db),
|
||||
EngineV2: *engine_v2.New(&conf, db),
|
||||
EngineV1: engine_v1.New(chainConfig, db),
|
||||
EngineV2: engine_v2.New(chainConfig, db, minePeriodCh),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -89,6 +124,8 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
|||
conf = chainConfig.XDPoS
|
||||
}
|
||||
|
||||
minePeriodCh := make(chan int)
|
||||
|
||||
// Allocate the snapshot caches and create the engine
|
||||
signingTxsCache, _ := lru.New(utils.BlockSignersCacheLimit)
|
||||
|
||||
|
|
@ -96,13 +133,38 @@ func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS {
|
|||
config: conf,
|
||||
db: db,
|
||||
|
||||
MinePeriodCh: minePeriodCh,
|
||||
|
||||
GetXDCXService: func() utils.TradingService { return nil },
|
||||
GetLendingService: func() utils.LendingService { return nil },
|
||||
|
||||
signingTxsCache: signingTxsCache,
|
||||
EngineV1: *engine_v1.NewFaker(db, conf),
|
||||
EngineV2: *engine_v2.NewFaker(db, conf),
|
||||
EngineV1: engine_v1.NewFaker(db, chainConfig),
|
||||
EngineV2: engine_v2.New(chainConfig, db, minePeriodCh),
|
||||
}
|
||||
return fakeEngine
|
||||
}
|
||||
|
||||
// Reset parameters after checkpoint due to config may change
|
||||
func (x *XDPoS) UpdateParams(header *types.Header) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
x.EngineV2.UpdateParams(header)
|
||||
return
|
||||
default: // Default "v1"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) Initial(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Initial(chain, header)
|
||||
default: // Default "v1"
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Eth Consensus engine interface implementation
|
||||
*/
|
||||
|
|
@ -120,7 +182,7 @@ func (x *XDPoS) APIs(chain consensus.ChainReader) []rpc.API {
|
|||
// Author implements consensus.Engine, returning the Ethereum address recovered
|
||||
// from the signature in the header's extra-data section.
|
||||
func (x *XDPoS) Author(header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Author(header)
|
||||
default: // Default "v1"
|
||||
|
|
@ -130,7 +192,9 @@ func (x *XDPoS) Author(header *types.Header) (common.Address, error) {
|
|||
|
||||
// VerifyHeader checks whether a header conforms to the consensus rules.
|
||||
func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header, fullVerify bool) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.VerifyHeader(chain, header, fullVerify)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifyHeader(chain, header, fullVerify)
|
||||
}
|
||||
|
|
@ -140,14 +204,38 @@ func (x *XDPoS) VerifyHeader(chain consensus.ChainReader, header *types.Header,
|
|||
// method returns a quit channel to abort the operations and a results channel to
|
||||
// retrieve the async verifications (the order is that of the input slice).
|
||||
func (x *XDPoS) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
|
||||
// TODO: (Hashlab) This funciton is a special case
|
||||
return x.EngineV1.VerifyHeaders(chain, headers, fullVerifies)
|
||||
abort := make(chan struct{})
|
||||
results := make(chan error, len(headers))
|
||||
|
||||
// Split the headers list into v1 and v2 buckets
|
||||
var v1headers []*types.Header
|
||||
var v2headers []*types.Header
|
||||
|
||||
for _, header := range headers {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
v2headers = append(v2headers, header)
|
||||
default: // Default "v1"
|
||||
v1headers = append(v1headers, header)
|
||||
}
|
||||
}
|
||||
|
||||
if v1headers != nil {
|
||||
x.EngineV1.VerifyHeaders(chain, v1headers, fullVerifies, abort, results)
|
||||
}
|
||||
if v2headers != nil {
|
||||
x.EngineV2.VerifyHeaders(chain, v2headers, fullVerifies, abort, results)
|
||||
}
|
||||
|
||||
return abort, results
|
||||
}
|
||||
|
||||
// VerifyUncles implements consensus.Engine, always returning an error for any
|
||||
// uncles as this consensus mechanism doesn't permit uncles.
|
||||
func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) error {
|
||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||
switch x.config.BlockConsensusVersion(block.Number(), block.Extra(), ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifyUncles(chain, block)
|
||||
}
|
||||
|
|
@ -156,7 +244,9 @@ func (x *XDPoS) VerifyUncles(chain consensus.ChainReader, block *types.Block) er
|
|||
// VerifySeal implements consensus.Engine, checking whether the signature contained
|
||||
// in the header satisfies the consensus protocol requirements.
|
||||
func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.VerifySeal(chain, header)
|
||||
}
|
||||
|
|
@ -165,7 +255,9 @@ func (x *XDPoS) VerifySeal(chain consensus.ChainReader, header *types.Header) er
|
|||
// Prepare implements consensus.Engine, preparing all the consensus fields of the
|
||||
// header for running the transactions on top.
|
||||
func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, nil, SkipExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Prepare(chain, header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Prepare(chain, header)
|
||||
}
|
||||
|
|
@ -174,7 +266,9 @@ func (x *XDPoS) Prepare(chain consensus.ChainReader, header *types.Header) error
|
|||
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
|
||||
// rewards given, and returns the final block.
|
||||
func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, parentState *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Finalize(chain, header, state, parentState, txs, uncles, receipts)
|
||||
}
|
||||
|
|
@ -183,7 +277,9 @@ func (x *XDPoS) Finalize(chain consensus.ChainReader, header *types.Header, stat
|
|||
// Seal implements consensus.Engine, attempting to create a sealed block using
|
||||
// the local signing credentials.
|
||||
func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-chan struct{}) (*types.Block, error) {
|
||||
switch x.config.BlockConsensusVersion(block.Number()) {
|
||||
switch x.config.BlockConsensusVersion(block.Number(), block.Extra(), ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.Seal(chain, block, stop)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.Seal(chain, block, stop)
|
||||
}
|
||||
|
|
@ -193,12 +289,23 @@ func (x *XDPoS) Seal(chain consensus.ChainReader, block *types.Block, stop <-cha
|
|||
// that a new block should have based on the previous blocks in the chain and the
|
||||
// current signer.
|
||||
func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int {
|
||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
||||
switch x.config.BlockConsensusVersion(parent.Number, parent.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.CalcDifficulty(chain, time, parent)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.CalcDifficulty(chain, time, parent)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) HandleProposedBlock(chain consensus.ChainReader, header *types.Header) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.ProposedBlockHandler(chain, header)
|
||||
default: // Default "v1"
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
XDC specific methods
|
||||
*/
|
||||
|
|
@ -208,66 +315,116 @@ func (x *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent
|
|||
func (x *XDPoS) Authorize(signer common.Address, signFn clique.SignerFn) {
|
||||
// Authorize each consensus individually
|
||||
x.EngineV1.Authorize(signer, signFn)
|
||||
x.EngineV2.Authorize(signer, signFn)
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetPeriod() uint64 {
|
||||
return x.config.Period
|
||||
}
|
||||
|
||||
func (x *XDPoS) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
func (x *XDPoS) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.IsAuthorisedAddress(chain, header, address)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.IsAuthorisedAddress(header, chain, address)
|
||||
return x.EngineV1.IsAuthorisedAddress(chain, header, address)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodes(chain, header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodes(chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
switch x.config.BlockConsensusVersion(parent.Number) {
|
||||
func (x *XDPoS) GetMasternodesByNumber(chain consensus.ChainReader, blockNumber uint64) []common.Address {
|
||||
blockHeader := chain.GetHeaderByNumber(blockNumber)
|
||||
if blockHeader == nil {
|
||||
log.Error("[GetMasternodesByNumber] Unable to find block", "Num", blockNumber)
|
||||
return []common.Address{}
|
||||
}
|
||||
switch x.config.BlockConsensusVersion(big.NewInt(int64(blockNumber)), blockHeader.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodes(chain, blockHeader)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodes(chain, blockHeader)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
|
||||
switch x.config.BlockConsensusVersion(big.NewInt(parent.Number.Int64()+1), nil, SkipExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.YourTurn(chain, parent, signer)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.YourTurn(chain, parent, signer)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetValidator(creator common.Address, chain consensus.ChainReader, header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
default: // Default "v1"
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
default: // Default "v1", v2 does not need this function
|
||||
return x.EngineV1.GetValidator(creator, chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
// fmt.Println("UpdateMasternodes")
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.UpdateMasternodes(chain, header, ms)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.UpdateMasternodes(chain, header, ms)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) RecoverSigner(header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.RecoverSigner(header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) RecoverValidator(header *types.Header) (common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.RecoverValidator(header)
|
||||
}
|
||||
}
|
||||
|
||||
// Get master nodes over extra data of previous checkpoint block.
|
||||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(preCheckpointHeader.Number) {
|
||||
func (x *XDPoS) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
switch x.config.BlockConsensusVersion(checkpointHeader.Number, checkpointHeader.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetMasternodesFromEpochSwitchHeader(checkpointHeader)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetMasternodesFromCheckpointHeader(preCheckpointHeader, n, e)
|
||||
return x.EngineV1.GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Check is epoch switch (checkpoint) block
|
||||
func (x *XDPoS) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.IsEpochSwitch(header)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.IsEpochSwitch(header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNumber *big.Int) (uint64, uint64, error) {
|
||||
header := chain.GetHeaderByNumber(blockNumber.Uint64())
|
||||
switch x.config.BlockConsensusVersion(blockNumber, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return x.EngineV2.GetCurrentEpochSwitchBlock(chain, blockNumber)
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetCurrentEpochSwitchBlock(blockNumber)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +434,14 @@ func (x *XDPoS) GetDb() ethdb.Database {
|
|||
}
|
||||
|
||||
func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (*utils.PublicApiSnapshot, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
sp, err := x.EngineV2.GetSnapshot(chain, header)
|
||||
return &utils.PublicApiSnapshot{
|
||||
Number: sp.Number,
|
||||
Hash: sp.Hash,
|
||||
Signers: sp.GetMappedMasterNodes(),
|
||||
}, err
|
||||
default: // Default "v1"
|
||||
sp, err := x.EngineV1.GetSnapshot(chain, header)
|
||||
// Convert to a standard PublicApiSnapshot type, otherwise it's a breaking change to API
|
||||
|
|
@ -293,12 +457,27 @@ func (x *XDPoS) GetSnapshot(chain consensus.ChainReader, header *types.Header) (
|
|||
}
|
||||
|
||||
func (x *XDPoS) GetAuthorisedSignersFromSnapshot(chain consensus.ChainReader, header *types.Header) ([]common.Address, error) {
|
||||
switch x.config.BlockConsensusVersion(header.Number) {
|
||||
switch x.config.BlockConsensusVersion(header.Number, header.Extra, ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
return []common.Address{}, nil
|
||||
default: // Default "v1"
|
||||
return x.EngineV1.GetAuthorisedSignersFromSnapshot(chain, header)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS) FindParentBlockToAssign(chain consensus.ChainReader, currentBlock *types.Block) *types.Block {
|
||||
switch x.config.BlockConsensusVersion(currentBlock.Number(), currentBlock.Extra(), ExtraFieldCheck) {
|
||||
case params.ConsensusEngineVersion2:
|
||||
block := x.EngineV2.FindParentBlockToAssign(chain)
|
||||
if block == nil {
|
||||
return currentBlock
|
||||
}
|
||||
return block
|
||||
default: // Default "v1"
|
||||
return currentBlock
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Caching
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
func TestAdaptorShouldShareDbWithV1Engine(t *testing.T) {
|
||||
database := rawdb.NewMemoryDatabase()
|
||||
config := params.TestXDPoSMockChainConfig.XDPoS
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
engine := New(config, database)
|
||||
|
||||
assert := assert.New(t)
|
||||
|
|
|
|||
|
|
@ -16,12 +16,14 @@
|
|||
package XDPoS
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||
)
|
||||
|
||||
|
|
@ -31,6 +33,19 @@ type API struct {
|
|||
chain consensus.ChainReader
|
||||
XDPoS *XDPoS
|
||||
}
|
||||
|
||||
type V2BlockInfo struct {
|
||||
Hash common.Hash
|
||||
Round types.Round
|
||||
Number *big.Int
|
||||
ParentHash common.Hash
|
||||
Committed bool
|
||||
Miner common.Hash
|
||||
Timestamp *big.Int
|
||||
EncodedRLP string
|
||||
Error string
|
||||
}
|
||||
|
||||
type NetworkInformation struct {
|
||||
NetworkId *big.Int
|
||||
XDCValidatorAddress common.Address
|
||||
|
|
@ -40,6 +55,26 @@ type NetworkInformation struct {
|
|||
LendingAddress common.Address
|
||||
}
|
||||
|
||||
type SignerTypes struct {
|
||||
CurrentNumber int
|
||||
CurrentSigners []common.Address
|
||||
MissingSigners []common.Address
|
||||
}
|
||||
|
||||
type MasternodesStatus struct {
|
||||
Number uint64
|
||||
Round types.Round
|
||||
MasternodesLen int
|
||||
Masternodes []common.Address
|
||||
PenaltyLen int
|
||||
Penalty []common.Address
|
||||
StandbynodesLen int
|
||||
Standbynodes []common.Address
|
||||
Error error
|
||||
}
|
||||
|
||||
type MessageStatus map[string]map[string]SignerTypes
|
||||
|
||||
// GetSnapshot retrieves the state snapshot at a given block.
|
||||
func (api *API) GetSnapshot(number *rpc.BlockNumber) (*utils.PublicApiSnapshot, error) {
|
||||
// Retrieve the requested block number (or current if none requested)
|
||||
|
|
@ -91,6 +126,143 @@ func (api *API) GetSignersAtHash(hash common.Hash) ([]common.Address, error) {
|
|||
return api.XDPoS.GetAuthorisedSignersFromSnapshot(api.chain, header)
|
||||
}
|
||||
|
||||
func (api *API) GetMasternodesByNumber(number *rpc.BlockNumber) MasternodesStatus {
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else if *number == rpc.CommittedBlockNumber {
|
||||
hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
|
||||
header = api.chain.GetHeaderByHash(hash)
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
|
||||
round, err := api.XDPoS.EngineV2.GetRoundNumber(header)
|
||||
if err != nil {
|
||||
return MasternodesStatus{
|
||||
Error: err,
|
||||
}
|
||||
}
|
||||
|
||||
masterNodes := api.XDPoS.EngineV2.GetMasternodes(api.chain, header)
|
||||
penalties := api.XDPoS.EngineV2.GetPenalties(api.chain, header)
|
||||
standbynodes := api.XDPoS.EngineV2.GetStandbynodes(api.chain, header)
|
||||
|
||||
info := MasternodesStatus{
|
||||
Number: header.Number.Uint64(),
|
||||
Round: round,
|
||||
MasternodesLen: len(masterNodes),
|
||||
Masternodes: masterNodes,
|
||||
PenaltyLen: len(penalties),
|
||||
Penalty: penalties,
|
||||
StandbynodesLen: len(standbynodes),
|
||||
Standbynodes: standbynodes,
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// Get current vote pool and timeout pool content and missing messages
|
||||
func (api *API) GetLatestPoolStatus() MessageStatus {
|
||||
header := api.chain.CurrentHeader()
|
||||
masternodes := api.XDPoS.EngineV2.GetMasternodes(api.chain, header)
|
||||
|
||||
receivedVotes := api.XDPoS.EngineV2.ReceivedVotes()
|
||||
receivedTimeouts := api.XDPoS.EngineV2.ReceivedTimeouts()
|
||||
info := make(MessageStatus)
|
||||
info["vote"] = make(map[string]SignerTypes)
|
||||
info["timeout"] = make(map[string]SignerTypes)
|
||||
|
||||
calculateSigners(info["vote"], receivedVotes, masternodes)
|
||||
calculateSigners(info["timeout"], receivedTimeouts, masternodes)
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func (api *API) GetV2BlockByHeader(header *types.Header, uncle bool) *V2BlockInfo {
|
||||
committed := false
|
||||
latestCommittedBlock := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo()
|
||||
if latestCommittedBlock == nil {
|
||||
return &V2BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
Error: "can not find latest committed block from consensus",
|
||||
}
|
||||
}
|
||||
if header.Number.Uint64() <= latestCommittedBlock.Number.Uint64() {
|
||||
committed = true && !uncle
|
||||
}
|
||||
|
||||
round, err := api.XDPoS.EngineV2.GetRoundNumber(header)
|
||||
|
||||
if err != nil {
|
||||
return &V2BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
encodeBytes, err := rlp.EncodeToBytes(header)
|
||||
if err != nil {
|
||||
return &V2BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
Error: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
block := &V2BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
ParentHash: header.ParentHash,
|
||||
Number: header.Number,
|
||||
Round: round,
|
||||
Committed: committed,
|
||||
Miner: header.Coinbase.Hash(),
|
||||
Timestamp: header.Time,
|
||||
EncodedRLP: base64.StdEncoding.EncodeToString(encodeBytes),
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
func (api *API) GetV2BlockByNumber(number *rpc.BlockNumber) *V2BlockInfo {
|
||||
var header *types.Header
|
||||
if number == nil || *number == rpc.LatestBlockNumber {
|
||||
header = api.chain.CurrentHeader()
|
||||
} else if *number == rpc.CommittedBlockNumber {
|
||||
hash := api.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
|
||||
header = api.chain.GetHeaderByHash(hash)
|
||||
} else {
|
||||
header = api.chain.GetHeaderByNumber(uint64(number.Int64()))
|
||||
}
|
||||
|
||||
if header == nil {
|
||||
return &V2BlockInfo{
|
||||
Number: big.NewInt(number.Int64()),
|
||||
Error: "can not find block from this number",
|
||||
}
|
||||
}
|
||||
|
||||
uncle := false
|
||||
return api.GetV2BlockByHeader(header, uncle)
|
||||
}
|
||||
|
||||
// Confirm V2 Block Committed Status
|
||||
func (api *API) GetV2BlockByHash(blockHash common.Hash) *V2BlockInfo {
|
||||
header := api.chain.GetHeaderByHash(blockHash)
|
||||
if header == nil {
|
||||
return &V2BlockInfo{
|
||||
Hash: blockHash,
|
||||
Error: "can not find block from this hash",
|
||||
}
|
||||
}
|
||||
|
||||
// confirm this is on the main chain
|
||||
chainHeader := api.chain.GetHeaderByNumber(header.Number.Uint64())
|
||||
uncle := false
|
||||
if header.Hash() != chainHeader.Hash() {
|
||||
uncle = true
|
||||
}
|
||||
|
||||
return api.GetV2BlockByHeader(header, uncle)
|
||||
}
|
||||
|
||||
func (api *API) NetworkInformation() NetworkInformation {
|
||||
info := NetworkInformation{}
|
||||
info.NetworkId = api.chain.Config().ChainId
|
||||
|
|
@ -108,3 +280,28 @@ func (api *API) NetworkInformation() NetworkInformation {
|
|||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func calculateSigners(message map[string]SignerTypes, pool map[string]map[common.Hash]utils.PoolObj, masternodes []common.Address) {
|
||||
for name, objs := range pool {
|
||||
var currentSigners []common.Address
|
||||
missingSigners := make([]common.Address, len(masternodes))
|
||||
copy(missingSigners, masternodes)
|
||||
|
||||
num := len(objs)
|
||||
for _, obj := range objs {
|
||||
signer := obj.GetSigner()
|
||||
currentSigners = append(currentSigners, signer)
|
||||
for i, mn := range missingSigners {
|
||||
if mn == signer {
|
||||
missingSigners = append(missingSigners[:i], missingSigners[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
message[name] = SignerTypes{
|
||||
CurrentNumber: num,
|
||||
CurrentSigners: currentSigners,
|
||||
MissingSigners: missingSigners,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
73
consensus/XDPoS/api_test.go
Normal file
73
consensus/XDPoS/api_test.go
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
package XDPoS
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCalculateSignersVote(t *testing.T) {
|
||||
|
||||
info := make(map[string]SignerTypes)
|
||||
votes := utils.NewPool()
|
||||
masternodes := []common.Address{{1}, {2}, {3}}
|
||||
|
||||
vote1 := types.Vote{
|
||||
Signature: types.Signature{1},
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.Hash{1},
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
GapNumber: 450,
|
||||
}
|
||||
vote1.SetSigner(common.Address{1})
|
||||
|
||||
vote2 := types.Vote{
|
||||
Signature: types.Signature{2},
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.Hash{1},
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
GapNumber: 450,
|
||||
}
|
||||
vote2.SetSigner(common.Address{2})
|
||||
|
||||
votes.Add(&vote1)
|
||||
votes.Add(&vote2)
|
||||
|
||||
calculateSigners(info, votes.Get(), masternodes)
|
||||
assert.Equal(t, info["10:450:910:0x0100000000000000000000000000000000000000000000000000000000000000"].CurrentNumber, 2)
|
||||
}
|
||||
|
||||
func TestCalculateSignersTimeout(t *testing.T) {
|
||||
|
||||
info := make(map[string]SignerTypes)
|
||||
timeouts := utils.NewPool()
|
||||
masternodes := []common.Address{{1}, {2}, {3}}
|
||||
|
||||
timeout1 := types.Timeout{
|
||||
Signature: types.Signature{1},
|
||||
Round: types.Round(10),
|
||||
GapNumber: 450,
|
||||
}
|
||||
timeout1.SetSigner(common.Address{1})
|
||||
|
||||
timeout2 := types.Timeout{
|
||||
Signature: types.Signature{2},
|
||||
Round: types.Round(10),
|
||||
GapNumber: 450,
|
||||
}
|
||||
timeout1.SetSigner(common.Address{2})
|
||||
|
||||
timeouts.Add(&timeout1)
|
||||
timeouts.Add(&timeout2)
|
||||
|
||||
calculateSigners(info, timeouts.Get(), masternodes)
|
||||
assert.Equal(t, info["10:450"].CurrentNumber, 2)
|
||||
}
|
||||
|
|
@ -28,34 +28,18 @@ import (
|
|||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// ecrecover extracts the Ethereum account address from a signed header.
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
// Retrieve the signature from the header extra-data
|
||||
if len(header.Extra) < utils.ExtraSeal {
|
||||
return common.Address{}, utils.ErrMissingSignature
|
||||
}
|
||||
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), signature)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
}
|
||||
const (
|
||||
// timeout waiting for M1
|
||||
minePeriod = 10
|
||||
// timeout for checkpoint.
|
||||
minePeriodCheckpoint = 20
|
||||
)
|
||||
|
||||
// XDPoS is the delegated-proof-of-stake consensus engine proposed to support the
|
||||
// Ethereum testnet following the Ropsten attacks.
|
||||
type XDPoS_v1 struct {
|
||||
chainConfig *params.ChainConfig // Chain & network configuration
|
||||
|
||||
config *params.XDPoSConfig // Consensus engine configuration parameters
|
||||
db ethdb.Database // Database to store and retrieve snapshot checkpoints
|
||||
|
||||
|
|
@ -78,9 +62,29 @@ type XDPoS_v1 struct {
|
|||
HookGetSignersFromContract func(blockHash common.Hash) ([]common.Address, error)
|
||||
}
|
||||
|
||||
/*
|
||||
V1 Block
|
||||
|
||||
SignerFn is a signer callback function to request a hash to be signed by a
|
||||
backing account.
|
||||
type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
||||
|
||||
sigHash returns the hash which is used as input for the delegated-proof-of-stake
|
||||
signing. It is the hash of the entire header apart from the 65 byte signature
|
||||
contained at the end of the extra data.
|
||||
|
||||
Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
||||
panics. This is done to avoid accidentally using both forms (signature present
|
||||
or not), which could be abused to produce different hashes for the same header.
|
||||
*/
|
||||
func (x *XDPoS_v1) SigHash(header *types.Header) (hash common.Hash) {
|
||||
return sigHash(header)
|
||||
}
|
||||
|
||||
// New creates a XDPoS delegated-proof-of-stake consensus engine with the initial
|
||||
// signers set to the ones provided by the user.
|
||||
func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
|
||||
func New(chainConfig *params.ChainConfig, db ethdb.Database) *XDPoS_v1 {
|
||||
config := chainConfig.XDPoS
|
||||
// Set any missing consensus parameters to their defaults
|
||||
conf := *config
|
||||
if conf.Epoch == 0 {
|
||||
|
|
@ -92,6 +96,8 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS_v1 {
|
|||
validatorSignatures, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
return &XDPoS_v1{
|
||||
chainConfig: chainConfig,
|
||||
|
||||
config: &conf,
|
||||
db: db,
|
||||
|
||||
|
|
@ -117,10 +123,7 @@ func (x *XDPoS_v1) VerifyHeader(chain consensus.ChainReader, header *types.Heade
|
|||
// VerifyHeaders is similar to VerifyHeader, but verifies a batch of headers. The
|
||||
// method returns a quit channel to abort the operations and a results channel to
|
||||
// retrieve the async verifications (the order is that of the input slice).
|
||||
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool) (chan<- struct{}, <-chan error) {
|
||||
abort := make(chan struct{})
|
||||
results := make(chan error, len(headers))
|
||||
|
||||
func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, fullVerifies []bool, abort <-chan struct{}, results chan<- error) {
|
||||
go func() {
|
||||
for i, header := range headers {
|
||||
err := x.verifyHeaderWithCache(chain, header, headers[:i], fullVerifies[i])
|
||||
|
|
@ -132,7 +135,6 @@ func (x *XDPoS_v1) VerifyHeaders(chain consensus.ChainReader, headers []*types.H
|
|||
}
|
||||
}
|
||||
}()
|
||||
return abort, results
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
|
|
@ -153,7 +155,7 @@ func (x *XDPoS_v1) verifyHeaderWithCache(chain consensus.ChainReader, header *ty
|
|||
// a batch of new headers.
|
||||
func (x *XDPoS_v1) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
// If we're running a engine faking, accept any block as valid
|
||||
if x.config.SkipValidation {
|
||||
if x.config.SkipV1Validation {
|
||||
return nil
|
||||
}
|
||||
if common.IsTestnet {
|
||||
|
|
@ -321,7 +323,7 @@ func (x *XDPoS_v1) checkSignersOnCheckpoint(chain consensus.ChainReader, header
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) IsAuthorisedAddress(header *types.Header, chain consensus.ChainReader, address common.Address) bool {
|
||||
func (x *XDPoS_v1) IsAuthorisedAddress(chain consensus.ChainReader, header *types.Header, address common.Address) bool {
|
||||
snap, err := x.GetSnapshot(chain, header)
|
||||
if err != nil {
|
||||
log.Error("[IsAuthorisedAddress] Can't get snapshot with at ", "number", header.Number, "hash", header.Hash().Hex(), "err", err)
|
||||
|
|
@ -355,32 +357,33 @@ func (x *XDPoS_v1) StoreSnapshot(snap *SnapshotV1) error {
|
|||
return snap.store(x.db)
|
||||
}
|
||||
|
||||
func position(list []common.Address, x common.Address) int {
|
||||
for i, item := range list {
|
||||
if item == x {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetMasternodes(chain consensus.ChainReader, header *types.Header) []common.Address {
|
||||
n := header.Number.Uint64()
|
||||
e := x.config.Epoch
|
||||
switch {
|
||||
case n%e == 0:
|
||||
return x.GetMasternodesFromCheckpointHeader(header, n, e)
|
||||
return x.GetMasternodesFromCheckpointHeader(header)
|
||||
case n%e != 0:
|
||||
h := chain.GetHeaderByNumber(n - (n % e))
|
||||
return x.GetMasternodesFromCheckpointHeader(h, n, e)
|
||||
if h == nil {
|
||||
log.Warn("[GetMasternodes v1] epoch switch block header nil", "BlockNum", n)
|
||||
}
|
||||
return x.GetMasternodesFromCheckpointHeader(h)
|
||||
default:
|
||||
return []common.Address{}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetCurrentEpochSwitchBlock(blockNumber *big.Int) (uint64, uint64, error) {
|
||||
currentBlockNum := blockNumber.Uint64()
|
||||
currentCheckpointNumber := currentBlockNum - currentBlockNum%x.config.Epoch
|
||||
epochNumber := currentBlockNum / x.config.Epoch
|
||||
return currentCheckpointNumber, epochNumber, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetPeriod() uint64 { return x.config.Period }
|
||||
|
||||
func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
|
||||
func (x *XDPoS_v1) whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error) {
|
||||
if header.Number.Uint64() == 0 {
|
||||
return common.Address{}, errors.New("Don't take block 0")
|
||||
}
|
||||
|
|
@ -390,8 +393,41 @@ func whoIsCreator(snap *SnapshotV1, header *types.Header) (common.Address, error
|
|||
}
|
||||
return m, nil
|
||||
}
|
||||
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (bool, error) {
|
||||
len, preIndex, curIndex, ok, err := x.yourTurn(chain, parent, signer)
|
||||
|
||||
func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
if err != nil {
|
||||
log.Warn("Failed when trying to commit new work", "err", err)
|
||||
return false, err
|
||||
}
|
||||
if !ok {
|
||||
// in case some nodes are down
|
||||
if preIndex == -1 {
|
||||
// first block
|
||||
return false, nil
|
||||
}
|
||||
if curIndex == -1 {
|
||||
// you're not allowed to create this block
|
||||
return false, nil
|
||||
}
|
||||
h := utils.Hop(len, preIndex, curIndex)
|
||||
gap := minePeriod * int64(h)
|
||||
// Check nearest checkpoint block in hop range.
|
||||
nearest := x.config.Epoch - (parent.Number.Uint64() % x.config.Epoch)
|
||||
if uint64(h) >= nearest {
|
||||
gap = minePeriodCheckpoint * int64(h)
|
||||
}
|
||||
log.Info("Distance from the parent block", "seconds", gap, "hops", h)
|
||||
waitedTime := time.Now().Unix() - parent.Time.Int64()
|
||||
if gap > waitedTime {
|
||||
return false, nil
|
||||
}
|
||||
log.Info("Wait enough. It's my turn", "waited seconds", waitedTime)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) yourTurn(chain consensus.ChainReader, parent *types.Header, signer common.Address) (int, int, int, bool, error) {
|
||||
masternodes := x.GetMasternodes(chain, parent)
|
||||
|
||||
// if common.IsTestnet {
|
||||
|
|
@ -415,13 +451,13 @@ func (x *XDPoS_v1) YourTurn(chain consensus.ChainReader, parent *types.Header, s
|
|||
// masternode[0] has chance to create block 1
|
||||
preIndex := -1
|
||||
if parent.Number.Uint64() != 0 {
|
||||
pre, err = whoIsCreator(snap, parent)
|
||||
pre, err = x.whoIsCreator(snap, parent)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false, err
|
||||
}
|
||||
preIndex = position(masternodes, pre)
|
||||
preIndex = utils.Position(masternodes, pre)
|
||||
}
|
||||
curIndex := position(masternodes, signer)
|
||||
curIndex := utils.Position(masternodes, signer)
|
||||
if signer == x.signer {
|
||||
log.Debug("Masternodes cycle info", "number of masternodes", len(masternodes), "previous", pre, "position", preIndex, "current", signer, "position", curIndex)
|
||||
}
|
||||
|
|
@ -643,7 +679,7 @@ func (x *XDPoS_v1) GetValidator(creator common.Address, chain consensus.ChainRea
|
|||
return common.Address{}, fmt.Errorf("couldn't find checkpoint header")
|
||||
}
|
||||
}
|
||||
m, err := GetM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
|
||||
m, err := getM1M2FromCheckpointHeader(cpHeader, header, chain.Config())
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
|
@ -749,7 +785,20 @@ func (x *XDPoS_v1) Prepare(chain consensus.ChainReader, header *types.Header) er
|
|||
return nil
|
||||
}
|
||||
|
||||
// Update masternodes into snapshot. In V1, truncating ms[:MaxMasternodes] is done in this function.
|
||||
func (x *XDPoS_v1) UpdateMasternodes(chain consensus.ChainReader, header *types.Header, ms []utils.Masternode) error {
|
||||
var maxMasternodes int
|
||||
// check if block number is increase ms checkpoint
|
||||
if x.chainConfig.IsTIPIncreaseMasternodes(header.Number) || (x.config.V2.SwitchBlock != nil && header.Number.Cmp(x.config.V2.SwitchBlock) == 1) {
|
||||
// using new masterndoes
|
||||
maxMasternodes = common.MaxMasternodesV2
|
||||
} else {
|
||||
// using old masterndoes
|
||||
maxMasternodes = common.MaxMasternodes
|
||||
}
|
||||
if len(ms) > maxMasternodes {
|
||||
ms = ms[:maxMasternodes]
|
||||
}
|
||||
number := header.Number.Uint64()
|
||||
log.Trace("take snapshot", "number", number, "hash", header.Hash())
|
||||
// get snapshot
|
||||
|
|
@ -876,7 +925,7 @@ func (x *XDPoS_v1) Seal(chain consensus.ChainReader, block *types.Block, stop <-
|
|||
default:
|
||||
}
|
||||
// Sign all the things!
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, utils.SigHash(header).Bytes())
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, x.SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -900,10 +949,10 @@ func (x *XDPoS_v1) CalcDifficulty(chain consensus.ChainReader, time uint64, pare
|
|||
|
||||
func (x *XDPoS_v1) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
|
||||
// If we're running a engine faking, skip calculation
|
||||
if x.config.SkipValidation {
|
||||
if x.config.SkipV1Validation {
|
||||
return big.NewInt(1)
|
||||
}
|
||||
len, preIndex, curIndex, _, err := x.YourTurn(chain, parent, signer)
|
||||
len, preIndex, curIndex, _, err := x.yourTurn(chain, parent, signer)
|
||||
if err != nil {
|
||||
return big.NewInt(int64(len + curIndex - preIndex))
|
||||
}
|
||||
|
|
@ -926,7 +975,7 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
|
|||
return common.Address{}, consensus.ErrFailValidatorSignature
|
||||
}
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(utils.SigHash(header).Bytes(), header.Validator)
|
||||
pubkey, err := crypto.Ecrecover(x.SigHash(header).Bytes(), header.Validator)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
|
@ -937,18 +986,13 @@ func (x *XDPoS_v1) RecoverValidator(header *types.Header) (common.Address, error
|
|||
return signer, nil
|
||||
}
|
||||
|
||||
// Get master nodes over extra data of previous checkpoint block.
|
||||
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(preCheckpointHeader *types.Header, n, e uint64) []common.Address {
|
||||
if preCheckpointHeader == nil {
|
||||
log.Info("Previous checkpoint's header is empty", "block number", n, "epoch", e)
|
||||
// Get master nodes over extra data of checkpoint block.
|
||||
func (x *XDPoS_v1) GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
if checkpointHeader == nil {
|
||||
log.Warn("Checkpoint's header is empty", "Header", checkpointHeader)
|
||||
return []common.Address{}
|
||||
}
|
||||
masternodes := make([]common.Address, (len(preCheckpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], preCheckpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
|
||||
return masternodes
|
||||
return decodeMasternodesFromHeaderExtra(checkpointHeader)
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) GetDb() ethdb.Database {
|
||||
|
|
@ -970,30 +1014,6 @@ func removePenaltiesFromBlock(chain consensus.ChainReader, masternodes []common.
|
|||
return masternodes
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := utils.GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpointHeader *types.Header) ([]common.Address, error) {
|
||||
startGapBlockHeader := checkpointHeader
|
||||
number := checkpointHeader.Number.Uint64()
|
||||
|
|
@ -1007,10 +1027,10 @@ func (x *XDPoS_v1) getSignersFromContract(chain consensus.ChainReader, checkpoin
|
|||
return signers, nil
|
||||
}
|
||||
|
||||
func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
||||
func NewFaker(db ethdb.Database, chainConfig *params.ChainConfig) *XDPoS_v1 {
|
||||
var fakeEngine *XDPoS_v1
|
||||
// Set any missing consensus parameters to their defaults
|
||||
conf := config
|
||||
conf := chainConfig.XDPoS
|
||||
|
||||
// Allocate the snapshot caches and create the engine
|
||||
recents, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
|
|
@ -1018,6 +1038,8 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
|||
validatorSignatures, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
verifiedHeaders, _ := lru.NewARC(utils.InmemorySnapshots)
|
||||
fakeEngine = &XDPoS_v1{
|
||||
chainConfig: chainConfig,
|
||||
|
||||
config: conf,
|
||||
db: db,
|
||||
recents: recents,
|
||||
|
|
@ -1028,3 +1050,10 @@ func NewFaker(db ethdb.Database, config *params.XDPoSConfig) *XDPoS_v1 {
|
|||
}
|
||||
return fakeEngine
|
||||
}
|
||||
|
||||
// Epoch Switch is also known as checkpoint in v1
|
||||
func (x *XDPoS_v1) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
epochNumber := header.Number.Uint64() / x.config.Epoch
|
||||
blockNumInEpoch := header.Number.Uint64() % x.config.Epoch
|
||||
return blockNumInEpoch == 0, epochNumber, nil
|
||||
}
|
||||
|
|
|
|||
112
consensus/XDPoS/engines/engine_v1/utils.go
Normal file
112
consensus/XDPoS/engines/engine_v1/utils.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package engine_v1
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func getM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := decodeMasternodesFromHeaderExtra(checkpointHeader)
|
||||
validators := utils.ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := getM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func getM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) {
|
||||
m1m2 := map[common.Address]common.Address{}
|
||||
maxMNs := len(masternodes)
|
||||
moveM2 := uint64(0)
|
||||
if len(validators) < maxMNs {
|
||||
return nil, moveM2, errors.New("len(m2) is less than len(m1)")
|
||||
}
|
||||
if maxMNs > 0 {
|
||||
isForked := config.IsTIPRandomize(currentHeader.Number)
|
||||
if isForked {
|
||||
moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs)
|
||||
}
|
||||
for i, m1 := range masternodes {
|
||||
m2Index := uint64(validators[i] % int64(maxMNs))
|
||||
m2Index = (m2Index + moveM2) % uint64(maxMNs)
|
||||
m1m2[m1] = masternodes[m2Index]
|
||||
}
|
||||
}
|
||||
return m1m2, moveM2, nil
|
||||
}
|
||||
|
||||
func sigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
// ecrecover extracts the Ethereum account address from a signed header.
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
// Retrieve the signature from the header extra-data
|
||||
if len(header.Extra) < utils.ExtraSeal {
|
||||
return common.Address{}, utils.ErrMissingSignature
|
||||
}
|
||||
signature := header.Extra[len(header.Extra)-utils.ExtraSeal:]
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), signature)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
}
|
||||
49
consensus/XDPoS/engines/engine_v1/utils_test.go
Normal file
49
consensus/XDPoS/engines/engine_v1/utils_test.go
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
package engine_v1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
func TestGetM1M2FromCheckpointHeader(t *testing.T) {
|
||||
masternodes := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc"),
|
||||
}
|
||||
validators := []int64{
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
}
|
||||
epoch := uint64(900)
|
||||
config := ¶ms.ChainConfig{
|
||||
XDPoS: ¶ms.XDPoSConfig{
|
||||
Epoch: uint64(epoch),
|
||||
},
|
||||
}
|
||||
testMoveM2 := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2}
|
||||
//try from block 3410001 to 3410018
|
||||
for i := uint64(3464001); i <= 3464018; i++ {
|
||||
currentNumber := int64(i)
|
||||
currentHeader := &types.Header{
|
||||
Number: big.NewInt(currentNumber),
|
||||
}
|
||||
m1m2, moveM2, err := getM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
t.Error("can't get m1m2", "err", err)
|
||||
}
|
||||
fmt.Printf("block: %v, moveM2: %v\n", currentHeader.Number.Int64(), moveM2)
|
||||
for _, k := range masternodes {
|
||||
fmt.Printf("m1: %v - m2: %v\n", k.Str(), m1m2[k].Str())
|
||||
}
|
||||
if moveM2 != testMoveM2[i-3464001] {
|
||||
t.Error("wrong moveM2", "currentNumber", currentNumber, "want", testMoveM2[i-3464001], "have", moveM2)
|
||||
}
|
||||
}
|
||||
}
|
||||
14
consensus/XDPoS/engines/engine_v2/difficulty.go
Normal file
14
consensus/XDPoS/engines/engine_v2/difficulty.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
// TODO: what should be new difficulty
|
||||
func (x *XDPoS_v2) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int {
|
||||
return big.NewInt(1)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
158
consensus/XDPoS/engines/engine_v2/epochSwitch.go
Normal file
158
consensus/XDPoS/engines/engine_v2/epochSwitch.go
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// get epoch switch of the previous `limit` epoch
|
||||
func (x *XDPoS_v2) getPreviousEpochSwitchInfoByHash(chain consensus.ChainReader, hash common.Hash, limit int) (*types.EpochSwitchInfo, error) {
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, hash)
|
||||
if err != nil {
|
||||
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
epochSwitchInfo, err = x.getEpochSwitchInfo(chain, nil, epochSwitchInfo.EpochSwitchParentBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[getPreviousEpochSwitchInfoByHash] Adaptor v2 getEpochSwitchInfo has error, potentially bug", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
|
||||
// Given header and its hash, get epoch switch info from the epoch switch block of that epoch,
|
||||
// header is allow to be nil.
|
||||
func (x *XDPoS_v2) getEpochSwitchInfo(chain consensus.ChainReader, header *types.Header, hash common.Hash) (*types.EpochSwitchInfo, error) {
|
||||
e, ok := x.epochSwitches.Get(hash)
|
||||
if ok {
|
||||
epochSwitchInfo := e.(*types.EpochSwitchInfo)
|
||||
log.Debug("[getEpochSwitchInfo] cache hit", "number", epochSwitchInfo.EpochSwitchBlockInfo.Number, "hash", hash.Hex())
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
h := header
|
||||
if h == nil {
|
||||
log.Debug("[getEpochSwitchInfo] header doesn't provide, get header by hash", "hash", hash.Hex())
|
||||
h = chain.GetHeaderByHash(hash)
|
||||
if h == nil {
|
||||
log.Warn("[getEpochSwitchInfo] can not find header from db", "hash", hash.Hex())
|
||||
return nil, fmt.Errorf("[getEpochSwitchInfo] can not find header from db hash %v", hash.Hex())
|
||||
}
|
||||
}
|
||||
isEpochSwitch, _, err := x.IsEpochSwitch(h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
log.Debug("[getEpochSwitchInfo] header is epoch switch", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
quorumCert, round, masternodes, err := x.getExtraFields(h)
|
||||
if err != nil {
|
||||
log.Error("[getEpochSwitchInfo] get extra field", "err", err, "number", h.Number.Uint64())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snap, err := x.getSnapshot(chain, h.Number.Uint64(), false)
|
||||
if err != nil {
|
||||
log.Error("[getEpochSwitchInfo] Adaptor v2 getSnapshot has error", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
penalties := common.ExtractAddressFromBytes(h.Penalties)
|
||||
candidates := snap.NextEpochMasterNodes
|
||||
standbynodes := []common.Address{}
|
||||
if len(masternodes) != len(candidates) {
|
||||
standbynodes = candidates
|
||||
standbynodes = common.RemoveItemFromArray(standbynodes, masternodes)
|
||||
standbynodes = common.RemoveItemFromArray(standbynodes, penalties)
|
||||
}
|
||||
|
||||
epochSwitchInfo := &types.EpochSwitchInfo{
|
||||
Penalties: penalties,
|
||||
Standbynodes: standbynodes,
|
||||
Masternodes: masternodes,
|
||||
EpochSwitchBlockInfo: &types.BlockInfo{
|
||||
Hash: hash,
|
||||
Number: h.Number,
|
||||
Round: round,
|
||||
},
|
||||
}
|
||||
if quorumCert != nil {
|
||||
epochSwitchInfo.EpochSwitchParentBlockInfo = quorumCert.ProposedBlockInfo
|
||||
}
|
||||
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, nil, h.ParentHash)
|
||||
if err != nil {
|
||||
log.Error("[getEpochSwitchInfo] recursive error", "err", err, "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("[getEpochSwitchInfo] get epoch switch info recursively", "hash", hash.Hex(), "number", h.Number.Uint64())
|
||||
x.epochSwitches.Add(hash, epochSwitchInfo)
|
||||
return epochSwitchInfo, nil
|
||||
}
|
||||
|
||||
// IsEpochSwitchAtRound() is used by miner to check whether it mines a block in the same epoch with parent
|
||||
func (x *XDPoS_v2) isEpochSwitchAtRound(round types.Round, parentHeader *types.Header) (bool, uint64, error) {
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
|
||||
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
|
||||
if parentHeader.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
return true, epochNum, nil
|
||||
}
|
||||
|
||||
_, parentRound, _, err := x.getExtraFields(parentHeader)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", parentHeader, "extra", common.Bytes2Hex(parentHeader.Extra))
|
||||
return false, 0, err
|
||||
}
|
||||
if round <= parentRound {
|
||||
// this round is no larger than parentRound, should return false
|
||||
return false, epochNum, nil
|
||||
}
|
||||
|
||||
epochStartRound := round - round%types.Round(x.config.Epoch)
|
||||
return parentRound < epochStartRound, epochNum, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetCurrentEpochSwitchBlock(chain consensus.ChainReader, blockNum *big.Int) (uint64, uint64, error) {
|
||||
header := chain.GetHeaderByNumber(blockNum.Uint64())
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, header, header.Hash())
|
||||
if err != nil {
|
||||
log.Error("[GetCurrentEpochSwitchBlock] Fail to get epoch switch info", "Num", header.Number, "Hash", header.Hash())
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
currentCheckpointNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(epochSwitchInfo.EpochSwitchBlockInfo.Round)/x.config.Epoch
|
||||
return currentCheckpointNumber, epochNum, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) IsEpochSwitch(header *types.Header) (bool, uint64, error) {
|
||||
// Return true directly if we are examing the last v1 block. This could happen if the calling function is examing parent block
|
||||
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
log.Info("[IsEpochSwitch] examing last v1 block")
|
||||
return true, header.Number.Uint64() / x.config.Epoch, nil
|
||||
}
|
||||
|
||||
quorumCert, round, _, err := x.getExtraFields(header)
|
||||
if err != nil {
|
||||
log.Error("[IsEpochSwitch] decode header error", "err", err, "header", header, "extra", common.Bytes2Hex(header.Extra))
|
||||
return false, 0, err
|
||||
}
|
||||
parentRound := quorumCert.ProposedBlockInfo.Round
|
||||
epochStartRound := round - round%types.Round(x.config.Epoch)
|
||||
epochNum := x.config.V2.SwitchBlock.Uint64()/x.config.Epoch + uint64(round)/x.config.Epoch
|
||||
// if parent is last v1 block and this is first v2 block, this is treated as epoch switch
|
||||
if quorumCert.ProposedBlockInfo.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
log.Info("[IsEpochSwitch] true, parent equals V2.SwitchBlock", "round", round, "number", header.Number.Uint64(), "hash", header.Hash())
|
||||
return true, epochNum, nil
|
||||
}
|
||||
log.Debug("[IsEpochSwitch]", "is", parentRound < epochStartRound, "parentRound", parentRound, "round", round, "number", header.Number.Uint64(), "epochNum", epochNum, "hash", header.Hash())
|
||||
return parentRound < epochStartRound, epochNum, nil
|
||||
}
|
||||
562
consensus/XDPoS/engines/engine_v2/forensics.go
Normal file
562
consensus/XDPoS/engines/engine_v2/forensics.go
Normal file
|
|
@ -0,0 +1,562 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/event"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
const (
|
||||
NUM_OF_FORENSICS_QC = 3
|
||||
)
|
||||
|
||||
// Forensics instance. Placeholder for future properties to be added
|
||||
type Forensics struct {
|
||||
HighestCommittedQCs []types.QuorumCert
|
||||
forensicsFeed event.Feed
|
||||
scope event.SubscriptionScope
|
||||
}
|
||||
|
||||
// Initiate a forensics process
|
||||
func NewForensics() *Forensics {
|
||||
return &Forensics{}
|
||||
}
|
||||
|
||||
// SubscribeForensicsEvent registers a subscription of ForensicsEvent and
|
||||
// starts sending event to the given channel.
|
||||
func (f *Forensics) SubscribeForensicsEvent(ch chan<- types.ForensicsEvent) event.Subscription {
|
||||
return f.scope.Track(f.forensicsFeed.Subscribe(ch))
|
||||
}
|
||||
|
||||
func (f *Forensics) ForensicsMonitoring(chain consensus.ChainReader, engine *XDPoS_v2, headerQcToBeCommitted []types.Header, incomingQC types.QuorumCert) error {
|
||||
f.ProcessForensics(chain, engine, incomingQC)
|
||||
return f.SetCommittedQCs(headerQcToBeCommitted, incomingQC)
|
||||
}
|
||||
|
||||
// Set the forensics committed QCs list. The order is from grandparent to current header. i.e it shall follow the QC in its header as follow [hcqc1, hcqc2, hcqc3]
|
||||
func (f *Forensics) SetCommittedQCs(headers []types.Header, incomingQC types.QuorumCert) error {
|
||||
// highestCommitQCs is an array, assign the parentBlockQc and its child as well as its grandchild QC into this array for forensics purposes.
|
||||
if len(headers) != NUM_OF_FORENSICS_QC-1 {
|
||||
log.Error("[SetCommittedQcs] Received input length not equal to 2", len(headers))
|
||||
return fmt.Errorf("received headers length not equal to 2 ")
|
||||
}
|
||||
|
||||
var committedQCs []types.QuorumCert
|
||||
for i, h := range headers {
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(h.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[SetCommittedQCs] Fail to decode extra when committing QC to forensics", "err", err, "index", i)
|
||||
return err
|
||||
}
|
||||
if i != 0 {
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Hash != headers[i-1].Hash() {
|
||||
log.Error("[SetCommittedQCs] Headers shall be on the same chain and in the right order", "parentHash", h.ParentHash.Hex(), "headers[i-1].Hash()", headers[i-1].Hash().Hex())
|
||||
return fmt.Errorf("headers shall be on the same chain and in the right order")
|
||||
} else if i == len(headers)-1 { // The last header shall be pointed by the incoming QC
|
||||
if incomingQC.ProposedBlockInfo.Hash != h.Hash() {
|
||||
log.Error("[SetCommittedQCs] incomingQc is not pointing at the last header received", "hash", h.Hash().Hex(), "incomingQC.ProposedBlockInfo.Hash", incomingQC.ProposedBlockInfo.Hash.Hex())
|
||||
return fmt.Errorf("incomingQc is not pointing at the last header received")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
committedQCs = append(committedQCs, *decodedExtraField.QuorumCert)
|
||||
}
|
||||
f.HighestCommittedQCs = append(committedQCs, incomingQC)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Entry point for processing forensics.
|
||||
Triggered once processQC is successfully.
|
||||
Forensics runs in a seperate go routine as its no system critical
|
||||
Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/97878029/Forensics+Diagram+flow
|
||||
*/
|
||||
func (f *Forensics) ProcessForensics(chain consensus.ChainReader, engine *XDPoS_v2, incomingQC types.QuorumCert) error {
|
||||
log.Debug("Received a QC in forensics", "QC", incomingQC)
|
||||
// Clone the values to a temporary variable
|
||||
highestCommittedQCs := f.HighestCommittedQCs
|
||||
if len(highestCommittedQCs) != NUM_OF_FORENSICS_QC {
|
||||
log.Error("[ProcessForensics] HighestCommittedQCs value not set", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round)
|
||||
return fmt.Errorf("HighestCommittedQCs value not set")
|
||||
}
|
||||
// Find the QC1 and QC2. We only care 2 parents in front of the incomingQC. The returned value contains QC1, QC2 and QC3(the incomingQC)
|
||||
incomingQuorunCerts, err := f.findAncestorQCs(chain, incomingQC, 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
isOnTheChain, err := f.checkQCsOnTheSameChain(chain, highestCommittedQCs, incomingQuorunCerts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isOnTheChain {
|
||||
// Passed the checking, nothing suspecious.
|
||||
log.Debug("[ProcessForensics] Passed forensics checking, nothing suspecious need to be reported", "incomingQcProposedBlockHash", incomingQC.ProposedBlockInfo.Hash, "incomingQcProposedBlockNumber", incomingQC.ProposedBlockInfo.Number.Uint64(), "incomingQcProposedBlockRound", incomingQC.ProposedBlockInfo.Round)
|
||||
return nil
|
||||
}
|
||||
// Trigger the safety Alarm if failed
|
||||
// First, find the QC in the two sets that have the same round
|
||||
foundSameRoundQC, sameRoundHCQC, sameRoundQC := f.findQCsInSameRound(highestCommittedQCs, incomingQuorunCerts)
|
||||
|
||||
if foundSameRoundQC {
|
||||
f.SendForensicProof(chain, engine, sameRoundHCQC, sameRoundQC)
|
||||
} else {
|
||||
// Not found, need a more complex approach to find the two QC
|
||||
ancestorQC, lowerRoundQCs, _, err := f.findAncestorQcThroughRound(chain, highestCommittedQCs, incomingQuorunCerts)
|
||||
if err != nil {
|
||||
log.Error("[ProcessForensics] Error while trying to find ancestor QC through round number", "err", err)
|
||||
}
|
||||
f.SendForensicProof(chain, engine, ancestorQC, lowerRoundQCs[NUM_OF_FORENSICS_QC-1])
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Last step of forensics which sends out detailed proof to report service.
|
||||
func (f *Forensics) SendForensicProof(chain consensus.ChainReader, engine *XDPoS_v2, firstQc types.QuorumCert, secondQc types.QuorumCert) error {
|
||||
// Re-order the QC by its round number to make the function cleaner.
|
||||
lowerRoundQC := firstQc
|
||||
higherRoundQC := secondQc
|
||||
|
||||
if secondQc.ProposedBlockInfo.Round < firstQc.ProposedBlockInfo.Round {
|
||||
lowerRoundQC = secondQc
|
||||
higherRoundQC = firstQc
|
||||
}
|
||||
|
||||
// Find common ancestor block
|
||||
ancestorHash, ancestorToLowerRoundPath, ancestorToHigherRoundPath, err := f.FindAncestorBlockHash(chain, lowerRoundQC.ProposedBlockInfo, higherRoundQC.ProposedBlockInfo)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Error while trying to find ancestor block hash", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if two QCs are across epoch, this is used as a indicator for the "prone to attack" scenario
|
||||
lowerRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, lowerRoundQC.ProposedBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Errir while trying to find lowerRoundQcEpochSwitchInfo", "lowerRoundQC.ProposedBlockInfo.Hash", lowerRoundQC.ProposedBlockInfo.Hash, "err", err)
|
||||
return err
|
||||
}
|
||||
higherRoundQcEpochSwitchInfo, err := engine.getEpochSwitchInfo(chain, nil, higherRoundQC.ProposedBlockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] Errir while trying to find higherRoundQcEpochSwitchInfo", "higherRoundQC.ProposedBlockInfo.Hash", higherRoundQC.ProposedBlockInfo.Hash, "err", err)
|
||||
return err
|
||||
}
|
||||
accrossEpoches := false
|
||||
if lowerRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash != higherRoundQcEpochSwitchInfo.EpochSwitchBlockInfo.Hash {
|
||||
accrossEpoches = true
|
||||
}
|
||||
|
||||
ancestorBlock := chain.GetHeaderByHash(ancestorHash)
|
||||
|
||||
if ancestorBlock == nil {
|
||||
log.Error("[SendForensicProof] Unable to find the ancestor block by its hash", "Hash", ancestorHash)
|
||||
return fmt.Errorf("Can't find ancestor block via hash")
|
||||
}
|
||||
|
||||
content, err := json.Marshal(&types.ForensicsContent{
|
||||
DivergingBlockHash: ancestorHash.Hex(),
|
||||
AcrossEpoch: accrossEpoches,
|
||||
DivergingBlockNumber: ancestorBlock.Number.Uint64(),
|
||||
SmallerRoundInfo: &types.ForensicsInfo{
|
||||
HashPath: ancestorToLowerRoundPath,
|
||||
QuorumCert: lowerRoundQC,
|
||||
SignerAddresses: f.getQcSignerAddresses(lowerRoundQC),
|
||||
},
|
||||
LargerRoundInfo: &types.ForensicsInfo{
|
||||
HashPath: ancestorToHigherRoundPath,
|
||||
QuorumCert: higherRoundQC,
|
||||
SignerAddresses: f.getQcSignerAddresses(higherRoundQC),
|
||||
},
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error("[SendForensicProof] fail to json stringify forensics content", "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
forensicsProof := &types.ForensicProof{
|
||||
Id: generateForensicsId(ancestorHash.Hex(), &lowerRoundQC, &higherRoundQC),
|
||||
ForensicsType: "QC",
|
||||
Content: string(content),
|
||||
}
|
||||
log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof)
|
||||
go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Utils function to help find the n-th previous QC. It returns an array of QC in ascending order including the currentQc as the last item in the array
|
||||
func (f *Forensics) findAncestorQCs(chain consensus.ChainReader, currentQc types.QuorumCert, distanceFromCurrrentQc int) ([]types.QuorumCert, error) {
|
||||
var quorumCerts []types.QuorumCert
|
||||
quorumCertificate := currentQc
|
||||
// Append the initial value
|
||||
quorumCerts = append(quorumCerts, quorumCertificate)
|
||||
// Append the parents
|
||||
for i := 0; i < distanceFromCurrrentQc; i++ {
|
||||
parentHash := quorumCertificate.ProposedBlockInfo.Hash
|
||||
parentHeader := chain.GetHeaderByHash(parentHash)
|
||||
if parentHeader == nil {
|
||||
log.Error("[findAncestorQCs] Forensics findAncestorQCs unable to find its parent block header", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex())
|
||||
return nil, fmt.Errorf("unable to find parent block header in forensics")
|
||||
}
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[findAncestorQCs] Error while trying to decode from parent block extra", "BlockNum", parentHeader.Number.Int64(), "ParentHash", parentHash.Hex())
|
||||
}
|
||||
quorumCertificate = *decodedExtraField.QuorumCert
|
||||
quorumCerts = append(quorumCerts, quorumCertificate)
|
||||
}
|
||||
// The quorumCerts is in the reverse order, we need to flip it
|
||||
var quorumCertsInAscendingOrder []types.QuorumCert
|
||||
for i := len(quorumCerts) - 1; i >= 0; i-- {
|
||||
quorumCertsInAscendingOrder = append(quorumCertsInAscendingOrder, quorumCerts[i])
|
||||
}
|
||||
return quorumCertsInAscendingOrder, nil
|
||||
}
|
||||
|
||||
// Check whether two provided QC set are on the same chain
|
||||
func (f *Forensics) checkQCsOnTheSameChain(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (bool, error) {
|
||||
// Re-order two sets of QCs by block Number
|
||||
lowerBlockNumQCs := highestCommittedQCs
|
||||
higherBlockNumQCs := incomingQCandItsParents
|
||||
if incomingQCandItsParents[0].ProposedBlockInfo.Number.Cmp(highestCommittedQCs[0].ProposedBlockInfo.Number) == -1 {
|
||||
lowerBlockNumQCs = incomingQCandItsParents
|
||||
higherBlockNumQCs = highestCommittedQCs
|
||||
}
|
||||
|
||||
proposedBlockInfo := higherBlockNumQCs[0].ProposedBlockInfo
|
||||
for i := 0; i < int((big.NewInt(0).Sub(higherBlockNumQCs[0].ProposedBlockInfo.Number, lowerBlockNumQCs[0].ProposedBlockInfo.Number)).Int64()); i++ {
|
||||
parentHeader := chain.GetHeaderByHash(proposedBlockInfo.Hash)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentHeader.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[checkQCsOnTheSameChain] Fail to decode extra when checking the two QCs set on the same chain", "err", err)
|
||||
return false, err
|
||||
}
|
||||
proposedBlockInfo = decodedExtraField.QuorumCert.ProposedBlockInfo
|
||||
}
|
||||
// Check the final proposed blockInfo is the same as what we have from lowerBlockNumQCs[0]
|
||||
if reflect.DeepEqual(proposedBlockInfo, lowerBlockNumQCs[0].ProposedBlockInfo) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Given the two QCs set, find if there are any QC that have the same round
|
||||
func (f *Forensics) findQCsInSameRound(quorumCerts1 []types.QuorumCert, quorumCerts2 []types.QuorumCert) (bool, types.QuorumCert, types.QuorumCert) {
|
||||
for _, quorumCert1 := range quorumCerts1 {
|
||||
for _, quorumCert2 := range quorumCerts2 {
|
||||
if quorumCert1.ProposedBlockInfo.Round == quorumCert2.ProposedBlockInfo.Round {
|
||||
return true, quorumCert1, quorumCert2
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, types.QuorumCert{}, types.QuorumCert{}
|
||||
}
|
||||
|
||||
// Find the signer list from QC signatures
|
||||
func (f *Forensics) getQcSignerAddresses(quorumCert types.QuorumCert) []string {
|
||||
var signerList []string
|
||||
|
||||
// The QC signatures are signed by votes special struct VoteForSign
|
||||
quorumCertSignedHash := types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: quorumCert.ProposedBlockInfo,
|
||||
GapNumber: quorumCert.GapNumber,
|
||||
})
|
||||
for _, signature := range quorumCert.Signatures {
|
||||
var signerAddress common.Address
|
||||
pubkey, err := crypto.Ecrecover(quorumCertSignedHash.Bytes(), signature)
|
||||
if err != nil {
|
||||
log.Error("[getQcSignerAddresses] Fail to Ecrecover signer from the quorumCertSignedHash", "quorumCert.GapNumber", quorumCert.GapNumber, "quorumCert.ProposedBlockInfo", quorumCert.ProposedBlockInfo)
|
||||
}
|
||||
|
||||
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
signerList = append(signerList, signerAddress.Hex())
|
||||
}
|
||||
return signerList
|
||||
}
|
||||
|
||||
// Check whether the given QCs are on the same chain as the stored committed QCs(f.HighestCommittedQCs) regardless their orders
|
||||
func (f *Forensics) findAncestorQcThroughRound(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingQCandItsParents []types.QuorumCert) (types.QuorumCert, []types.QuorumCert, []types.QuorumCert, error) {
|
||||
/*
|
||||
Re-order two sets of QCs by Round number
|
||||
*/
|
||||
lowerRoundQCs := highestCommittedQCs
|
||||
higherRoundQCs := incomingQCandItsParents
|
||||
if incomingQCandItsParents[0].ProposedBlockInfo.Round < highestCommittedQCs[0].ProposedBlockInfo.Round {
|
||||
lowerRoundQCs = incomingQCandItsParents
|
||||
higherRoundQCs = highestCommittedQCs
|
||||
}
|
||||
|
||||
// Find the ancestorFromIncomingQC1 that matches round number < lowerRoundQCs3
|
||||
ancestorQC := higherRoundQCs[0]
|
||||
for ancestorQC.ProposedBlockInfo.Round >= lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
proposedBlock := chain.GetHeaderByHash(ancestorQC.ProposedBlockInfo.Hash)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(proposedBlock.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[findAncestorQcThroughRound] Error while trying to decode extra field", "ProposedBlockInfo.Hash", ancestorQC.ProposedBlockInfo.Hash)
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, err
|
||||
}
|
||||
// Found the ancestor QC
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Round < lowerRoundQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, nil
|
||||
}
|
||||
ancestorQC = *decodedExtraField.QuorumCert
|
||||
}
|
||||
return ancestorQC, lowerRoundQCs, higherRoundQCs, fmt.Errorf("[findAncestorQcThroughRound] Could not find ancestor QC")
|
||||
}
|
||||
|
||||
func (f *Forensics) FindAncestorBlockHash(chain consensus.ChainReader, firstBlockInfo *types.BlockInfo, secondBlockInfo *types.BlockInfo) (common.Hash, []string, []string, error) {
|
||||
// Re-arrange by block number
|
||||
lowerBlockNumHash := firstBlockInfo.Hash
|
||||
higherBlockNumberHash := secondBlockInfo.Hash
|
||||
|
||||
var lowerBlockNumToAncestorHashPath []string
|
||||
var higherBlockToAncestorNumHashPath []string
|
||||
orderSwapped := false
|
||||
|
||||
blockNumberDifference := big.NewInt(0).Sub(secondBlockInfo.Number, firstBlockInfo.Number).Int64()
|
||||
if blockNumberDifference < 0 {
|
||||
lowerBlockNumHash = secondBlockInfo.Hash
|
||||
higherBlockNumberHash = firstBlockInfo.Hash
|
||||
blockNumberDifference = -blockNumberDifference // and make it positive
|
||||
orderSwapped = true
|
||||
}
|
||||
lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex())
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex())
|
||||
|
||||
// First, make their block number the same to start with
|
||||
for i := 0; i < int(blockNumberDifference); i++ {
|
||||
ph := chain.GetHeaderByHash(higherBlockNumberHash)
|
||||
if ph == nil {
|
||||
return common.Hash{}, lowerBlockNumToAncestorHashPath, higherBlockToAncestorNumHashPath, fmt.Errorf("unable to find parent block of hash %v", higherBlockNumberHash)
|
||||
}
|
||||
higherBlockNumberHash = ph.ParentHash
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, ph.ParentHash.Hex())
|
||||
}
|
||||
|
||||
// Now, they are on the same starting line, we try find the common ancestor
|
||||
for lowerBlockNumHash.Hex() != higherBlockNumberHash.Hex() {
|
||||
lowerBlockNumHash = chain.GetHeaderByHash(lowerBlockNumHash).ParentHash
|
||||
higherBlockNumberHash = chain.GetHeaderByHash(higherBlockNumberHash).ParentHash
|
||||
// Append the path
|
||||
lowerBlockNumToAncestorHashPath = append(lowerBlockNumToAncestorHashPath, lowerBlockNumHash.Hex())
|
||||
higherBlockToAncestorNumHashPath = append(higherBlockToAncestorNumHashPath, higherBlockNumberHash.Hex())
|
||||
}
|
||||
|
||||
// Reverse the list order as it's from ancestor to X block path.
|
||||
ancestorToLowerBlockNumHashPath := reverse(lowerBlockNumToAncestorHashPath)
|
||||
ancestorToHigherBlockNumHashPath := reverse(higherBlockToAncestorNumHashPath)
|
||||
// Swap back the order. We must return in the order that matches what we acceptted in the parameter of firstBlock & secondBlock
|
||||
if orderSwapped {
|
||||
return lowerBlockNumHash, ancestorToHigherBlockNumHashPath, ancestorToLowerBlockNumHashPath, nil
|
||||
}
|
||||
return lowerBlockNumHash, ancestorToLowerBlockNumHashPath, ancestorToHigherBlockNumHashPath, nil
|
||||
}
|
||||
|
||||
func generateForensicsId(divergingHash string, qc1 *types.QuorumCert, qc2 *types.QuorumCert) string {
|
||||
keysList := []string{divergingHash, qc1.ProposedBlockInfo.Hash.Hex(), qc2.ProposedBlockInfo.Hash.Hex()}
|
||||
return strings.Join(keysList[:], ":")
|
||||
}
|
||||
|
||||
func reverse(ss []string) []string {
|
||||
last := len(ss) - 1
|
||||
for i := 0; i < len(ss)/2; i++ {
|
||||
ss[i], ss[last-i] = ss[last-i], ss[i]
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func generateVoteEquivocationId(signer common.Address, round1, round2 types.Round) string {
|
||||
return fmt.Sprintf("%x:%d:%d", signer, round1, round2)
|
||||
}
|
||||
|
||||
/*
|
||||
Entry point for processing vote equivocation.
|
||||
Triggered once handle vote is successfully.
|
||||
Forensics runs in a seperate go routine as its no system critical
|
||||
Link to the flow diagram: https://hashlabs.atlassian.net/wiki/spaces/HASHLABS/pages/99516417/Vote+Equivocation+detection+specification
|
||||
*/
|
||||
func (f *Forensics) ProcessVoteEquivocation(chain consensus.ChainReader, engine *XDPoS_v2, incomingVote *types.Vote) error {
|
||||
log.Debug("Received a vote in forensics", "vote", incomingVote)
|
||||
// Clone the values to a temporary variable
|
||||
highestCommittedQCs := f.HighestCommittedQCs
|
||||
if len(highestCommittedQCs) != NUM_OF_FORENSICS_QC {
|
||||
log.Error("[ProcessVoteEquivocation] HighestCommittedQCs value not set", "incomingVoteProposedBlockHash", incomingVote.ProposedBlockInfo.Hash, "incomingVoteProposedBlockNumber", incomingVote.ProposedBlockInfo.Number.Uint64(), "incomingVoteProposedBlockRound", incomingVote.ProposedBlockInfo.Round)
|
||||
return fmt.Errorf("HighestCommittedQCs value not set")
|
||||
}
|
||||
if incomingVote.ProposedBlockInfo.Round < highestCommittedQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
log.Debug("Received a too old vote in forensics", "vote", incomingVote)
|
||||
return nil
|
||||
}
|
||||
// is vote extending committed block
|
||||
isOnTheChain, err := f.isExtendingFromAncestor(chain, incomingVote.ProposedBlockInfo, highestCommittedQCs[0].ProposedBlockInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isOnTheChain {
|
||||
// Passed the checking, nothing suspecious.
|
||||
log.Debug("[ProcessVoteEquivocation] Passed forensics checking, nothing suspecious need to be reported", "incomingVoteProposedBlockHash", incomingVote.ProposedBlockInfo.Hash, "incomingVoteProposedBlockNumber", incomingVote.ProposedBlockInfo.Number.Uint64(), "incomingVoteProposedBlockRound", incomingVote.ProposedBlockInfo.Round)
|
||||
return nil
|
||||
}
|
||||
// Trigger the safety Alarm if failed
|
||||
isVoteBlamed, parentQC, err := f.isVoteBlamed(chain, highestCommittedQCs, incomingVote)
|
||||
if err != nil {
|
||||
log.Error("[ProcessVoteEquivocation] Error while trying to call isVoteBlamed", "error", err)
|
||||
return err
|
||||
}
|
||||
if isVoteBlamed {
|
||||
signer, err := GetVoteSignerAddresses(incomingVote)
|
||||
if err != nil {
|
||||
log.Error("[ProcessVoteEquivocation] GetVoteSignerAddresses", "error", err)
|
||||
}
|
||||
qc := highestCommittedQCs[NUM_OF_FORENSICS_QC-1]
|
||||
for _, signature := range qc.Signatures {
|
||||
voteFromQC := &types.Vote{ProposedBlockInfo: qc.ProposedBlockInfo, Signature: signature, GapNumber: qc.GapNumber}
|
||||
signerFromQC, err := GetVoteSignerAddresses(voteFromQC)
|
||||
if err != nil {
|
||||
log.Error("[ProcessVoteEquivocation] GetVoteSignerAddresses", "error", err)
|
||||
return err
|
||||
}
|
||||
if signerFromQC == signer {
|
||||
f.SendVoteEquivocationProof(incomingVote, voteFromQC, signer)
|
||||
break
|
||||
}
|
||||
}
|
||||
// if no same-signer vote, nothing to report
|
||||
} else {
|
||||
// use the parent QC to do forensics
|
||||
f.ProcessForensics(chain, engine, *parentQC)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Forensics) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *types.BlockInfo, ancestorBlock *types.BlockInfo) (bool, error) {
|
||||
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
|
||||
|
||||
nextBlockHash := currentBlock.Hash
|
||||
for i := 0; i < blockNumDiff; i++ {
|
||||
parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash)
|
||||
if parentBlock == nil {
|
||||
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number)
|
||||
} else {
|
||||
nextBlockHash = parentBlock.ParentHash
|
||||
}
|
||||
log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash)
|
||||
}
|
||||
|
||||
if nextBlockHash == ancestorBlock.Hash {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (f *Forensics) isVoteBlamed(chain consensus.ChainReader, highestCommittedQCs []types.QuorumCert, incomingVote *types.Vote) (bool, *types.QuorumCert, error) {
|
||||
proposedBlock := chain.GetHeaderByHash(incomingVote.ProposedBlockInfo.Hash)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(proposedBlock.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[findAncestorVoteThroughRound] Error while trying to decode extra field", "ProposedBlockInfo.Hash", incomingVote.ProposedBlockInfo.Hash)
|
||||
return false, nil, err
|
||||
}
|
||||
// Found the parent QC, if its round < hcqc3's round, return true
|
||||
if decodedExtraField.QuorumCert.ProposedBlockInfo.Round < highestCommittedQCs[NUM_OF_FORENSICS_QC-1].ProposedBlockInfo.Round {
|
||||
return true, decodedExtraField.QuorumCert, nil
|
||||
}
|
||||
return false, decodedExtraField.QuorumCert, nil
|
||||
}
|
||||
|
||||
func (f *Forensics) DetectEquivocationInVotePool(vote *types.Vote, votePool *utils.Pool) {
|
||||
poolKey := vote.PoolKey()
|
||||
votePoolKeys := votePool.PoolObjKeysList()
|
||||
signer, err := GetVoteSignerAddresses(vote)
|
||||
if err != nil {
|
||||
log.Error("[detectEquivocationInVotePool]", "err", err)
|
||||
}
|
||||
|
||||
for _, k := range votePoolKeys {
|
||||
if k == poolKey {
|
||||
continue
|
||||
}
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Error("[detectEquivocationInVotePool] Error while trying to get keyedRound inside pool", "Error", err)
|
||||
continue
|
||||
}
|
||||
if types.Round(keyedRound) == vote.ProposedBlockInfo.Round {
|
||||
votes := votePool.GetObjsByKey(k)
|
||||
for _, v := range votes {
|
||||
voteTransfered, ok := v.(*types.Vote)
|
||||
if !ok {
|
||||
log.Warn("[detectEquivocationInVotePool] obj type is not vote, potential a bug in votePool")
|
||||
continue
|
||||
}
|
||||
signer2, err := GetVoteSignerAddresses(voteTransfered)
|
||||
if err != nil {
|
||||
log.Warn("[detectEquivocationInVotePool]", "err", err)
|
||||
continue
|
||||
}
|
||||
if signer == signer2 {
|
||||
f.SendVoteEquivocationProof(vote, voteTransfered, signer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Forensics) SendVoteEquivocationProof(vote1, vote2 *types.Vote, signer common.Address) error {
|
||||
smallerRoundVote := vote1
|
||||
largerRoundVote := vote2
|
||||
if vote1.ProposedBlockInfo.Round > vote2.ProposedBlockInfo.Round {
|
||||
smallerRoundVote = vote2
|
||||
largerRoundVote = vote1
|
||||
}
|
||||
content, err := json.Marshal(&types.VoteEquivocationContent{
|
||||
SmallerRoundVote: smallerRoundVote,
|
||||
LargerRoundVote: largerRoundVote,
|
||||
Signer: signer,
|
||||
})
|
||||
if err != nil {
|
||||
log.Error("[SendVoteEquivocationProof] fail to json stringify forensics content", "err", err)
|
||||
return err
|
||||
}
|
||||
forensicsProof := &types.ForensicProof{
|
||||
Id: generateVoteEquivocationId(signer, smallerRoundVote.ProposedBlockInfo.Round, largerRoundVote.ProposedBlockInfo.Round),
|
||||
ForensicsType: "Vote",
|
||||
Content: string(content),
|
||||
}
|
||||
log.Info("Forensics proof report generated, sending to the stats server", "forensicsProof", forensicsProof)
|
||||
go f.forensicsFeed.Send(types.ForensicsEvent{ForensicsProof: forensicsProof})
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetVoteSignerAddresses(vote *types.Vote) (common.Address, error) {
|
||||
// The QC signatures are signed by votes special struct VoteForSign
|
||||
signHash := types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: vote.ProposedBlockInfo,
|
||||
GapNumber: vote.GapNumber,
|
||||
})
|
||||
var signerAddress common.Address
|
||||
pubkey, err := crypto.Ecrecover(signHash.Bytes(), vote.Signature)
|
||||
if err != nil {
|
||||
return signerAddress, fmt.Errorf("fail to Ecrecover signer from the vote: %v", vote)
|
||||
}
|
||||
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
return signerAddress, nil
|
||||
}
|
||||
145
consensus/XDPoS/engines/engine_v2/forensics_test.go
Normal file
145
consensus/XDPoS/engines/engine_v2/forensics_test.go
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Utils to help mocking the signing of signatures
|
||||
var (
|
||||
signer1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
signer2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
signer3, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
)
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte {
|
||||
signer, signFn, err := getSignerAndSignFn(pk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, itemToSign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return signedHash
|
||||
}
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", fmt.Sprintf("eth-getSignerAndSignFn-test-%v", RandStringBytes(5)))
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.ImportECDSA(pk, pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
func TestFindQCsInSameRound(t *testing.T) {
|
||||
forensics := &Forensics{}
|
||||
gapNumber := 450
|
||||
|
||||
// If ONE in common
|
||||
var sig []types.Signature
|
||||
qc1 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc1"),
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc2 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc2"),
|
||||
Round: types.Round(12),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc3 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc3"),
|
||||
Round: types.Round(13),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc4 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc4"),
|
||||
Round: types.Round(12),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc5 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc5"),
|
||||
Round: types.Round(13),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
qc6 := &types.QuorumCert{
|
||||
ProposedBlockInfo: &types.BlockInfo{
|
||||
Hash: common.StringToHash("qc6"),
|
||||
Round: types.Round(15),
|
||||
Number: big.NewInt(910),
|
||||
},
|
||||
Signatures: sig,
|
||||
GapNumber: uint64(gapNumber),
|
||||
}
|
||||
|
||||
var qcSet1 []types.QuorumCert
|
||||
var qcSet2 []types.QuorumCert
|
||||
|
||||
found, first, second := forensics.findQCsInSameRound(append(qcSet1, *qc1, *qc2, *qc3), append(qcSet2, *qc4, *qc5, *qc6))
|
||||
assert.True(t, found)
|
||||
assert.Equal(t, *qc2, first)
|
||||
assert.Equal(t, *qc4, second)
|
||||
}
|
||||
|
||||
// TODO: Add test for FindAncestorBlockHash
|
||||
62
consensus/XDPoS/engines/engine_v2/mining.go
Normal file
62
consensus/XDPoS/engines/engine_v2/mining.go
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Using parent and current round to find the finalised master node list(with penalties applied from last epoch)
|
||||
func (x *XDPoS_v2) yourturn(chain consensus.ChainReader, round types.Round, parent *types.Header, signer common.Address) (bool, error) {
|
||||
if round <= x.highestSelfMinedRound {
|
||||
log.Warn("[yourturn] Already mined on this round", "Round", round, "highestSelfMinedRound", x.highestSelfMinedRound, "ParentHash", parent.Hash().Hex(), "ParentNumber", parent.Number)
|
||||
return false, utils.ErrAlreadyMined
|
||||
}
|
||||
|
||||
isEpochSwitch, _, err := x.isEpochSwitchAtRound(round, parent)
|
||||
if err != nil {
|
||||
log.Error("[yourturn] check epoch switch at round failed", "Error", err)
|
||||
return false, err
|
||||
}
|
||||
var masterNodes []common.Address
|
||||
if isEpochSwitch {
|
||||
masterNodes, _, err = x.calcMasternodes(chain, big.NewInt(0).Add(parent.Number, big.NewInt(1)), parent.Hash())
|
||||
if err != nil {
|
||||
log.Error("[yourturn] Cannot calcMasternodes at gap num ", "err", err, "parent number", parent.Number)
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
// this block and parent belong to the same epoch
|
||||
masterNodes = x.GetMasternodes(chain, parent)
|
||||
}
|
||||
|
||||
if len(masterNodes) == 0 {
|
||||
log.Error("[yourturn] Fail to find any master nodes from current block round epoch", "Hash", parent.Hash(), "CurrentRound", round, "Number", parent.Number)
|
||||
return false, errors.New("masternodes not found")
|
||||
}
|
||||
|
||||
curIndex := utils.Position(masterNodes, signer)
|
||||
if curIndex == -1 {
|
||||
log.Warn("[yourturn] I am not in masternodes list", "Hash", parent.Hash(), "signer", signer)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for i, s := range masterNodes {
|
||||
log.Debug("[yourturn] Masternode:", "index", i, "address", s.String(), "parentBlockNum", parent.Number)
|
||||
}
|
||||
|
||||
leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes))
|
||||
x.whosTurn = masterNodes[leaderIndex]
|
||||
if x.whosTurn != signer {
|
||||
log.Info("[yourturn] Not my turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", parent.Hash().Hex(), "whosTurn", x.whosTurn, "myaddr", signer)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
log.Info("[yourturn] Yes, it's my turn based on parent block", "ParentHash", parent.Hash().Hex(), "ParentBlockNumber", parent.Number.Uint64())
|
||||
return true, nil
|
||||
}
|
||||
|
|
@ -1 +1,102 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Snapshot is the state of the smart contract validator list
|
||||
// The validator list is used on next epoch master nodes
|
||||
// If we don't have the snapshot, then we have to trace back the gap block smart contract state which is very costly
|
||||
type SnapshotV2 struct {
|
||||
Number uint64 `json:"number"` // Block number where the snapshot was created
|
||||
Hash common.Hash `json:"hash"` // Block hash where the snapshot was created
|
||||
|
||||
// MasterNodes will get assigned on updateM1
|
||||
NextEpochMasterNodes []common.Address `json:"masterNodes"` // Set of authorized master nodes at this moment for next epoch
|
||||
}
|
||||
|
||||
// create new snapshot for next epoch to use
|
||||
func newSnapshot(number uint64, hash common.Hash, masternodes []common.Address) *SnapshotV2 {
|
||||
snap := &SnapshotV2{
|
||||
Number: number,
|
||||
Hash: hash,
|
||||
NextEpochMasterNodes: masternodes,
|
||||
}
|
||||
return snap
|
||||
}
|
||||
|
||||
// loadSnapshot loads an existing snapshot from the database.
|
||||
func loadSnapshot(db ethdb.Database, hash common.Hash) (*SnapshotV2, error) {
|
||||
blob, err := db.Get(append([]byte("XDPoS-V2-"), hash[:]...))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
snap := new(SnapshotV2)
|
||||
if err := json.Unmarshal(blob, snap); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// store inserts the SnapshotV2 into the database.
|
||||
func storeSnapshot(s *SnapshotV2, db ethdb.Database) error {
|
||||
blob, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return db.Put(append([]byte("XDPoS-V2-"), s.Hash[:]...), blob)
|
||||
}
|
||||
|
||||
// retrieves master nodes list in map type
|
||||
func (s *SnapshotV2) GetMappedMasterNodes() map[common.Address]struct{} {
|
||||
ms := make(map[common.Address]struct{})
|
||||
for _, n := range s.NextEpochMasterNodes {
|
||||
ms[n] = struct{}{}
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
func (s *SnapshotV2) IsMasterNodes(address common.Address) bool {
|
||||
for _, n := range s.NextEpochMasterNodes {
|
||||
if n.String() == address.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// snapshot retrieves the authorization snapshot at a given point in time.
|
||||
func (x *XDPoS_v2) getSnapshot(chain consensus.ChainReader, number uint64, isGapNumber bool) (*SnapshotV2, error) {
|
||||
var gapBlockNum uint64
|
||||
if isGapNumber {
|
||||
gapBlockNum = number
|
||||
} else {
|
||||
gapBlockNum = number - number%x.config.Epoch - x.config.Gap
|
||||
}
|
||||
|
||||
gapBlockHash := chain.GetHeaderByNumber(gapBlockNum).Hash()
|
||||
log.Debug("get snapshot from gap block", "number", gapBlockNum, "hash", gapBlockHash.Hex())
|
||||
|
||||
// If an in-memory SnapshotV2 was found, use that
|
||||
if s, ok := x.snapshots.Get(gapBlockHash); ok {
|
||||
snap := s.(*SnapshotV2)
|
||||
log.Trace("Loaded snapshot from memory", "number", gapBlockNum, "hash", gapBlockHash)
|
||||
return snap, nil
|
||||
}
|
||||
// If an on-disk checkpoint snapshot can be found, use that
|
||||
snap, err := loadSnapshot(x.db, gapBlockHash)
|
||||
if err != nil {
|
||||
log.Error("Cannot find snapshot from last gap block", "err", err, "number", gapBlockNum, "hash", gapBlockHash)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Trace("Loaded snapshot from disk", "number", gapBlockNum, "hash", gapBlockHash)
|
||||
x.snapshots.Add(snap.Hash, snap)
|
||||
return snap, nil
|
||||
}
|
||||
|
|
|
|||
46
consensus/XDPoS/engines/engine_v2/snapshot_test.go
Normal file
46
consensus/XDPoS/engines/engine_v2/snapshot_test.go
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb/leveldb"
|
||||
)
|
||||
|
||||
func TestGetMasterNodes(t *testing.T) {
|
||||
masterNodes := []common.Address{{0x4}, {0x3}, {0x2}, {0x1}}
|
||||
snap := newSnapshot(1, common.Hash{}, masterNodes)
|
||||
|
||||
for _, address := range masterNodes {
|
||||
if _, ok := snap.GetMappedMasterNodes()[address]; !ok {
|
||||
t.Error("should get master node from map", address.Hex(), snap.GetMappedMasterNodes())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreLoadSnapshot(t *testing.T) {
|
||||
snap := newSnapshot(1, common.Hash{0x1}, nil)
|
||||
dir, err := ioutil.TempDir("", "snapshot-test")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't create temporary directory: %v", err))
|
||||
}
|
||||
db, err := leveldb.New(dir, 256, 0, "")
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("can't create temporary database: %v", err))
|
||||
}
|
||||
lddb := rawdb.NewDatabase(db)
|
||||
|
||||
err = storeSnapshot(snap, lddb)
|
||||
if err != nil {
|
||||
t.Error("store snapshot failed", err)
|
||||
}
|
||||
|
||||
restoredSnapshot, err := loadSnapshot(lddb, snap.Hash)
|
||||
if err != nil || restoredSnapshot.Hash != snap.Hash {
|
||||
t.Error("load snapshot failed", err)
|
||||
}
|
||||
}
|
||||
88
consensus/XDPoS/engines/engine_v2/testing_utils.go
Normal file
88
consensus/XDPoS/engines/engine_v2/testing_utils.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
/*
|
||||
Testing tools
|
||||
*/
|
||||
|
||||
func (x *XDPoS_v2) SetNewRoundFaker(blockChainReader consensus.ChainReader, newRound types.Round, resetTimer bool) {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
// Reset a bunch of things
|
||||
if resetTimer {
|
||||
x.timeoutWorker.Reset(blockChainReader)
|
||||
}
|
||||
x.currentRound = newRound
|
||||
}
|
||||
|
||||
// for test only
|
||||
func (x *XDPoS_v2) ProcessQCFaker(chain consensus.ChainReader, qc *types.QuorumCert) error {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
return x.processQC(chain, qc)
|
||||
}
|
||||
|
||||
// Utils for test to check currentRound value
|
||||
func (x *XDPoS_v2) GetCurrentRoundFaker() types.Round {
|
||||
x.lock.RLock()
|
||||
defer x.lock.RUnlock()
|
||||
return x.currentRound
|
||||
}
|
||||
|
||||
// Utils for test to get current Pool size
|
||||
func (x *XDPoS_v2) GetVotePoolSizeFaker(vote *types.Vote) int {
|
||||
return x.votePool.Size(vote)
|
||||
}
|
||||
|
||||
// Utils for test to get Timeout Pool Size
|
||||
func (x *XDPoS_v2) GetTimeoutPoolSizeFaker(timeout *types.Timeout) int {
|
||||
return x.timeoutPool.Size(timeout)
|
||||
}
|
||||
|
||||
// WARN: This function is designed for testing purpose only!
|
||||
// Utils for test to check currentRound values
|
||||
func (x *XDPoS_v2) GetPropertiesFaker() (types.Round, *types.QuorumCert, *types.QuorumCert, *types.TimeoutCert, types.Round, *types.BlockInfo) {
|
||||
x.lock.RLock()
|
||||
defer x.lock.RUnlock()
|
||||
return x.currentRound, x.lockQuorumCert, x.highestQuorumCert, x.highestTimeoutCert, x.highestVotedRound, x.highestCommitBlock
|
||||
}
|
||||
|
||||
// WARN: This function is designed for testing purpose only!
|
||||
// Utils for tests to set engine specific values
|
||||
func (x *XDPoS_v2) SetPropertiesFaker(highestQC *types.QuorumCert, highestTC *types.TimeoutCert) {
|
||||
x.highestQuorumCert = highestQC
|
||||
x.highestTimeoutCert = highestTC
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) HygieneVotePoolFaker() {
|
||||
x.hygieneVotePool()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetVotePoolKeyListFaker() []string {
|
||||
return x.votePool.PoolObjKeysList()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) HygieneTimeoutPoolFaker() {
|
||||
x.hygieneTimeoutPool()
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetTimeoutPoolKeyListFaker() []string {
|
||||
return x.timeoutPool.PoolObjKeysList()
|
||||
}
|
||||
|
||||
// Fake the signer address, the signing function is incompatible
|
||||
func (x *XDPoS_v2) AuthorizeFaker(signer common.Address) {
|
||||
x.signLock.Lock()
|
||||
defer x.signLock.Unlock()
|
||||
|
||||
x.signer = signer
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetForensicsFaker() *Forensics {
|
||||
return x.ForensicsProcessor
|
||||
}
|
||||
265
consensus/XDPoS/engines/engine_v2/timeout.go
Normal file
265
consensus/XDPoS/engines/engine_v2/timeout.go
Normal file
|
|
@ -0,0 +1,265 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
func (x *XDPoS_v2) timeoutHandler(blockChainReader consensus.ChainReader, timeout *types.Timeout) error {
|
||||
// checkRoundNumber
|
||||
if timeout.Round != x.currentRound {
|
||||
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
|
||||
Type: "timeout",
|
||||
IncomingRound: timeout.Round,
|
||||
CurrentRound: x.currentRound,
|
||||
}
|
||||
}
|
||||
// Collect timeout, generate TC
|
||||
numberOfTimeoutsInPool, pooledTimeouts := x.timeoutPool.Add(timeout)
|
||||
log.Debug("[timeoutHandler] collect timeout", "number", numberOfTimeoutsInPool)
|
||||
|
||||
// Threshold reached
|
||||
certThreshold := x.config.V2.Config(uint64(x.currentRound)).CertThreshold
|
||||
isThresholdReached := numberOfTimeoutsInPool >= certThreshold
|
||||
if isThresholdReached {
|
||||
log.Info(fmt.Sprintf("Timeout pool threashold reached: %v, number of items in the pool: %v", isThresholdReached, numberOfTimeoutsInPool))
|
||||
err := x.onTimeoutPoolThresholdReached(blockChainReader, pooledTimeouts, timeout, timeout.GapNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by timeoutPool when it reached threshold.
|
||||
In the engine v2, we will need to:
|
||||
1. Genrate TC
|
||||
2. processTC()
|
||||
3. generateSyncInfo()
|
||||
*/
|
||||
func (x *XDPoS_v2) onTimeoutPoolThresholdReached(blockChainReader consensus.ChainReader, pooledTimeouts map[common.Hash]utils.PoolObj, currentTimeoutMsg utils.PoolObj, gapNumber uint64) error {
|
||||
signatures := []types.Signature{}
|
||||
for _, v := range pooledTimeouts {
|
||||
signatures = append(signatures, v.(*types.Timeout).Signature)
|
||||
}
|
||||
// Genrate TC
|
||||
timeoutCert := &types.TimeoutCert{
|
||||
Round: currentTimeoutMsg.(*types.Timeout).Round,
|
||||
Signatures: signatures,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
// Process TC
|
||||
err := x.processTC(blockChainReader, timeoutCert)
|
||||
if err != nil {
|
||||
log.Error("Error while processing TC in the Timeout handler after reaching pool threshold", "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures), "GapNumber", gapNumber, "Error", err)
|
||||
return err
|
||||
}
|
||||
// Generate and broadcast syncInfo
|
||||
syncInfo := x.getSyncInfo()
|
||||
x.broadcastToBftChannel(syncInfo)
|
||||
|
||||
log.Info("Successfully processed the timeout message and produced TC & SyncInfo!", "QcRound", syncInfo.HighestQuorumCert.ProposedBlockInfo.Round, "QcBlockNum", syncInfo.HighestQuorumCert.ProposedBlockInfo.Number, "TcRound", timeoutCert.Round, "NumberOfTcSig", len(timeoutCert.Signatures))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) verifyTC(chain consensus.ChainReader, timeoutCert *types.TimeoutCert) error {
|
||||
/*
|
||||
1. Get epoch master node list by gapNumber
|
||||
2. Check number of signatures > threshold, as well as it's format. (Same as verifyQC)
|
||||
2. Verify signer signature: (List of signatures)
|
||||
- Use ecRecover to get the public key
|
||||
- Use the above public key to find out the xdc address
|
||||
- Use the above xdc address to check against the master node list from step 1(For the received TC epoch)
|
||||
*/
|
||||
if timeoutCert == nil || timeoutCert.Signatures == nil {
|
||||
log.Warn("[verifyTC] TC or TC signatures is Nil")
|
||||
return utils.ErrInvalidTC
|
||||
}
|
||||
|
||||
snap, err := x.getSnapshot(chain, timeoutCert.GapNumber, true)
|
||||
if err != nil {
|
||||
log.Error("[verifyTC] Fail to get snapshot when verifying TC!", "TCGapNumber", timeoutCert.GapNumber)
|
||||
return fmt.Errorf("[verifyTC] Unable to get snapshot")
|
||||
}
|
||||
if snap == nil || len(snap.NextEpochMasterNodes) == 0 {
|
||||
log.Error("[verifyTC] Something wrong with the snapshot from gapNumber", "messageGapNumber", timeoutCert.GapNumber, "snapshot", snap)
|
||||
return fmt.Errorf("Empty master node lists from snapshot")
|
||||
}
|
||||
|
||||
signatures, duplicates := UniqueSignatures(timeoutCert.Signatures)
|
||||
if len(duplicates) != 0 {
|
||||
for _, d := range duplicates {
|
||||
log.Warn("[verifyQC] duplicated signature in QC", "duplicate", common.Bytes2Hex(d))
|
||||
}
|
||||
}
|
||||
|
||||
certThreshold := x.config.V2.Config(uint64(timeoutCert.Round)).CertThreshold
|
||||
if len(signatures) < certThreshold {
|
||||
log.Warn("[verifyTC] Invalid TC Signature is nil or empty", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(timeoutCert.Signatures), "CertThreshold", certThreshold)
|
||||
return utils.ErrInvalidTCSignatures
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(signatures))
|
||||
var haveError error
|
||||
|
||||
signedTimeoutObj := types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: timeoutCert.Round,
|
||||
GapNumber: timeoutCert.GapNumber,
|
||||
})
|
||||
|
||||
for _, signature := range signatures {
|
||||
go func(sig types.Signature) {
|
||||
defer wg.Done()
|
||||
verified, _, err := x.verifyMsgSignature(signedTimeoutObj, sig, snap.NextEpochMasterNodes)
|
||||
if err != nil {
|
||||
log.Error("[verifyTC] Error while verfying TC message signatures", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(signatures), "Error", err)
|
||||
haveError = fmt.Errorf("Error while verfying TC message signatures")
|
||||
return
|
||||
}
|
||||
if !verified {
|
||||
log.Warn("[verifyTC] Signature not verified doing TC verification", "timeoutCert.Round", timeoutCert.Round, "timeoutCert.GapNumber", timeoutCert.GapNumber, "Signatures len", len(signatures))
|
||||
haveError = fmt.Errorf("Fail to verify TC due to signature mis-match")
|
||||
return
|
||||
}
|
||||
}(signature)
|
||||
}
|
||||
wg.Wait()
|
||||
if haveError != nil {
|
||||
return haveError
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
1. Update highestTC
|
||||
2. Check TC round >= node's currentRound. If yes, call setNewRound
|
||||
*/
|
||||
func (x *XDPoS_v2) processTC(blockChainReader consensus.ChainReader, timeoutCert *types.TimeoutCert) error {
|
||||
if timeoutCert.Round > x.highestTimeoutCert.Round {
|
||||
x.highestTimeoutCert = timeoutCert
|
||||
}
|
||||
if timeoutCert.Round >= x.currentRound {
|
||||
x.setNewRound(blockChainReader, timeoutCert.Round+1)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Generate and send timeout into BFT channel.
|
||||
/*
|
||||
1. timeout.round = currentRound
|
||||
2. Sign the signature
|
||||
3. send to broadcast channel
|
||||
*/
|
||||
func (x *XDPoS_v2) sendTimeout(chain consensus.ChainReader) error {
|
||||
// Construct the gapNumber
|
||||
var gapNumber uint64
|
||||
currentBlockHeader := chain.CurrentHeader()
|
||||
isEpochSwitch, epochNum, err := x.isEpochSwitchAtRound(x.currentRound, currentBlockHeader)
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] Error while checking if the currentBlock is epoch switch", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
|
||||
return err
|
||||
}
|
||||
|
||||
if isEpochSwitch {
|
||||
// Notice this +1 is because we expect a block whos is the child of currentHeader
|
||||
currentNumber := currentBlockHeader.Number.Uint64() + 1
|
||||
gapNumber = currentNumber - currentNumber%x.config.Epoch - x.config.Gap
|
||||
log.Debug("[sendTimeout] is epoch switch when sending out timeout message", "currentNumber", currentNumber, "gapNumber", gapNumber)
|
||||
} else {
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chain, currentBlockHeader, currentBlockHeader.Hash())
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] Error when trying to get current epoch switch info for a non-epoch block", "currentRound", x.currentRound, "currentBlockNum", currentBlockHeader.Number, "currentBlockHash", currentBlockHeader.Hash(), "epochNum", epochNum)
|
||||
}
|
||||
gapNumber = epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64() - epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()%x.config.Epoch - x.config.Gap
|
||||
log.Debug("[sendTimeout] non-epoch-switch block found its epoch block and calculated the gapNumber", "epochSwitchInfo.EpochSwitchBlockInfo.Number", epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64(), "gapNumber", gapNumber)
|
||||
}
|
||||
|
||||
signedHash, err := x.signSignature(types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: x.currentRound,
|
||||
GapNumber: gapNumber,
|
||||
}))
|
||||
if err != nil {
|
||||
log.Error("[sendTimeout] signSignature when sending out TC", "Error", err, "round", x.currentRound, "gap", gapNumber)
|
||||
return err
|
||||
}
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: x.currentRound,
|
||||
Signature: signedHash,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
log.Warn("[sendTimeout] Timeout message generated, ready to send!", "timeoutMsgRound", timeoutMsg.Round, "timeoutMsgGapNumber", timeoutMsg.GapNumber, "whosTurn", x.whosTurn)
|
||||
err = x.timeoutHandler(chain, timeoutMsg)
|
||||
if err != nil {
|
||||
log.Error("TimeoutHandler error", "TimeoutRound", timeoutMsg.Round, "Error", err)
|
||||
return err
|
||||
}
|
||||
x.broadcastToBftChannel(timeoutMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by timer when countdown reaches its threshold.
|
||||
In the engine v2, we would need to broadcast timeout messages to other peers
|
||||
*/
|
||||
func (x *XDPoS_v2) OnCountdownTimeout(time time.Time, chain interface{}) error {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
|
||||
// Check if we are within the master node list
|
||||
allow := x.allowedToSend(chain.(consensus.ChainReader), chain.(consensus.ChainReader).CurrentHeader(), "timeout")
|
||||
if !allow {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := x.sendTimeout(chain.(consensus.ChainReader))
|
||||
if err != nil {
|
||||
log.Error("Error while sending out timeout message at time: ", "time", time, "err", err)
|
||||
return err
|
||||
}
|
||||
|
||||
x.timeoutCount++
|
||||
if x.timeoutCount%x.config.V2.CurrentConfig.TimeoutSyncThreshold == 0 {
|
||||
log.Warn("[OnCountdownTimeout] timeout sync threadhold reached, send syncInfo message")
|
||||
syncInfo := x.getSyncInfo()
|
||||
x.broadcastToBftChannel(syncInfo)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) hygieneTimeoutPool() {
|
||||
x.lock.RLock()
|
||||
currentRound := x.currentRound
|
||||
x.lock.RUnlock()
|
||||
timeoutPoolKeys := x.timeoutPool.PoolObjKeysList()
|
||||
|
||||
// Extract round number
|
||||
for _, k := range timeoutPoolKeys {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Error("[hygieneTimeoutPool] Error while trying to get keyedRound inside pool", "Error", err)
|
||||
continue
|
||||
}
|
||||
// Clean up any timeouts round that is 10 rounds older
|
||||
if keyedRound < int64(currentRound)-utils.PoolHygieneRound {
|
||||
log.Debug("[hygieneTimeoutPool] Cleaned timeout pool at round", "Round", keyedRound, "CurrentRound", currentRound, "Key", k)
|
||||
x.timeoutPool.ClearByPoolKey(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) ReceivedTimeouts() map[string]map[common.Hash]utils.PoolObj {
|
||||
return x.timeoutPool.Get()
|
||||
}
|
||||
159
consensus/XDPoS/engines/engine_v2/utils.go
Normal file
159
consensus/XDPoS/engines/engine_v2/utils.go
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
func sigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra,
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
header.Validators,
|
||||
header.Penalties,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
}
|
||||
|
||||
func ecrecover(header *types.Header, sigcache *lru.ARCCache) (common.Address, error) {
|
||||
// If the signature's already cached, return that
|
||||
hash := header.Hash()
|
||||
if address, known := sigcache.Get(hash); known {
|
||||
return address.(common.Address), nil
|
||||
}
|
||||
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(sigHash(header).Bytes(), header.Validator)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
var signer common.Address
|
||||
copy(signer[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
|
||||
sigcache.Add(hash, signer)
|
||||
return signer, nil
|
||||
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header. Only used for v1 last block
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func UniqueSignatures(signatureSlice []types.Signature) ([]types.Signature, []types.Signature) {
|
||||
keys := make(map[string]bool)
|
||||
list := []types.Signature{}
|
||||
duplicates := []types.Signature{}
|
||||
for _, signature := range signatureSlice {
|
||||
hexOfSig := common.Bytes2Hex(signature)
|
||||
if _, value := keys[hexOfSig]; !value {
|
||||
keys[hexOfSig] = true
|
||||
list = append(list, signature)
|
||||
} else {
|
||||
duplicates = append(duplicates, signature)
|
||||
}
|
||||
}
|
||||
return list, duplicates
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) signSignature(signingHash common.Hash) (types.Signature, error) {
|
||||
// Don't hold the signFn for the whole signing operation
|
||||
x.signLock.RLock()
|
||||
signer, signFn := x.signer, x.signFn
|
||||
x.signLock.RUnlock()
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, signingHash.Bytes())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error %v while signing hash", err)
|
||||
}
|
||||
return signedHash, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) verifyMsgSignature(signedHashToBeVerified common.Hash, signature types.Signature, masternodes []common.Address) (bool, common.Address, error) {
|
||||
var signerAddress common.Address
|
||||
if len(masternodes) == 0 {
|
||||
return false, signerAddress, fmt.Errorf("Empty masternode list detected when verifying message signatures")
|
||||
}
|
||||
// Recover the public key and the Ethereum address
|
||||
pubkey, err := crypto.Ecrecover(signedHashToBeVerified.Bytes(), signature)
|
||||
if err != nil {
|
||||
return false, signerAddress, fmt.Errorf("Error while verifying message: %v", err)
|
||||
}
|
||||
|
||||
copy(signerAddress[:], crypto.Keccak256(pubkey[1:])[12:])
|
||||
for _, mn := range masternodes {
|
||||
if mn == signerAddress {
|
||||
return true, signerAddress, nil
|
||||
}
|
||||
}
|
||||
|
||||
log.Warn("[verifyMsgSignature] signer is not part of masternode list", "signer", signerAddress, "masternodes", masternodes)
|
||||
return false, signerAddress, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) getExtraFields(header *types.Header) (*types.QuorumCert, types.Round, []common.Address, error) {
|
||||
|
||||
var masternodes []common.Address
|
||||
|
||||
// last v1 block
|
||||
if header.Number.Cmp(x.config.V2.SwitchBlock) == 0 {
|
||||
masternodes = decodeMasternodesFromHeaderExtra(header)
|
||||
return nil, types.Round(0), masternodes, nil
|
||||
}
|
||||
|
||||
// v2 block
|
||||
masternodes = x.GetMasternodesFromEpochSwitchHeader(header)
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
log.Error("[getExtraFields] error on decode extra fields", "err", err, "extra", header.Extra)
|
||||
return nil, types.Round(0), masternodes, err
|
||||
}
|
||||
return decodedExtraField.QuorumCert, decodedExtraField.Round, masternodes, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) GetRoundNumber(header *types.Header) (types.Round, error) {
|
||||
// If not v2 yet, return 0
|
||||
if header.Number.Cmp(x.config.V2.SwitchBlock) <= 0 {
|
||||
return types.Round(0), nil
|
||||
} else {
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
return types.Round(0), err
|
||||
}
|
||||
return decodedExtraField.Round, nil
|
||||
}
|
||||
}
|
||||
191
consensus/XDPoS/engines/engine_v2/verifyHeader.go
Normal file
191
consensus/XDPoS/engines/engine_v2/verifyHeader.go
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/misc"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Verify individual header
|
||||
func (x *XDPoS_v2) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error {
|
||||
// If we're running a engine faking, accept any block as valid
|
||||
if x.config.V2.SkipV2Validation {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !x.isInitilised {
|
||||
if err := x.initial(chain, header); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
_, check := x.verifiedHeaders.Get(header.Hash())
|
||||
if check {
|
||||
return nil
|
||||
}
|
||||
|
||||
if header.Number == nil {
|
||||
return utils.ErrUnknownBlock
|
||||
}
|
||||
|
||||
if len(header.Validator) == 0 {
|
||||
return consensus.ErrNoValidatorSignature
|
||||
}
|
||||
|
||||
if fullVerify {
|
||||
// Don't waste time checking blocks from the future
|
||||
if header.Time.Int64() > time.Now().Unix() {
|
||||
return consensus.ErrFutureBlock
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the block's timestamp isn't too close to it's parent
|
||||
var parent *types.Header
|
||||
number := header.Number.Uint64()
|
||||
|
||||
if len(parents) > 0 {
|
||||
parent = parents[len(parents)-1]
|
||||
} else {
|
||||
parent = chain.GetHeader(header.ParentHash, number-1)
|
||||
}
|
||||
if parent == nil || parent.Number.Uint64() != number-1 || parent.Hash() != header.ParentHash {
|
||||
return consensus.ErrUnknownAncestor
|
||||
}
|
||||
|
||||
// Verify this is truely a v2 block first
|
||||
quorumCert, round, _, err := x.getExtraFields(header)
|
||||
if err != nil {
|
||||
log.Warn("[verifyHeader] decode extra field error", "err", err)
|
||||
return utils.ErrInvalidV2Extra
|
||||
}
|
||||
|
||||
minePeriod := uint64(x.config.V2.Config(uint64(round)).MinePeriod)
|
||||
if parent.Number.Uint64() > x.config.V2.SwitchBlock.Uint64() && parent.Time.Uint64()+minePeriod > header.Time.Uint64() {
|
||||
log.Warn("[verifyHeader] Fail to verify header due to invalid timestamp", "ParentTime", parent.Time.Uint64(), "MinePeriod", minePeriod, "HeaderTime", header.Time.Uint64(), "Hash", header.Hash().Hex())
|
||||
return utils.ErrInvalidTimestamp
|
||||
}
|
||||
|
||||
if round <= quorumCert.ProposedBlockInfo.Round {
|
||||
return utils.ErrRoundInvalid
|
||||
}
|
||||
|
||||
err = x.verifyQC(chain, quorumCert, parent)
|
||||
if err != nil {
|
||||
log.Warn("[verifyHeader] fail to verify QC", "QCNumber", quorumCert.ProposedBlockInfo.Number, "QCsigLength", len(quorumCert.Signatures))
|
||||
return err
|
||||
}
|
||||
// Nonces must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
|
||||
if !bytes.Equal(header.Nonce[:], utils.NonceAuthVote) && !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
||||
return utils.ErrInvalidVote
|
||||
}
|
||||
// Ensure that the mix digest is zero as we don't have fork protection currently
|
||||
if header.MixDigest != (common.Hash{}) {
|
||||
return utils.ErrInvalidMixDigest
|
||||
}
|
||||
// Ensure that the block doesn't contain any uncles which are meaningless in XDPoS_v1
|
||||
if header.UncleHash != utils.UncleHash {
|
||||
return utils.ErrInvalidUncleHash
|
||||
}
|
||||
|
||||
if header.Difficulty.Cmp(big.NewInt(1)) != 0 {
|
||||
return utils.ErrInvalidDifficulty
|
||||
}
|
||||
|
||||
var masterNodes []common.Address
|
||||
isEpochSwitch, _, err := x.IsEpochSwitch(header) // Verify v2 block that is on the epoch switch
|
||||
if err != nil {
|
||||
log.Error("[verifyHeader] error when checking if header is epoch switch header", "Hash", header.Hash(), "Number", header.Number, "Error", err)
|
||||
return err
|
||||
}
|
||||
if isEpochSwitch {
|
||||
if !bytes.Equal(header.Nonce[:], utils.NonceDropVote) {
|
||||
return utils.ErrInvalidCheckpointVote
|
||||
}
|
||||
if header.Validators == nil || len(header.Validators) == 0 {
|
||||
return utils.ErrEmptyEpochSwitchValidators
|
||||
}
|
||||
if len(header.Validators)%common.AddressLength != 0 {
|
||||
return utils.ErrInvalidCheckpointSigners
|
||||
}
|
||||
|
||||
localMasterNodes, localPenalties, err := x.calcMasternodes(chain, header.Number, header.ParentHash)
|
||||
masterNodes = localMasterNodes
|
||||
if err != nil {
|
||||
log.Error("[verifyHeader] Fail to calculate master nodes list with penalty", "Number", header.Number, "Hash", header.Hash())
|
||||
return err
|
||||
}
|
||||
|
||||
validatorsAddress := common.ExtractAddressFromBytes(header.Validators)
|
||||
if !utils.CompareSignersLists(localMasterNodes, validatorsAddress) {
|
||||
for i, addr := range localMasterNodes {
|
||||
log.Warn("[verifyHeader] localMasterNodes", "i", i, "addr", addr.Hex())
|
||||
}
|
||||
for i, addr := range validatorsAddress {
|
||||
log.Warn("[verifyHeader] validatorsAddress", "i", i, "addr", addr.Hex())
|
||||
}
|
||||
return utils.ErrValidatorsNotLegit
|
||||
}
|
||||
|
||||
penaltiesAddress := common.ExtractAddressFromBytes(header.Penalties)
|
||||
if !utils.CompareSignersLists(localPenalties, penaltiesAddress) {
|
||||
for i, addr := range localPenalties {
|
||||
log.Warn("[verifyHeader] localPenalties", "i", i, "addr", addr.Hex())
|
||||
}
|
||||
for i, addr := range penaltiesAddress {
|
||||
log.Warn("[verifyHeader] penaltiesAddress", "i", i, "addr", addr.Hex())
|
||||
}
|
||||
return utils.ErrPenaltiesNotLegit
|
||||
}
|
||||
|
||||
} else {
|
||||
if len(header.Validators) != 0 {
|
||||
log.Warn("[verifyHeader] Validators shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Validators", header.Validators)
|
||||
return utils.ErrInvalidFieldInNonEpochSwitch
|
||||
}
|
||||
if len(header.Penalties) != 0 {
|
||||
log.Warn("[verifyHeader] Penalties shall not have values in non-epochSwitch block", "Hash", header.Hash(), "Number", header.Number, "header.Penalties", header.Penalties)
|
||||
return utils.ErrInvalidFieldInNonEpochSwitch
|
||||
}
|
||||
masterNodes = x.GetMasternodes(chain, header)
|
||||
}
|
||||
|
||||
// If all checks passed, validate any special fields for hard forks
|
||||
if err := misc.VerifyForkHashes(chain.Config(), header, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check its validator
|
||||
verified, validatorAddress, err := x.verifyMsgSignature(sigHash(header), header.Validator, masterNodes)
|
||||
if err != nil {
|
||||
for index, mn := range masterNodes {
|
||||
log.Error("[verifyHeader] masternode list during validator verification", "Masternode Address", mn.Hex(), "index", index)
|
||||
}
|
||||
log.Error("[verifyHeader] Error while verifying header validator signature", "BlockNumber", header.Number, "Hash", header.Hash().Hex(), "validator in hex", common.ToHex(header.Validator))
|
||||
return err
|
||||
}
|
||||
if !verified {
|
||||
log.Warn("[verifyHeader] Fail to verify the block validator as the validator address not within the masternode list", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex())
|
||||
return utils.ErrValidatorNotWithinMasternodes
|
||||
}
|
||||
if validatorAddress != header.Coinbase {
|
||||
log.Warn("[verifyHeader] Header validator and coinbase address not match", header.Number, "Hash", header.Hash().Hex(), "validatorAddress", validatorAddress.Hex(), "coinbase", header.Coinbase.Hex())
|
||||
return utils.ErrCoinbaseAndValidatorMismatch
|
||||
}
|
||||
// Check the proposer is the leader
|
||||
curIndex := utils.Position(masterNodes, validatorAddress)
|
||||
leaderIndex := uint64(round) % x.config.Epoch % uint64(len(masterNodes))
|
||||
if masterNodes[leaderIndex] != validatorAddress {
|
||||
log.Warn("[verifyHeader] Invalid blocker proposer, not its turn", "curIndex", curIndex, "leaderIndex", leaderIndex, "Hash", header.Hash().Hex(), "masterNodes[leaderIndex]", masterNodes[leaderIndex], "validatorAddress", validatorAddress)
|
||||
return utils.ErrNotItsTurn
|
||||
}
|
||||
|
||||
x.verifiedHeaders.Add(header.Hash(), true)
|
||||
return nil
|
||||
}
|
||||
263
consensus/XDPoS/engines/engine_v2/vote.go
Normal file
263
consensus/XDPoS/engines/engine_v2/vote.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
package engine_v2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// Once Hot stuff voting rule has verified, this node can then send vote
|
||||
func (x *XDPoS_v2) sendVote(chainReader consensus.ChainReader, blockInfo *types.BlockInfo) error {
|
||||
// First step: Update the highest Voted round
|
||||
// Second step: Generate the signature by using node's private key(The signature is the blockInfo signature)
|
||||
// Third step: Construct the vote struct with the above signature & blockinfo struct
|
||||
// Forth step: Send the vote to broadcast channel
|
||||
|
||||
epochSwitchInfo, err := x.getEpochSwitchInfo(chainReader, nil, blockInfo.Hash)
|
||||
if err != nil {
|
||||
log.Error("getEpochSwitchInfo when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
epochSwitchNumber := epochSwitchInfo.EpochSwitchBlockInfo.Number.Uint64()
|
||||
gapNumber := epochSwitchNumber - epochSwitchNumber%x.config.Epoch - x.config.Gap
|
||||
signedHash, err := x.signSignature(types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: gapNumber,
|
||||
}))
|
||||
if err != nil {
|
||||
log.Error("signSignature when sending out Vote", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
x.highestVotedRound = x.currentRound
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
err = x.voteHandler(chainReader, voteMsg)
|
||||
if err != nil {
|
||||
log.Error("sendVote error", "BlockInfoHash", blockInfo.Hash, "Error", err)
|
||||
return err
|
||||
}
|
||||
x.broadcastToBftChannel(voteMsg)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) voteHandler(chain consensus.ChainReader, voteMsg *types.Vote) error {
|
||||
// checkRoundNumber
|
||||
if (voteMsg.ProposedBlockInfo.Round != x.currentRound) && (voteMsg.ProposedBlockInfo.Round != x.currentRound+1) {
|
||||
return &utils.ErrIncomingMessageRoundTooFarFromCurrentRound{
|
||||
Type: "vote",
|
||||
IncomingRound: voteMsg.ProposedBlockInfo.Round,
|
||||
CurrentRound: x.currentRound,
|
||||
}
|
||||
}
|
||||
|
||||
if x.votePoolCollectionTime.IsZero() {
|
||||
log.Info("[voteHandler] set vote pool time", "round", x.currentRound)
|
||||
x.votePoolCollectionTime = time.Now()
|
||||
}
|
||||
|
||||
// Collect vote
|
||||
numberOfVotesInPool, pooledVotes := x.votePool.Add(voteMsg)
|
||||
log.Debug("[voteHandler] collect votes", "number", numberOfVotesInPool)
|
||||
go x.ForensicsProcessor.DetectEquivocationInVotePool(voteMsg, x.votePool)
|
||||
go x.ForensicsProcessor.ProcessVoteEquivocation(chain, x, voteMsg)
|
||||
|
||||
certThreshold := x.config.V2.Config(uint64(voteMsg.ProposedBlockInfo.Round)).CertThreshold
|
||||
thresholdReached := numberOfVotesInPool >= certThreshold
|
||||
if thresholdReached {
|
||||
log.Info(fmt.Sprintf("[voteHandler] Vote pool threashold reached: %v, number of items in the pool: %v", thresholdReached, numberOfVotesInPool))
|
||||
|
||||
// Check if the block already exist, otherwise we try luck with the next vote
|
||||
proposedBlockHeader := chain.GetHeaderByHash(voteMsg.ProposedBlockInfo.Hash)
|
||||
if proposedBlockHeader == nil {
|
||||
log.Warn("[voteHandler] The proposed block from vote message does not exist yet, wait for the next vote to try again", "blockNum", voteMsg.ProposedBlockInfo.Number, "Hash", voteMsg.ProposedBlockInfo.Hash, "Round", voteMsg.ProposedBlockInfo.Round)
|
||||
return nil
|
||||
}
|
||||
|
||||
err := x.VerifyBlockInfo(chain, voteMsg.ProposedBlockInfo, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
x.verifyVotes(chain, pooledVotes, proposedBlockHeader)
|
||||
|
||||
err = x.onVotePoolThresholdReached(chain, pooledVotes, voteMsg, proposedBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
elapsed := time.Since(x.votePoolCollectionTime)
|
||||
log.Info("[voteHandler] time cost from receive first vote under QC create", "elapsed", elapsed)
|
||||
x.votePoolCollectionTime = time.Time{}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) verifyVotes(chain consensus.ChainReader, votes map[common.Hash]utils.PoolObj, header *types.Header) {
|
||||
masternodes := x.GetMasternodes(chain, header)
|
||||
start := time.Now()
|
||||
emptySigner := common.Address{}
|
||||
// Filter out non-Master nodes signatures
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(votes))
|
||||
for h, vote := range votes {
|
||||
go func(hash common.Hash, v *types.Vote) {
|
||||
defer wg.Done()
|
||||
if v.GetSigner() != emptySigner {
|
||||
// verify before
|
||||
return
|
||||
}
|
||||
signedVote := types.VoteSigHash(&types.VoteForSign{
|
||||
ProposedBlockInfo: v.ProposedBlockInfo,
|
||||
GapNumber: v.GapNumber,
|
||||
})
|
||||
verified, masterNode, err := x.verifyMsgSignature(signedVote, v.Signature, masternodes)
|
||||
if err != nil {
|
||||
log.Warn("[verifyVotes] error while verifying vote signature", "error", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if !verified {
|
||||
log.Warn("[verifyVotes] non-verified vote signature", "verified", verified)
|
||||
return
|
||||
}
|
||||
v.SetSigner(masterNode)
|
||||
}(h, vote.(*types.Vote))
|
||||
}
|
||||
wg.Wait()
|
||||
elapsed := time.Since(start)
|
||||
log.Debug("[verifyVotes] verify message signatures of vote pool took", "elapsed", elapsed)
|
||||
}
|
||||
|
||||
/*
|
||||
Function that will be called by votePool when it reached threshold.
|
||||
In the engine v2, we will need to generate and process QC
|
||||
*/
|
||||
func (x *XDPoS_v2) onVotePoolThresholdReached(chain consensus.ChainReader, pooledVotes map[common.Hash]utils.PoolObj, currentVoteMsg utils.PoolObj, proposedBlockHeader *types.Header) error {
|
||||
// The signature list may contain empty entey. we only care the ones with values
|
||||
var validSignatures []types.Signature
|
||||
emptySigner := common.Address{}
|
||||
for _, vote := range pooledVotes {
|
||||
if vote.GetSigner() != emptySigner {
|
||||
validSignatures = append(validSignatures, vote.(*types.Vote).Signature)
|
||||
}
|
||||
}
|
||||
|
||||
// Skip and wait for the next vote to process again if valid votes is less than what we required
|
||||
certThreshold := x.config.V2.Config(uint64(currentVoteMsg.(*types.Vote).ProposedBlockInfo.Round)).CertThreshold
|
||||
if len(validSignatures) < certThreshold {
|
||||
log.Warn("[onVotePoolThresholdReached] Not enough valid signatures to generate QC", "VotesSignaturesAfterFilter", validSignatures, "NumberOfValidVotes", len(validSignatures), "NumberOfVotes", len(pooledVotes))
|
||||
return nil
|
||||
}
|
||||
// Genrate QC
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: currentVoteMsg.(*types.Vote).ProposedBlockInfo,
|
||||
Signatures: validSignatures,
|
||||
GapNumber: currentVoteMsg.(*types.Vote).GapNumber,
|
||||
}
|
||||
err := x.processQC(chain, quorumCert)
|
||||
if err != nil {
|
||||
log.Error("Error while processing QC in the Vote handler after reaching pool threshold, ", err)
|
||||
return err
|
||||
}
|
||||
log.Info("Successfully processed the vote and produced QC!", "QcRound", quorumCert.ProposedBlockInfo.Round, "QcNumOfSig", len(quorumCert.Signatures), "QcHash", quorumCert.ProposedBlockInfo.Hash, "QcNumber", quorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Hot stuff rule to decide whether this node is eligible to vote for the received block
|
||||
func (x *XDPoS_v2) verifyVotingRule(blockChainReader consensus.ChainReader, blockInfo *types.BlockInfo, quorumCert *types.QuorumCert) (bool, error) {
|
||||
// Make sure this node has not voted for this round.
|
||||
if x.currentRound <= x.highestVotedRound {
|
||||
log.Warn("Failed to pass the voting rule verification, currentRound is not large then highestVoteRound", "x.currentRound", x.currentRound, "x.highestVotedRound", x.highestVotedRound)
|
||||
return false, nil
|
||||
}
|
||||
/*
|
||||
HotStuff Voting rule:
|
||||
header's round == local current round, AND (one of the following two:)
|
||||
header's block extends lockQuorumCert's ProposedBlockInfo (we need a isExtending(block_a, block_b) function), OR
|
||||
header's QC's ProposedBlockInfo.Round > lockQuorumCert's ProposedBlockInfo.Round
|
||||
*/
|
||||
if blockInfo.Round != x.currentRound {
|
||||
log.Warn("Failed to pass the voting rule verification, blockRound is not equal currentRound", "x.currentRound", x.currentRound, "blockInfo.Round", blockInfo.Round)
|
||||
return false, nil
|
||||
}
|
||||
// XDPoS v1.0 switch to v2.0, the proposed block can always pass voting rule
|
||||
if x.lockQuorumCert == nil {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if quorumCert.ProposedBlockInfo.Round > x.lockQuorumCert.ProposedBlockInfo.Round {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
isExtended, err := x.isExtendingFromAncestor(blockChainReader, blockInfo, x.lockQuorumCert.ProposedBlockInfo)
|
||||
if err != nil {
|
||||
log.Error("Failed to pass the voting rule verification, error on isExtendingFromAncestor", "err", err, "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo)
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !isExtended {
|
||||
log.Warn("Failed to pass the voting rule verification, block is not extended from ancestor", "blockInfo", blockInfo, "x.lockQuorumCert.ProposedBlockInfo", x.lockQuorumCert.ProposedBlockInfo)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) isExtendingFromAncestor(blockChainReader consensus.ChainReader, currentBlock *types.BlockInfo, ancestorBlock *types.BlockInfo) (bool, error) {
|
||||
blockNumDiff := int(big.NewInt(0).Sub(currentBlock.Number, ancestorBlock.Number).Int64())
|
||||
|
||||
nextBlockHash := currentBlock.Hash
|
||||
for i := 0; i < blockNumDiff; i++ {
|
||||
parentBlock := blockChainReader.GetHeaderByHash(nextBlockHash)
|
||||
if parentBlock == nil {
|
||||
return false, fmt.Errorf("Could not find its parent block when checking whether currentBlock %v with hash %v is extending from the ancestorBlock %v", currentBlock.Number, currentBlock.Hash, ancestorBlock.Number)
|
||||
} else {
|
||||
nextBlockHash = parentBlock.ParentHash
|
||||
}
|
||||
log.Debug("[isExtendingFromAncestor] Found parent block", "CurrentBlockHash", currentBlock.Hash, "ParentHash", nextBlockHash)
|
||||
}
|
||||
|
||||
if nextBlockHash == ancestorBlock.Hash {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) hygieneVotePool() {
|
||||
x.lock.RLock()
|
||||
round := x.currentRound
|
||||
x.lock.RUnlock()
|
||||
votePoolKeys := x.votePool.PoolObjKeysList()
|
||||
|
||||
// Extract round number
|
||||
for _, k := range votePoolKeys {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
if err != nil {
|
||||
log.Error("[hygieneVotePool] Error while trying to get keyedRound inside pool", "Error", err)
|
||||
continue
|
||||
}
|
||||
// Clean up any votes round that is 10 rounds older
|
||||
if keyedRound < int64(round)-utils.PoolHygieneRound {
|
||||
log.Debug("[hygieneVotePool] Cleaned vote poll at round", "Round", keyedRound, "currentRound", round, "Key", k)
|
||||
x.votePool.ClearByPoolKey(k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *XDPoS_v2) ReceivedVotes() map[string]map[common.Hash]utils.PoolObj {
|
||||
return x.votePool.Get()
|
||||
}
|
||||
|
|
@ -15,7 +15,8 @@ var (
|
|||
NonceAuthVote = hexutil.MustDecode("0xffffffffffffffff") // Magic nonce number to vote on adding a new signer
|
||||
NonceDropVote = hexutil.MustDecode("0x0000000000000000") // Magic nonce number to vote on removing a signer.
|
||||
|
||||
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
||||
UncleHash = types.CalcUncleHash(nil) // Always Keccak256(RLP([])) as uncles are meaningless outside of PoW.
|
||||
InmemoryEpochs = 5 * EpochLength // Number of mapping from block to epoch switch infos to keep in memory
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -23,3 +24,8 @@ const (
|
|||
BlockSignersCacheLimit = 9000
|
||||
M2ByteLength = 4
|
||||
)
|
||||
|
||||
const (
|
||||
PeriodicJobPeriod = 60
|
||||
PoolHygieneRound = 10
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
package utils
|
||||
|
||||
import "errors"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
// Various error messages to mark blocks invalid. These should be private to
|
||||
// prevent engine specific errors from being referenced in the remainder of the
|
||||
|
|
@ -42,6 +47,9 @@ var (
|
|||
|
||||
ErrInvalidCheckpointPenalties = errors.New("invalid penalty list on checkpoint block")
|
||||
|
||||
ErrValidatorsNotLegit = errors.New("validators does not match what's stored in snapshot minutes its penalty")
|
||||
ErrPenaltiesNotLegit = errors.New("penalties does not match")
|
||||
|
||||
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
|
||||
ErrInvalidMixDigest = errors.New("non-zero mix digest")
|
||||
|
||||
|
|
@ -60,6 +68,9 @@ var (
|
|||
// be modified via out-of-range or non-contiguous headers.
|
||||
ErrInvalidVotingChain = errors.New("invalid voting chain")
|
||||
|
||||
ErrInvalidHeaderOrder = errors.New("invalid header order")
|
||||
ErrInvalidChild = errors.New("invalid header child")
|
||||
|
||||
// errUnauthorized is returned if a header is signed by a non-authorized entity.
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
|
||||
|
|
@ -71,4 +82,41 @@ var (
|
|||
ErrWaitTransactions = errors.New("waiting for transactions")
|
||||
|
||||
ErrInvalidCheckpointValidators = errors.New("invalid validators list on checkpoint block")
|
||||
|
||||
ErrEmptyEpochSwitchValidators = errors.New("empty validators list on epoch switch block")
|
||||
|
||||
ErrInvalidV2Extra = errors.New("Invalid v2 extra in the block")
|
||||
ErrInvalidQC = errors.New("Invalid QC content")
|
||||
ErrInvalidQCSignatures = errors.New("Invalid QC Signatures")
|
||||
ErrInvalidTC = errors.New("Invalid TC content")
|
||||
ErrInvalidTCSignatures = errors.New("Invalid TC Signatures")
|
||||
ErrEmptyBlockInfoHash = errors.New("BlockInfo hash is empty")
|
||||
ErrInvalidFieldInNonEpochSwitch = errors.New("Invalid field exist in a non-epoch swtich block")
|
||||
ErrValidatorNotWithinMasternodes = errors.New("Validaotor address is not in the master node list")
|
||||
ErrCoinbaseAndValidatorMismatch = errors.New("Validaotor and coinbase address in header does not match")
|
||||
ErrNotItsTurn = errors.New("Not validator's turn to mine this block")
|
||||
|
||||
ErrRoundInvalid = errors.New("Invalid Round, it shall be bigger than QC round")
|
||||
|
||||
ErrAlreadyMined = errors.New("Already mined")
|
||||
)
|
||||
|
||||
type ErrIncomingMessageRoundNotEqualCurrentRound struct {
|
||||
Type string
|
||||
IncomingRound types.Round
|
||||
CurrentRound types.Round
|
||||
}
|
||||
|
||||
func (e *ErrIncomingMessageRoundNotEqualCurrentRound) Error() string {
|
||||
return fmt.Sprintf("%s message round number: %v does not match currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
|
||||
}
|
||||
|
||||
type ErrIncomingMessageRoundTooFarFromCurrentRound struct {
|
||||
Type string
|
||||
IncomingRound types.Round
|
||||
CurrentRound types.Round
|
||||
}
|
||||
|
||||
func (e *ErrIncomingMessageRoundTooFarFromCurrentRound) Error() string {
|
||||
return fmt.Sprintf("%s message round number: %v is too far away from currentRound: %v", e.Type, e.IncomingRound, e.CurrentRound)
|
||||
}
|
||||
|
|
|
|||
102
consensus/XDPoS/utils/pool.go
Normal file
102
consensus/XDPoS/utils/pool.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type PoolObj interface {
|
||||
Hash() common.Hash
|
||||
PoolKey() string
|
||||
GetSigner() common.Address
|
||||
}
|
||||
type Pool struct {
|
||||
objList map[string]map[common.Hash]PoolObj
|
||||
lock sync.RWMutex // Protects the pool fields
|
||||
}
|
||||
|
||||
func NewPool() *Pool {
|
||||
return &Pool{
|
||||
objList: make(map[string]map[common.Hash]PoolObj),
|
||||
}
|
||||
}
|
||||
func (p *Pool) Get() map[string]map[common.Hash]PoolObj {
|
||||
return p.objList
|
||||
}
|
||||
|
||||
// return true if it has reached threshold
|
||||
func (p *Pool) Add(obj PoolObj) (int, map[common.Hash]PoolObj) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
poolKey := obj.PoolKey()
|
||||
objListKeyed, ok := p.objList[poolKey]
|
||||
if !ok {
|
||||
p.objList[poolKey] = make(map[common.Hash]PoolObj)
|
||||
objListKeyed = p.objList[poolKey]
|
||||
}
|
||||
objListKeyed[obj.Hash()] = obj
|
||||
numOfItems := len(objListKeyed)
|
||||
return numOfItems, objListKeyed
|
||||
}
|
||||
|
||||
func (p *Pool) Size(obj PoolObj) int {
|
||||
poolKey := obj.PoolKey()
|
||||
objListKeyed, ok := p.objList[poolKey]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
return len(objListKeyed)
|
||||
}
|
||||
|
||||
func (p *Pool) PoolObjKeysList() []string {
|
||||
p.lock.RLock()
|
||||
defer p.lock.RUnlock()
|
||||
|
||||
var keyList []string
|
||||
for key := range p.objList {
|
||||
keyList = append(keyList, key)
|
||||
}
|
||||
return keyList
|
||||
}
|
||||
|
||||
// Given the pool object, clear all object under the same pool key
|
||||
func (p *Pool) ClearPoolKeyByObj(obj PoolObj) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
poolKey := obj.PoolKey()
|
||||
delete(p.objList, poolKey)
|
||||
}
|
||||
|
||||
// Given the pool key, clean its content
|
||||
func (p *Pool) ClearByPoolKey(poolKey string) {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
delete(p.objList, poolKey)
|
||||
}
|
||||
|
||||
func (p *Pool) Clear() {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
p.objList = make(map[string]map[common.Hash]PoolObj)
|
||||
}
|
||||
|
||||
func (p *Pool) GetObjsByKey(poolKey string) []PoolObj {
|
||||
p.lock.Lock()
|
||||
defer p.lock.Unlock()
|
||||
|
||||
objListKeyed, ok := p.objList[poolKey]
|
||||
if !ok {
|
||||
return []PoolObj{}
|
||||
}
|
||||
objList := make([]PoolObj, len(objListKeyed))
|
||||
cnt := 0
|
||||
for _, obj := range objListKeyed {
|
||||
objList[cnt] = obj
|
||||
cnt += 1
|
||||
}
|
||||
return objList
|
||||
}
|
||||
59
consensus/XDPoS/utils/pool_test.go
Normal file
59
consensus/XDPoS/utils/pool_test.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPoolAdd(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
pool := NewPool()
|
||||
timeout1 := types.Timeout{Round: 1, Signature: []byte{1}}
|
||||
timeout2 := types.Timeout{Round: 1, Signature: []byte{2}}
|
||||
timeout3 := types.Timeout{Round: 1, Signature: []byte{3}}
|
||||
timeout4 := types.Timeout{Round: 1, Signature: []byte{4}}
|
||||
numOfItems, pooledTimeouts := pool.Add(&timeout1)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout1)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
// Duplicates should not be added
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
// Should add the one that is not a duplicates
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout2)
|
||||
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(2, numOfItems)
|
||||
|
||||
// Try to add one more to the same round, it should also trigger threshold
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout3)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(3, numOfItems)
|
||||
|
||||
// Only after manually clearned the pool at its objKey, we shall not have any value for this particular key
|
||||
pool.ClearPoolKeyByObj(&timeout3)
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout4)
|
||||
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
pool = NewPool()
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout1)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
assert.Equal(1, numOfItems)
|
||||
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout2)
|
||||
|
||||
assert.Equal(2, numOfItems)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
pool.Clear()
|
||||
|
||||
// Pool has been cleared. Start from 0 again
|
||||
numOfItems, pooledTimeouts = pool.Add(&timeout3)
|
||||
assert.Equal(1, numOfItems)
|
||||
assert.NotNil(pooledTimeouts)
|
||||
}
|
||||
|
|
@ -2,16 +2,14 @@ package utils
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
|
|
@ -52,100 +50,51 @@ func ExtractValidatorsFromBytes(byteValidators []byte) []int64 {
|
|||
return validators
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header.
|
||||
func GetMasternodesFromCheckpointHeader(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-ExtraVanity-ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
// Get m2 list from checkpoint block.
|
||||
func GetM1M2FromCheckpointHeader(checkpointHeader *types.Header, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, error) {
|
||||
if checkpointHeader.Number.Uint64()%common.EpocBlockRandomize != 0 {
|
||||
return nil, errors.New("This block is not checkpoint block epoc.")
|
||||
}
|
||||
// Get signers from this block.
|
||||
masternodes := GetMasternodesFromCheckpointHeader(checkpointHeader)
|
||||
validators := ExtractValidatorsFromBytes(checkpointHeader.Validators)
|
||||
m1m2, _, err := GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
return map[common.Address]common.Address{}, err
|
||||
}
|
||||
return m1m2, nil
|
||||
}
|
||||
|
||||
func GetM1M2(masternodes []common.Address, validators []int64, currentHeader *types.Header, config *params.ChainConfig) (map[common.Address]common.Address, uint64, error) {
|
||||
m1m2 := map[common.Address]common.Address{}
|
||||
maxMNs := len(masternodes)
|
||||
moveM2 := uint64(0)
|
||||
if len(validators) < maxMNs {
|
||||
return nil, moveM2, errors.New("len(m2) is less than len(m1)")
|
||||
}
|
||||
if maxMNs > 0 {
|
||||
isForked := config.IsTIPRandomize(currentHeader.Number)
|
||||
if isForked {
|
||||
moveM2 = ((currentHeader.Number.Uint64() % config.XDPoS.Epoch) / uint64(maxMNs)) % uint64(maxMNs)
|
||||
}
|
||||
for i, m1 := range masternodes {
|
||||
m2Index := uint64(validators[i] % int64(maxMNs))
|
||||
m2Index = (m2Index + moveM2) % uint64(maxMNs)
|
||||
m1m2[m1] = masternodes[m2Index]
|
||||
}
|
||||
}
|
||||
return m1m2, moveM2, nil
|
||||
}
|
||||
|
||||
// compare 2 signers lists
|
||||
// return true if they are same elements, otherwise return false
|
||||
func CompareSignersLists(list1 []common.Address, list2 []common.Address) bool {
|
||||
if len(list1) == 0 && len(list2) == 0 {
|
||||
l1 := make([]common.Address, len(list1))
|
||||
l2 := make([]common.Address, len(list2))
|
||||
|
||||
copy(l1, list1)
|
||||
copy(l2, list2)
|
||||
|
||||
if len(l1) == 0 && len(l2) == 0 {
|
||||
return true
|
||||
}
|
||||
sort.Slice(list1, func(i, j int) bool {
|
||||
return list1[i].String() <= list1[j].String()
|
||||
})
|
||||
sort.Slice(list2, func(i, j int) bool {
|
||||
return list2[i].String() <= list2[j].String()
|
||||
})
|
||||
return reflect.DeepEqual(list1, list2)
|
||||
}
|
||||
|
||||
// SignerFn is a signer callback function to request a hash to be signed by a
|
||||
// backing account.
|
||||
//type SignerFn func(accounts.Account, []byte) ([]byte, error)
|
||||
|
||||
// sigHash returns the hash which is used as input for the delegated-proof-of-stake
|
||||
// signing. It is the hash of the entire header apart from the 65 byte signature
|
||||
// contained at the end of the extra data.
|
||||
//
|
||||
// Note, the method requires the extra data to be at least 65 bytes, otherwise it
|
||||
// panics. This is done to avoid accidentally using both forms (signature present
|
||||
// or not), which could be abused to produce different hashes for the same header.
|
||||
func SigHash(header *types.Header) (hash common.Hash) {
|
||||
hasher := sha3.NewKeccak256()
|
||||
|
||||
err := rlp.Encode(hasher, []interface{}{
|
||||
header.ParentHash,
|
||||
header.UncleHash,
|
||||
header.Coinbase,
|
||||
header.Root,
|
||||
header.TxHash,
|
||||
header.ReceiptHash,
|
||||
header.Bloom,
|
||||
header.Difficulty,
|
||||
header.Number,
|
||||
header.GasLimit,
|
||||
header.GasUsed,
|
||||
header.Time,
|
||||
header.Extra[:len(header.Extra)-65], // Yes, this will panic if extra is too short
|
||||
header.MixDigest,
|
||||
header.Nonce,
|
||||
})
|
||||
if err != nil {
|
||||
log.Debug("Fail to encode", err)
|
||||
if len(l1) != len(l2) {
|
||||
return false
|
||||
}
|
||||
hasher.Sum(hash[:0])
|
||||
return hash
|
||||
|
||||
sort.Slice(l1, func(i, j int) bool {
|
||||
return l1[i].String() <= l1[j].String()
|
||||
})
|
||||
sort.Slice(l2, func(i, j int) bool {
|
||||
return l2[i].String() <= l2[j].String()
|
||||
})
|
||||
return reflect.DeepEqual(l1, l2)
|
||||
}
|
||||
|
||||
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
|
||||
func DecodeBytesExtraFields(b []byte, val interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("extra field is 0 length")
|
||||
}
|
||||
switch b[0] {
|
||||
case 2:
|
||||
return rlp.DecodeBytes(b[1:], val)
|
||||
default:
|
||||
return fmt.Errorf("consensus version %d is not defined, or this block is v1 block", b[0])
|
||||
}
|
||||
}
|
||||
|
||||
func rlpHash(x interface{}) (h common.Hash) {
|
||||
hw := sha3.NewKeccak256()
|
||||
err := rlp.Encode(hw, x)
|
||||
if err != nil {
|
||||
log.Error("[rlpHash] Fail to hash item", "Error", err)
|
||||
}
|
||||
hw.Sum(h[:0])
|
||||
return h
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +1,11 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
func TestGetM1M2FromCheckpointHeader(t *testing.T) {
|
||||
masternodes := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
common.StringToAddress("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"),
|
||||
common.StringToAddress("cccccccccccccccccccccccccccccccccccccccc"),
|
||||
}
|
||||
validators := []int64{
|
||||
2,
|
||||
1,
|
||||
0,
|
||||
}
|
||||
epoch := uint64(900)
|
||||
config := ¶ms.ChainConfig{
|
||||
XDPoS: ¶ms.XDPoSConfig{
|
||||
Epoch: uint64(epoch),
|
||||
},
|
||||
}
|
||||
testMoveM2 := []uint64{0, 0, 0, 1, 1, 1, 2, 2, 2, 0, 0, 0, 1, 1, 1, 2, 2, 2}
|
||||
//try from block 3410001 to 3410018
|
||||
for i := uint64(3464001); i <= 3464018; i++ {
|
||||
currentNumber := int64(i)
|
||||
currentHeader := &types.Header{
|
||||
Number: big.NewInt(currentNumber),
|
||||
}
|
||||
m1m2, moveM2, err := GetM1M2(masternodes, validators, currentHeader, config)
|
||||
if err != nil {
|
||||
t.Error("can't get m1m2", "err", err)
|
||||
}
|
||||
fmt.Printf("block: %v, moveM2: %v\n", currentHeader.Number.Int64(), moveM2)
|
||||
for _, k := range masternodes {
|
||||
fmt.Printf("m1: %v - m2: %v\n", k.Str(), m1m2[k].Str())
|
||||
}
|
||||
if moveM2 != testMoveM2[i-3464001] {
|
||||
t.Error("wrong moveM2", "currentNumber", currentNumber, "want", testMoveM2[i-3464001], "have", moveM2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompareSignersLists(t *testing.T) {
|
||||
list1 := []common.Address{
|
||||
common.StringToAddress("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
|
||||
|
|
|
|||
|
|
@ -38,4 +38,10 @@ var (
|
|||
ErrFailValidatorSignature = errors.New("missing validator in header")
|
||||
|
||||
ErrNoValidatorSignature = errors.New("no validator in header")
|
||||
|
||||
ErrNotReadyToPropose = errors.New("not ready to propose, QC is not ready")
|
||||
|
||||
ErrNotReadyToMine = errors.New("Not ready to mine, it's not your turn")
|
||||
|
||||
ErrCoinbaseMismatch = errors.New("Block Coinbase address does not match its wallte address")
|
||||
)
|
||||
|
|
|
|||
75
consensus/tests/engine_v1_tests/authorised_test.go
Normal file
75
consensus/tests/engine_v1_tests/authorised_test.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAuthorisedMNForConsensusV1(t *testing.T) {
|
||||
/*
|
||||
V1 consensus engine
|
||||
*/
|
||||
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 449
|
||||
t.Logf("Inserting block with propose at 449...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
||||
tx, err := voteTX(37117, 0, acc1Addr.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(449)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(block449)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
parentBlock = block449
|
||||
|
||||
// At block 449, we should not update signerList. we need to update it till block 450 gap block.
|
||||
// Acc3 is the default account that is on the signerList
|
||||
|
||||
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
isAuthorisedMN := engine.IsAuthorisedAddress(blockchain, block449.Header(), acc3Addr)
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block449.Header(), acc1Addr)
|
||||
assert.False(t, isAuthorisedMN)
|
||||
|
||||
// Now, let's mine another block to trigger the GAP block signerList update
|
||||
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(block450CoinbaseAddress),
|
||||
}
|
||||
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450)
|
||||
assert.Nil(t, err)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc3Addr)
|
||||
assert.False(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = engine.IsAuthorisedAddress(blockchain, block450.Header(), acc1Addr)
|
||||
assert.True(t, isAuthorisedMN)
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
|
@ -6,13 +6,15 @@ import (
|
|||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Should NOT update signerList if not on the gap block
|
||||
func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, 400, params.TestXDPoSMockChainConfig)
|
||||
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -26,11 +28,18 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 401, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(401)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -50,14 +59,22 @@ func TestNotUpdateSignerListIfNotOnGapBlock(t *testing.T) {
|
|||
|
||||
// Should call updateM1 at the gap block, and have the same snapshot values as the parent block if no SM transaction is involved
|
||||
func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
|
||||
blockchain, _, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, _, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Insert block 450
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", 450)
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block, err := insertBlock(blockchain, 450, blockCoinBase, parentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
parentSigners, err := GetSnapshotSigner(blockchain, parentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -78,7 +95,7 @@ func TestNotChangeSingerListIfNothingProposedOrVoted(t *testing.T) {
|
|||
//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved
|
||||
func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-2, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 449
|
||||
t.Logf("Inserting block with propose at 449...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000449"
|
||||
|
|
@ -89,10 +106,18 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block449, err := insertBlockTxs(blockchain, 449, blockCoinbaseA, parentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(449)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
block449, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block449)
|
||||
assert.Nil(t, err)
|
||||
parentBlock = block449
|
||||
|
||||
signers, err := GetSnapshotSigner(blockchain, block449.Header())
|
||||
|
|
@ -113,15 +138,23 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
// Now, let's mine another block to trigger the GAP block signerList update
|
||||
block450CoinbaseAddress := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block450, err := insertBlock(blockchain, 450, block450CoinbaseAddress, parentBlock, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(block450CoinbaseAddress),
|
||||
}
|
||||
block450, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block450)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block450.Header())
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get signers")
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
// Now, we voted acc 1 to be in the signerList, which will kick out acc3 because it has less funds
|
||||
if signers[acc3Addr.Hex()] == true {
|
||||
debugMessage(backend, signers, t)
|
||||
|
|
@ -136,7 +169,7 @@ func TestUpdateSignerListIfVotedBeforeGap(t *testing.T) {
|
|||
//Should call updateM1 before gap block, and update the snapshot if there are SM transactions involved
|
||||
func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
||||
|
||||
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Insert first Block 450 A
|
||||
t.Logf("Inserting block with propose at 450 A...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000450"
|
||||
|
|
@ -147,15 +180,23 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
|||
|
||||
//Get from block validator error message
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err := GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc1Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 1 should sit in the signer list")
|
||||
|
|
@ -165,7 +206,7 @@ func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) {
|
|||
// Should call updateM1 and update snapshot when a forked block(at gap block number) is inserted back into main chain (Edge case)
|
||||
func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
||||
|
||||
blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, currentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Check initial signer, by default, acc3 is in the signerList
|
||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||
if err != nil {
|
||||
|
|
@ -189,15 +230,23 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, blockA.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc1Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 1 should sit in the signer list")
|
||||
|
|
@ -217,14 +266,23 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, currentBlock, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
// Should not run the `updateM1` for forked chain, hence account3 still exit
|
||||
if signers[acc3Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
|
|
@ -240,8 +298,16 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -250,6 +316,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 2 should sit in the signer list")
|
||||
|
|
@ -263,6 +330,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 2 should sit in the signer list")
|
||||
|
|
@ -276,6 +344,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("acc2Addr should sit in the signer list")
|
||||
|
|
@ -288,7 +357,7 @@ func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) {
|
|||
|
||||
func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
|
||||
state, err := blockchain.State()
|
||||
if err != nil {
|
||||
|
|
@ -316,10 +385,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||
|
||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -334,6 +411,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc1Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 1 should sit in the signer list")
|
||||
|
|
@ -351,10 +429,18 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -367,6 +453,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
// Should not run the `updateM1` for forked chain, hence account3 still exit
|
||||
if signers[acc3Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
|
|
@ -378,16 +465,24 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 2 should sit in the signer list")
|
||||
|
|
@ -397,6 +492,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 2 should sit in the signer list")
|
||||
|
|
@ -406,6 +502,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc2Addr.Hex()] != true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("acc2Addr should sit in the signer list")
|
||||
|
|
@ -422,7 +519,7 @@ func TestStatesShouldBeUpdatedWhenForkedBlockBecameMainChainAtGapBlock(t *testin
|
|||
}
|
||||
|
||||
func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
// Check initial signer, by default, acc3 is in the signerList
|
||||
signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header())
|
||||
if err != nil {
|
||||
|
|
@ -440,10 +537,18 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
// Insert normal blocks 450 A
|
||||
blockCoinBase450A := "0xaaa0000000000000000000000000000000000450"
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block450A, err := insertBlock(blockchain, 450, blockCoinBase450A, parentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450A),
|
||||
}
|
||||
block450A, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450A)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Insert 451 A with vote
|
||||
blockCoinbase451A := "0xaaa0000000000000000000000000000000000451"
|
||||
|
|
@ -453,16 +558,25 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
}
|
||||
|
||||
merkleRoot = "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772"
|
||||
block451A, err := insertBlockTxs(blockchain, 451, blockCoinbase451A, block450A, []*types.Transaction{tx}, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450A.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbase451A),
|
||||
}
|
||||
block451A, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451A)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// SignerList should be unchanged as the vote happen after GAP block
|
||||
signers, err = GetSnapshotSigner(blockchain, block451A.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
if signers[acc1Addr.Hex()] == true {
|
||||
debugMessage(backend, signers, t)
|
||||
t.Fatalf("account 1 should NOT sit in the signer list")
|
||||
|
|
@ -476,28 +590,53 @@ func TestVoteShouldNotBeAffectedByFork(t *testing.T) {
|
|||
// Insert forked Block 450 B
|
||||
blockCoinBase450B := "0xbbb0000000000000000000000000000000000450"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block450B, err := insertBlock(blockchain, 450, blockCoinBase450B, parentBlock, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
}
|
||||
block450B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockCoinBase452B := "0xbbb0000000000000000000000000000000000452"
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block452B, err := insertBlock(blockchain, 452, blockCoinBase452B, block451B, merkleRoot, 1)
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(452)),
|
||||
ParentHash: block451B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase452B),
|
||||
}
|
||||
block452B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block452B)
|
||||
assert.Nil(t, err)
|
||||
signers, err = GetSnapshotSigner(blockchain, block452B.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
assert.Equal(t, common.MaxMasternodes, len(signers))
|
||||
|
||||
// Should run the `updateM1` for forked chain, but it should not be affected by the voted block 451A which is not on the mainchain anymore
|
||||
if signers[acc3Addr.Hex()] != true {
|
||||
|
|
@ -1,17 +1,19 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Snapshot try to read before blockchain is written
|
||||
func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
||||
|
||||
blockchain, backend, parentBlock := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
blockchain, backend, parentBlock, signer, signFn := PrepareXDCTestBlockChain(t, GAP-1, params.TestXDPoSMockChainConfig)
|
||||
|
||||
state, err := blockchain.State()
|
||||
if err != nil {
|
||||
|
|
@ -39,10 +41,20 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
transferTransaction := transferTx(t, acc1Addr, 999)
|
||||
|
||||
merkleRoot := "ea465415b60d88429f181fec9fae67c0f19cbf5a4fa10971d96d4faa57d96ffa"
|
||||
blockA, err := insertBlockTxs(blockchain, 450, blockCoinbaseA, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 1)
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
|
||||
blockA, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(blockA)
|
||||
assert.Nil(t, err)
|
||||
state, err = blockchain.State()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to get blockchain state")
|
||||
|
|
@ -74,10 +86,21 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
transferTransaction = transferTx(t, acc1Addr, 888)
|
||||
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block450B, err := insertBlockTxs(blockchain, 450, blockCoinBase450B, parentBlock, []*types.Transaction{tx, transferTransaction}, merkleRoot, 2)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(450)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase450B),
|
||||
Difficulty: big.NewInt(2),
|
||||
}
|
||||
|
||||
block450B, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx, transferTransaction}, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block450B)
|
||||
assert.Nil(t, err)
|
||||
if blockchain.CurrentHeader().Hash() != block450B.Hash() {
|
||||
t.Fatalf("the block with higher difficulty should be current header")
|
||||
}
|
||||
|
|
@ -104,11 +127,19 @@ func TestRaceConditionOnBlockchainReadAndWrite(t *testing.T) {
|
|||
|
||||
blockCoinBase451B := "0xbbb0000000000000000000000000000000000451"
|
||||
merkleRoot = "184edaddeafc2404248f896ae46be503ae68949896c8eb6b6ad43695581e5022"
|
||||
block451B, err := insertBlock(blockchain, 451, blockCoinBase451B, block450B, merkleRoot, 3)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(451)),
|
||||
ParentHash: block450B.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase451B),
|
||||
Difficulty: big.NewInt(3),
|
||||
}
|
||||
block451B, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block451B)
|
||||
assert.Nil(t, err)
|
||||
|
||||
signers, err = GetSnapshotSigner(blockchain, block450B.Header())
|
||||
if err != nil {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package consensus
|
||||
package engine_v1_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
|
@ -6,14 +6,17 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
. "github.com/XinFinOrg/XDPoSChain/core"
|
||||
|
|
@ -58,6 +61,16 @@ func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *te
|
|||
}
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
|
||||
|
||||
// initial helper backend
|
||||
|
|
@ -223,23 +236,49 @@ func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testi
|
|||
return ms
|
||||
}
|
||||
|
||||
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block) {
|
||||
// V1 consensus engine
|
||||
func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Preparation
|
||||
var err error
|
||||
// Authorise
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
|
||||
}
|
||||
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V1] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot, 1)
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(i)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentBlock = block
|
||||
}
|
||||
// Update Signer as there is no previous signer assigned
|
||||
|
|
@ -248,83 +287,96 @@ func PrepareXDCTestBlockChain(t *testing.T, numOfBlocks int, chainConfig *params
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock
|
||||
return blockchain, backend, currentBlock, signer, signFn
|
||||
}
|
||||
|
||||
// insert Block without transcation attached
|
||||
func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string, difficulty int64) (*types.Block, error) {
|
||||
block, err := createXDPoSTestBlock(
|
||||
blockchain,
|
||||
parentBlock.Hash().Hex(),
|
||||
blockCoinBase, blockNum, nil,
|
||||
"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
|
||||
common.HexToHash(root),
|
||||
difficulty,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte) *types.Block {
|
||||
currentBlock := startingBlock
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// reset extra
|
||||
header.Extra = []byte{}
|
||||
if len(header.Extra) < utils.ExtraVanity {
|
||||
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
|
||||
}
|
||||
header.Extra = header.Extra[:utils.ExtraVanity]
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
// masternodesFromV1LastEpoch = masternodes
|
||||
for _, masternode := range masternodes {
|
||||
header.Extra = append(header.Extra, masternode[:]...)
|
||||
}
|
||||
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
|
||||
|
||||
// Sign all the things for v1 block use v1 sigHash function
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
|
||||
}
|
||||
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
|
||||
}
|
||||
return block, nil
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
// insert Block with transcation attached
|
||||
func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string, difficulty int64) (*types.Block, error) {
|
||||
block, err := createXDPoSTestBlock(
|
||||
blockchain,
|
||||
parentBlock.Hash().Hex(),
|
||||
blockCoinBase, blockNum, txs,
|
||||
"0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c",
|
||||
common.HexToHash(root),
|
||||
difficulty,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
|
||||
if customHeader.Extra == nil {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
|
||||
}
|
||||
var difficulty *big.Int
|
||||
if customHeader.Difficulty == nil {
|
||||
difficulty = big.NewInt(1)
|
||||
} else {
|
||||
difficulty = customHeader.Difficulty
|
||||
}
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
// TODO: check if this is needed
|
||||
if len(txs) != 0 {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
|
||||
} else {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
}
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash, difficulty int64) (*types.Block, error) {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
//ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"
|
||||
//Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc"
|
||||
extraByte, _ := hex.DecodeString(extraSubstring)
|
||||
header := types.Header{
|
||||
ParentHash: common.HexToHash(parentHash),
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
// ReceiptHash: types.EmptyRootHash,
|
||||
ReceiptHash: common.HexToHash(receiptHash),
|
||||
Root: root,
|
||||
Coinbase: common.HexToAddress(coinbase),
|
||||
Difficulty: big.NewInt(difficulty),
|
||||
Number: big.NewInt(int64(number)),
|
||||
ParentHash: customHeader.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: customHeader.ReceiptHash,
|
||||
Root: customHeader.Root,
|
||||
Coinbase: customHeader.Coinbase,
|
||||
Difficulty: difficulty,
|
||||
Number: customHeader.Number,
|
||||
GasLimit: 1200000000,
|
||||
Time: big.NewInt(int64(number * 10)),
|
||||
Extra: extraByte,
|
||||
Time: big.NewInt(time.Now().Unix()),
|
||||
Extra: customHeader.Extra,
|
||||
Validator: customHeader.Validator,
|
||||
Validators: customHeader.Validators,
|
||||
Penalties: customHeader.Penalties,
|
||||
}
|
||||
|
||||
var block *types.Block
|
||||
if len(txs) == 0 {
|
||||
block = types.NewBlockWithHeader(&header)
|
||||
} else {
|
||||
|
||||
// Prepare Receipt
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when get state", err)
|
||||
}
|
||||
gp := new(GasPool).AddGas(header.GasLimit)
|
||||
// usedGas := uint64(0)
|
||||
|
||||
var gasUsed = new(uint64)
|
||||
var receipts types.Receipts
|
||||
|
|
@ -338,7 +390,6 @@ func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number in
|
|||
}
|
||||
|
||||
header.GasUsed = *gasUsed
|
||||
|
||||
block = types.NewBlock(&header, txs, nil, receipts)
|
||||
}
|
||||
|
||||
266
consensus/tests/engine_v2_tests/adaptor_test.go
Normal file
266
consensus/tests/engine_v2_tests/adaptor_test.go
Normal file
|
|
@ -0,0 +1,266 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAdaptorShouldGetAuthorForDifferentConsensusVersion(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
addressFromAdaptor, errorAdaptor := adaptor.Author(currentBlock.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV1Engine, errV1 := adaptor.EngineV1.Author(currentBlock.Header())
|
||||
if errV1 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v1")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V1 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV1Engine)
|
||||
|
||||
// Insert one more block to make it above 10, which means now we are on v2 of consensus engine
|
||||
// Insert block 901
|
||||
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(901)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(1, currentBlock, signer, signFn, nil)
|
||||
|
||||
block901, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
addressFromAdaptor, errorAdaptor = adaptor.Author(block901.Header())
|
||||
if errorAdaptor != nil {
|
||||
t.Fatalf("Failed while trying to get Author from adaptor")
|
||||
}
|
||||
addressFromV2Engine, errV2 := adaptor.EngineV2.Author(block901.Header())
|
||||
if errV2 != nil {
|
||||
t.Fatalf("Failed while trying to get Author from engine v2")
|
||||
}
|
||||
// Make sure the value is exactly the same as from V2 engine
|
||||
assert.Equal(t, addressFromAdaptor, addressFromV2Engine)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesFromCheckpointHeader(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
headerV1 := currentBlock.Header()
|
||||
headerV1.Extra = common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400")
|
||||
masternodesV1 := adaptor.GetMasternodesFromCheckpointHeader(headerV1)
|
||||
headerV2 := currentBlock.Header()
|
||||
headerV2.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1))
|
||||
headerV2.Validators = common.Hex2Bytes("0278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758c")
|
||||
headerV2.Extra = []byte{2}
|
||||
masternodesV2 := adaptor.GetMasternodesFromCheckpointHeader(headerV2)
|
||||
assert.True(t, reflect.DeepEqual(masternodesV1, masternodesV2), "GetMasternodesFromCheckpointHeader in adaptor for v1 v2 not equal", "v1", masternodesV1, "v2", masternodesV2)
|
||||
}
|
||||
func TestAdaptorIsEpochSwitch(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
header := currentBlock.Header()
|
||||
// v1
|
||||
header.Number.SetUint64(0)
|
||||
|
||||
isEpochSwitchBlock, epochNum, err := adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
assert.Equal(t, uint64(0), epochNum)
|
||||
header.Number.SetUint64(1)
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
// v2
|
||||
parentBlockInfo := &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(0),
|
||||
Number: big.NewInt(0).Set(blockchain.Config().XDPoS.V2.SwitchBlock),
|
||||
}
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err := extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(1),
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(1)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(2))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) - 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(101))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isEpochSwitchBlock, "header should be epoch switch", header)
|
||||
parentBlockInfo = &types.BlockInfo{
|
||||
Hash: header.ParentHash,
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 1,
|
||||
Number: big.NewInt(0).Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(100)),
|
||||
}
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: parentBlockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: types.Round(blockchain.Config().XDPoS.Epoch) + 2,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err = extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
header.Extra = extraBytes
|
||||
header.Number.Add(blockchain.Config().XDPoS.V2.SwitchBlock, big.NewInt(101))
|
||||
isEpochSwitchBlock, _, err = adaptor.IsEpochSwitch(header)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isEpochSwitchBlock, "header should not be epoch switch", header)
|
||||
}
|
||||
|
||||
func TestAdaptorGetMasternodesV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
|
||||
// block 901 is the first v2 block, and is treated as epoch switch block
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
adaptor.Initial(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, err)
|
||||
masternodes1 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.Equal(t, 5, len(masternodes1))
|
||||
masternodes1ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
|
||||
assert.True(t, reflect.DeepEqual(masternodes1, masternodes1ByNumber), "at block number", blockNum)
|
||||
for blockNum = 902; blockNum < 915; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
masternodes2 := adaptor.GetMasternodes(blockchain, currentBlock.Header())
|
||||
assert.True(t, reflect.DeepEqual(masternodes1, masternodes2), "at block number", blockNum)
|
||||
masternodes2ByNumber := adaptor.GetMasternodesByNumber(blockchain, currentBlock.NumberU64())
|
||||
assert.True(t, reflect.DeepEqual(masternodes2, masternodes2ByNumber), "at block number", blockNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCurrentEpochSwitchBlock(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// V1
|
||||
currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, big.NewInt(900))
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(900), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
|
||||
// V2
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
adaptor.Initial(blockchain, currentBlock.Header())
|
||||
|
||||
currentCheckpointNumber, epochNum, err = adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(901), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
|
||||
for blockNum = 902; blockNum < 915; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil, "")
|
||||
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
currentCheckpointNumber, epochNum, err := adaptor.GetCurrentEpochSwitchBlock(blockchain, currentBlock.Number())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(901), currentCheckpointNumber)
|
||||
assert.Equal(t, uint64(1), epochNum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetParentBlock(t *testing.T) {
|
||||
blockchain, _, block900, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// V1
|
||||
block := adaptor.FindParentBlockToAssign(blockchain, block900)
|
||||
assert.Equal(t, block, block900)
|
||||
|
||||
// Initialise
|
||||
err := adaptor.EngineV2.Initial(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
|
||||
// V2
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
block901 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block900, blockNum, 1, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// let's inject another one, but the highestedQC has not been updated, so it shall still point to 900
|
||||
blockNum = 902
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block901, blockNum, 1, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
block = adaptor.FindParentBlockToAssign(blockchain, block902)
|
||||
|
||||
assert.Equal(t, block900.Hash(), block.Hash())
|
||||
}
|
||||
114
consensus/tests/engine_v2_tests/authorised_masternode_test.go
Normal file
114
consensus/tests/engine_v2_tests/authorised_masternode_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsAuthorisedMNForConsensusV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 902
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
// As long as the address is in the master node list, they are all valid
|
||||
isAuthorisedMN := adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.True(t, isAuthorisedMN)
|
||||
|
||||
isAuthorisedMN = adaptor.IsAuthorisedAddress(blockchain, currentBlock.Header(), common.HexToAddress("xdcbanana"))
|
||||
assert.False(t, isAuthorisedMN)
|
||||
}
|
||||
|
||||
func TestIsYourTurnConsensusV2(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
minePeriod := params.UnitTestV2Configs[0].MinePeriod
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 901
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 1, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
currentBlockHeader := currentBlock.Header()
|
||||
currentBlockHeader.Time = big.NewInt(time.Now().Unix())
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
adaptor.Initial(blockchain, currentBlockHeader)
|
||||
|
||||
// Less then Mine Period
|
||||
isYourTurn, err := adaptor.YourTurn(blockchain, currentBlockHeader, common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
// The second address is valid as the round starting from 1
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlockHeader, common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isYourTurn)
|
||||
|
||||
// The first and third address are not valid
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc703c4b2bD70c169f5717101CaeE543299Fc946C7"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
// We continue to grow the chain which will increase the round number
|
||||
blockNum = 902
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, 2, false)
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc71562b71999873DB5b286dF957af199Ec94617F7"))
|
||||
assert.True(t, isYourTurn)
|
||||
|
||||
isYourTurn, _ = adaptor.YourTurn(blockchain, currentBlock.Header(), common.HexToAddress("xdc5F74529C0338546f82389402a01c31fB52c6f434"))
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
}
|
||||
|
||||
func TestIsYourTurnConsensusV2CrossConfig(t *testing.T) {
|
||||
// we skip test for v1 since it's hard to make a real genesis block
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 909, params.TestXDPoSMockChainConfig, nil)
|
||||
firstMinePeriod := blockchain.Config().XDPoS.V2.CurrentConfig.MinePeriod
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
blockNum := 910 // 910 is new config switch block
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 10, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
currentBlockHeader := currentBlock.Header()
|
||||
currentBlockHeader.Time = big.NewInt(time.Now().Unix())
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
// after first mine period
|
||||
time.Sleep(time.Duration(firstMinePeriod) * time.Second)
|
||||
isYourTurn, err := adaptor.YourTurn(blockchain, currentBlockHeader, common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, isYourTurn)
|
||||
|
||||
adaptor.UpdateParams(currentBlockHeader) // it will be triggered automatically on the real code by other process
|
||||
|
||||
// after new mine period
|
||||
secondMinePeriod := blockchain.Config().XDPoS.V2.CurrentConfig.MinePeriod
|
||||
|
||||
time.Sleep(time.Duration(secondMinePeriod-firstMinePeriod) * time.Second)
|
||||
isYourTurn, err = adaptor.YourTurn(blockchain, currentBlockHeader, common.HexToAddress("xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"))
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, isYourTurn)
|
||||
}
|
||||
54
consensus/tests/engine_v2_tests/commit_test.go
Normal file
54
consensus/tests/engine_v2_tests/commit_test.go
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNormalReorgWhenNotInvolveCommittedBlock(t *testing.T) {
|
||||
// create 3 forking blockss, so the committed block is not in the forking numbers
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 3
|
||||
blockchain, _, currentBlock, signer, signFn, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks})
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
engineV2.ProcessQCFaker(blockchain, extraField.QuorumCert)
|
||||
assert.Equal(t, uint64(903), engineV2.GetLatestCommittedBlockInfo().Number.Uint64())
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, forkedBlock, int(forkedBlock.NumberU64())+1, int64(extraField.Round)+10, blockCoinBase, signer, signFn, nil, nil, forkedBlock.Header().Root.Hex())
|
||||
err = blockchain.InsertBlock(newBlock)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestShouldNotReorgCommittedBlock(t *testing.T) {
|
||||
// create 4 forking blocks, so the committed block is in the forking numbers
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 4
|
||||
blockchain, _, currentBlock, signer, signFn, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks})
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
engineV2.ProcessQCFaker(blockchain, extraField.QuorumCert)
|
||||
assert.Equal(t, uint64(903), engineV2.GetLatestCommittedBlockInfo().Number.Uint64())
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
newBlock := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, forkedBlock, int(forkedBlock.NumberU64())+1, int64(extraField.Round)+10, blockCoinBase, signer, signFn, nil, nil, forkedBlock.Header().Root.Hex())
|
||||
err = blockchain.InsertBlock(newBlock)
|
||||
assert.NotNil(t, err)
|
||||
assert.True(t, strings.Contains(err.Error(), "reorg"))
|
||||
assert.True(t, strings.Contains(err.Error(), "attack"))
|
||||
}
|
||||
434
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
434
consensus/tests/engine_v2_tests/forensics_test.go
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestProcessQcShallSetForensicsCommittedQc(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Assuming we are getting block 906 which have QC pointing at block 905
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create another vote which is signed by someone not from the master node list
|
||||
randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: randomlySignedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
time.Sleep(5000 * time.Millisecond)
|
||||
assert.Equal(t, 3, len(engineV2.GetForensicsFaker().HighestCommittedQCs))
|
||||
}
|
||||
|
||||
func TestSetCommittedQCsInOrder(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
var headers []types.Header
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the qc1 and qc2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(902)), *decodedExtraField.QuorumCert)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "headers shall be on the same chain and in the right order", err.Error())
|
||||
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(904)), *decodedExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(forensics.HighestCommittedQCs))
|
||||
|
||||
// Test previous blocks
|
||||
err = utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(904).Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(902), *blockchain.GetHeaderByNumber(903)), *decodedExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 3, len(forensics.HighestCommittedQCs))
|
||||
}
|
||||
|
||||
// Happty path
|
||||
func TestForensicsMonitoring(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
var decodedCurrentblockExtraField types.ExtraFields_v2
|
||||
// Decode the QC from latest block
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Header().Extra, &decodedCurrentblockExtraField)
|
||||
assert.Nil(t, err)
|
||||
incomingQC := decodedCurrentblockExtraField.QuorumCert
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 905, 906 and 907
|
||||
var headers []types.Header
|
||||
var decodedBlock905ExtraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(905).Extra, &decodedBlock905ExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(903), *blockchain.GetHeaderByNumber(904)), *decodedBlock905ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
var newIncomingQcHeaders []types.Header
|
||||
newIncomingQcHeaders = append(newIncomingQcHeaders, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914))
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, newIncomingQcHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestForensicsMonitoringNotOnSameChainButHaveSameRoundQC(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 1
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 913, 914 and 915
|
||||
var headers []types.Header
|
||||
var decodedBlock915ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(915).Extra, &decodedBlock915ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914)), *decodedBlock915ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
assert.False(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(13), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(913), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 9, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 5, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(13), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(912), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 8, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 5, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestForensicsMonitoringNotOnSameChainDoNotHaveSameRoundQC(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 10
|
||||
var forkedChainSignersKey []*ecdsa.PrivateKey
|
||||
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 913, 914 and 915
|
||||
var headers []types.Header
|
||||
var decodedBlock915ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(915).Extra, &decodedBlock915ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914)), *decodedBlock915ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
|
||||
assert.False(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(14), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(914), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 5, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(16), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(906), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// "prone to attack" test where the "across epoch" field is true
|
||||
func TestForensicsAcrossEpoch(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 10
|
||||
var forkedChainSignersKey []*ecdsa.PrivateKey
|
||||
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 1801, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 1799, 1800 and 1801
|
||||
var headers []types.Header
|
||||
var decodedBlock1801ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(1801).Extra, &decodedBlock1801ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(1799), *blockchain.GetHeaderByNumber(1800)), *decodedBlock1801ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(currentForkBlock.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
var forkedHeaders []types.Header
|
||||
parentOfForkedHeader := blockchain.GetBlockByHash(currentForkBlock.ParentHash()).Header()
|
||||
grandParentOfForkedHeader := blockchain.GetBlockByHash(parentOfForkedHeader.ParentHash).Header()
|
||||
forkedHeaders = append(forkedHeaders, *grandParentOfForkedHeader, *parentOfForkedHeader)
|
||||
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ForensicsMonitoring(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, forkedHeaders, *incomingQC)
|
||||
assert.Nil(t, err)
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case forensics := <-forensicsEventCh:
|
||||
assert.NotNil(t, forensics.ForensicsProof)
|
||||
assert.Equal(t, "QC", forensics.ForensicsProof.ForensicsType)
|
||||
content := &types.ForensicsContent{}
|
||||
json.Unmarshal([]byte(forensics.ForensicsProof.Content), &content)
|
||||
|
||||
idToCompare := content.DivergingBlockHash + ":" + content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Hash.Hex() + ":" + content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Hash.Hex()
|
||||
assert.Equal(t, idToCompare, forensics.ForensicsProof.Id)
|
||||
assert.True(t, content.AcrossEpoch)
|
||||
assert.Equal(t, types.Round(900), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(1800), content.SmallerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 10, len(content.SmallerRoundInfo.HashPath))
|
||||
assert.Equal(t, 5, len(content.SmallerRoundInfo.SignerAddresses))
|
||||
assert.Equal(t, types.Round(902), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, uint64(1792), content.LargerRoundInfo.QuorumCert.ProposedBlockInfo.Number.Uint64())
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.HashPath))
|
||||
assert.Equal(t, 2, len(content.LargerRoundInfo.SignerAddresses))
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoteEquivocationSameRound(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 1
|
||||
blockchain, _, currentBlock, signer, signFn, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks})
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
// Set up forensics events trigger
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(901),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentForkBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(901),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
signedHash, err = signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
for {
|
||||
select {
|
||||
case msg := <-forensicsEventCh:
|
||||
assert.NotNil(t, msg.ForensicsProof)
|
||||
assert.Equal(t, "Vote", msg.ForensicsProof.ForensicsType)
|
||||
content := &types.VoteEquivocationContent{}
|
||||
json.Unmarshal([]byte(msg.ForensicsProof.Content), &content)
|
||||
assert.Equal(t, types.Round(5), content.SmallerRoundVote.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(5), content.LargerRoundVote.ProposedBlockInfo.Round)
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoteEquivocationDifferentRound(t *testing.T) {
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 10
|
||||
var forkRoundDifference = new(int)
|
||||
*forkRoundDifference = 1
|
||||
var forkedChainSignersKey []*ecdsa.PrivateKey
|
||||
forkedChainSignersKey = append(forkedChainSignersKey, acc1Key)
|
||||
blockchain, _, _, _, _, currentForkBlock := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks, forkedRoundDifference: forkRoundDifference, signersKey: forkedChainSignersKey})
|
||||
forensics := blockchain.Engine().(*XDPoS.XDPoS).EngineV2.GetForensicsFaker()
|
||||
|
||||
// Now, let's try set committed blocks, where the highestedCommitted blocks are 913, 914 and 915
|
||||
var headers []types.Header
|
||||
var decodedBlock915ExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(blockchain.GetHeaderByNumber(915).Extra, &decodedBlock915ExtraField)
|
||||
assert.Nil(t, err)
|
||||
err = forensics.SetCommittedQCs(append(headers, *blockchain.GetHeaderByNumber(913), *blockchain.GetHeaderByNumber(914)), *decodedBlock915ExtraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// find fork block 913
|
||||
forkBlock913 := blockchain.GetBlockByHash(blockchain.GetBlockByHash(currentForkBlock.ParentHash()).ParentHash())
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
// Decode the QC from forking chain
|
||||
err = utils.DecodeBytesExtraFields(forkBlock913.Header().Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
|
||||
incomingQC := decodedExtraField.QuorumCert
|
||||
// choose just one vote from it
|
||||
voteForSign := &types.VoteForSign{ProposedBlockInfo: incomingQC.ProposedBlockInfo, GapNumber: incomingQC.GapNumber}
|
||||
voteForSign.ProposedBlockInfo.Round = types.Round(16)
|
||||
signature := SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
incomingVote := &types.Vote{ProposedBlockInfo: voteForSign.ProposedBlockInfo, Signature: signature, GapNumber: voteForSign.GapNumber}
|
||||
// Set up forensics events trigger
|
||||
forensicsEventCh := make(chan types.ForensicsEvent)
|
||||
forensics.SubscribeForensicsEvent(forensicsEventCh)
|
||||
|
||||
err = forensics.ProcessVoteEquivocation(blockchain, blockchain.Engine().(*XDPoS.XDPoS).EngineV2, incomingVote)
|
||||
assert.Nil(t, err)
|
||||
// Check SendForensicProof triggered
|
||||
for {
|
||||
select {
|
||||
case msg := <-forensicsEventCh:
|
||||
assert.NotNil(t, msg.ForensicsProof)
|
||||
assert.Equal(t, "Vote", msg.ForensicsProof.ForensicsType)
|
||||
content := &types.VoteEquivocationContent{}
|
||||
json.Unmarshal([]byte(msg.ForensicsProof.Content), &content)
|
||||
assert.Equal(t, types.Round(14), content.SmallerRoundVote.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(16), content.LargerRoundVote.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, acc1Addr, content.Signer)
|
||||
return
|
||||
case <-time.After(5 * time.Second):
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
}
|
||||
825
consensus/tests/engine_v2_tests/helper.go
Normal file
825
consensus/tests/engine_v2_tests/helper.go
Normal file
|
|
@ -0,0 +1,825 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/contracts"
|
||||
contractValidator "github.com/XinFinOrg/XDPoSChain/contracts/validator/contract"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
. "github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type masterNodes map[string]big.Int
|
||||
type signersList map[string]bool
|
||||
|
||||
const GAP = int(450)
|
||||
|
||||
var (
|
||||
acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee")
|
||||
acc3Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
acc4Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f292")
|
||||
acc5Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f293")
|
||||
voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a")
|
||||
acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7
|
||||
acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e
|
||||
acc3Addr = crypto.PubkeyToAddress(acc3Key.PublicKey) //xdc71562b71999873DB5b286dF957af199Ec94617F7
|
||||
voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434
|
||||
chainID = int64(1337)
|
||||
)
|
||||
|
||||
func SignHashByPK(pk *ecdsa.PrivateKey, itemToSign []byte) []byte {
|
||||
signer, signFn, err := getSignerAndSignFn(pk)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, itemToSign)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return signedHash
|
||||
}
|
||||
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
func RandStringBytes(n int) string {
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letterBytes[rand.Intn(len(letterBytes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func getSignerAndSignFn(pk *ecdsa.PrivateKey) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error), error) {
|
||||
veryLightScryptN := 2
|
||||
veryLightScryptP := 1
|
||||
dir, _ := ioutil.TempDir("", fmt.Sprintf("eth-getSignerAndSignFn-test-%v", RandStringBytes(5)))
|
||||
|
||||
new := func(kd string) *keystore.KeyStore {
|
||||
return keystore.NewKeyStore(kd, veryLightScryptN, veryLightScryptP)
|
||||
}
|
||||
|
||||
defer os.RemoveAll(dir)
|
||||
ks := new(dir)
|
||||
pass := "" // not used but required by API
|
||||
a1, err := ks.ImportECDSA(pk, pass)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
if err := ks.Unlock(a1, ""); err != nil {
|
||||
return a1.Address, nil, fmt.Errorf(err.Error())
|
||||
}
|
||||
return a1.Address, ks.SignHash, nil
|
||||
}
|
||||
|
||||
func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) {
|
||||
vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea"
|
||||
action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:])
|
||||
data := common.Hex2Bytes(action)
|
||||
gasPrice := big.NewInt(int64(0))
|
||||
amountInt := new(big.Int)
|
||||
amount, ok := amountInt.SetString("60000", 10)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("big int init failed")
|
||||
}
|
||||
to := common.HexToAddress(common.MasternodeVotingSMC)
|
||||
tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data)
|
||||
|
||||
signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return signedTX, nil
|
||||
}
|
||||
|
||||
func getCommonBackend(t *testing.T, chainConfig *params.ChainConfig) *backends.SimulatedBackend {
|
||||
|
||||
// initial helper backend
|
||||
contractBackendForSC := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
}, 10000000, chainConfig)
|
||||
|
||||
transactOpts := bind.NewKeyedTransactor(voterKey)
|
||||
|
||||
var candidates []common.Address
|
||||
var caps []*big.Int
|
||||
defalutCap := new(big.Int)
|
||||
defalutCap.SetString("1000000000", 10)
|
||||
|
||||
for i := 1; i <= 16; i++ {
|
||||
addr := fmt.Sprintf("%02d", i)
|
||||
candidates = append(candidates, common.StringToAddress(addr)) // StringToAddress does not exist
|
||||
caps = append(caps, defalutCap)
|
||||
}
|
||||
|
||||
acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int)
|
||||
|
||||
acc1Cap.SetString("10000001", 10)
|
||||
acc2Cap.SetString("10000002", 10)
|
||||
acc3Cap.SetString("10000003", 10)
|
||||
voterCap.SetString("1000000000", 10)
|
||||
|
||||
caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap)
|
||||
candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr)
|
||||
// create validator smart contract
|
||||
validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator(
|
||||
transactOpts,
|
||||
contractBackendForSC,
|
||||
candidates,
|
||||
caps,
|
||||
voterAddr, // first owner, not used
|
||||
big.NewInt(50000),
|
||||
big.NewInt(1),
|
||||
big.NewInt(99),
|
||||
big.NewInt(100),
|
||||
big.NewInt(100),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("can't deploy root registry: %v", err)
|
||||
}
|
||||
|
||||
contractBackendForSC.Commit() // Write into database(state)
|
||||
|
||||
// Prepare Code and Storage
|
||||
d := time.Now().Add(1000 * time.Millisecond)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
|
||||
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
|
||||
storage := make(map[common.Hash]common.Hash)
|
||||
f := func(key, val common.Hash) bool {
|
||||
decode := []byte{}
|
||||
trim := bytes.TrimLeft(val.Bytes(), "\x00")
|
||||
err := rlp.DecodeBytes(trim, &decode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while decode byte")
|
||||
}
|
||||
storage[key] = common.BytesToHash(decode)
|
||||
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
|
||||
return true
|
||||
}
|
||||
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to read all keys from SC")
|
||||
}
|
||||
|
||||
// create test backend with smart contract in it
|
||||
contractBackend2 := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
common.HexToAddress(common.MasternodeVotingSMC): {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution
|
||||
}, 10000000, chainConfig)
|
||||
|
||||
return contractBackend2
|
||||
}
|
||||
|
||||
func getMultiCandidatesBackend(t *testing.T, chainConfig *params.ChainConfig, n int) *backends.SimulatedBackend {
|
||||
assert.GreaterOrEqual(t, n, 4)
|
||||
// initial helper backend, give a very large gas limit
|
||||
contractBackendForSC := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
}, 1000000000, chainConfig)
|
||||
|
||||
transactOpts := bind.NewKeyedTransactor(voterKey)
|
||||
|
||||
var candidates []common.Address
|
||||
var caps []*big.Int
|
||||
defalutCap := new(big.Int)
|
||||
defalutCap.SetString("1000000000", 10)
|
||||
|
||||
for i := 1; i <= n-4; i++ {
|
||||
addr := fmt.Sprintf("%04d", i)
|
||||
candidates = append(candidates, common.StringToAddress(addr))
|
||||
caps = append(caps, defalutCap)
|
||||
}
|
||||
|
||||
acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int)
|
||||
|
||||
acc1Cap.SetString("10000001", 10)
|
||||
acc2Cap.SetString("10000002", 10)
|
||||
acc3Cap.SetString("10000003", 10)
|
||||
voterCap.SetString("2000000000", 10) // give voter the highest cap to make it win the masternode selection
|
||||
|
||||
caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap)
|
||||
candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr)
|
||||
|
||||
// create validator smart contract
|
||||
validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator(
|
||||
transactOpts,
|
||||
contractBackendForSC,
|
||||
candidates,
|
||||
caps,
|
||||
voterAddr, // first owner, not used
|
||||
big.NewInt(50000),
|
||||
big.NewInt(1),
|
||||
big.NewInt(99),
|
||||
big.NewInt(100),
|
||||
big.NewInt(100),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("can't deploy root registry: %v", err)
|
||||
}
|
||||
|
||||
contractBackendForSC.Commit() // Write into database(state)
|
||||
|
||||
// Prepare Code and Storage
|
||||
d := time.Now().Add(3000 * time.Millisecond)
|
||||
ctx, cancel := context.WithDeadline(context.Background(), d)
|
||||
defer cancel()
|
||||
|
||||
code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil)
|
||||
storage := make(map[common.Hash]common.Hash)
|
||||
f := func(key, val common.Hash) bool {
|
||||
decode := []byte{}
|
||||
trim := bytes.TrimLeft(val.Bytes(), "\x00")
|
||||
err := rlp.DecodeBytes(trim, &decode)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while decode byte")
|
||||
}
|
||||
storage[key] = common.BytesToHash(decode)
|
||||
log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String())
|
||||
return true
|
||||
}
|
||||
err = contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed while trying to read all keys from SC")
|
||||
}
|
||||
|
||||
// create test backend with smart contract in it
|
||||
contractBackend2 := backends.NewXDCSimulatedBackend(core.GenesisAlloc{
|
||||
acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
voterAddr: {Balance: new(big.Int).SetUint64(10000000000)},
|
||||
common.HexToAddress(common.MasternodeVotingSMC): {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution
|
||||
}, 10000000, chainConfig)
|
||||
|
||||
return contractBackend2
|
||||
}
|
||||
|
||||
func signingTxWithKey(header *types.Header, nonce uint64, privateKey *ecdsa.PrivateKey) (*types.Transaction, error) {
|
||||
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
|
||||
s := types.NewEIP155Signer(big.NewInt(chainID))
|
||||
h := s.Hash(tx)
|
||||
sig, err := crypto.Sign(h[:], privateKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedTx, err := tx.WithSignature(s, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
func signingTxWithSignerFn(header *types.Header, nonce uint64, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) (*types.Transaction, error) {
|
||||
tx := contracts.CreateTxSign(header.Number, header.Hash(), nonce, common.HexToAddress(common.BlockSigners))
|
||||
s := types.NewEIP155Signer(big.NewInt(chainID))
|
||||
h := s.Hash(tx)
|
||||
sig, err := signFn(accounts.Account{Address: signer}, h[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signedTx, err := tx.WithSignature(s, sig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return signedTx, nil
|
||||
}
|
||||
|
||||
func UpdateSigner(bc *BlockChain) error {
|
||||
err := bc.UpdateM1()
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSnapshotSigner(bc *BlockChain, header *types.Header) (signersList, error) {
|
||||
engine := bc.Engine().(*XDPoS.XDPoS)
|
||||
snap, err := engine.GetSnapshot(bc, header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
ms := make(signersList)
|
||||
|
||||
for addr := range snap.Signers {
|
||||
ms[addr.Hex()] = true
|
||||
}
|
||||
return ms, nil
|
||||
|
||||
}
|
||||
|
||||
func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testing.T) masterNodes {
|
||||
addr := common.HexToAddress(common.MasternodeVotingSMC)
|
||||
validator, err := contractValidator.NewXDCValidator(addr, backend)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opts := new(bind.CallOpts)
|
||||
candidates, err := validator.GetCandidates(opts)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ms := make(masterNodes)
|
||||
for _, candidate := range candidates {
|
||||
v, err := validator.GetCandidateCap(opts, candidate)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ms[candidate.String()] = *v
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
type ForkedBlockOptions struct {
|
||||
numOfForkedBlocks *int
|
||||
forkedRoundDifference *int // Minimum is 1
|
||||
signersKey []*ecdsa.PrivateKey
|
||||
}
|
||||
|
||||
// V2 concensus engine
|
||||
func PrepareXDCTestBlockChainForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig, forkedBlockOptions *ForkedBlockOptions) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error), *types.Block) {
|
||||
// Preparation
|
||||
var err error
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error while creating simulated wallet for generating singer address and signer fn: %v", err))
|
||||
}
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Authorise
|
||||
engine.Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
var currentForkBlock *types.Block
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
// for v2 blocks, fill in correct coinbase
|
||||
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
|
||||
blockCoinBase = signer.Hex()
|
||||
}
|
||||
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
|
||||
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Produce forked block for the last numOfForkedBlocks'th blocks
|
||||
if forkedBlockOptions != nil && forkedBlockOptions.numOfForkedBlocks != nil && i > numOfBlocks-*forkedBlockOptions.numOfForkedBlocks {
|
||||
if currentForkBlock == nil {
|
||||
currentForkBlock = currentBlock
|
||||
}
|
||||
forkedBlockCoinBase := fmt.Sprintf("0x222000000000000000000000000000000%03d", i)
|
||||
var forkedBlockRoundNumber int64
|
||||
if forkedBlockOptions.forkedRoundDifference != nil {
|
||||
if *forkedBlockOptions.forkedRoundDifference == 0 {
|
||||
t.Fatal("forkedRoundDifference minimum is 1")
|
||||
}
|
||||
forkedBlockRoundNumber = roundNumber + int64(*forkedBlockOptions.forkedRoundDifference)
|
||||
} else {
|
||||
forkedBlockRoundNumber = roundNumber + int64(*forkedBlockOptions.numOfForkedBlocks)
|
||||
}
|
||||
|
||||
forkedBlock := CreateBlock(blockchain, chainConfig, currentForkBlock, i, forkedBlockRoundNumber, forkedBlockCoinBase, signer, signFn, nil, forkedBlockOptions.signersKey, "")
|
||||
|
||||
err = blockchain.InsertBlock(forkedBlock)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
currentForkBlock = forkedBlock
|
||||
}
|
||||
|
||||
// First v2 block
|
||||
if (int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()) == 1 {
|
||||
lastv1BlockNumber := block.Header().Number.Uint64() - 1
|
||||
checkpointBlockNumber := lastv1BlockNumber - lastv1BlockNumber%chainConfig.XDPoS.Epoch
|
||||
checkpointHeader := blockchain.GetHeaderByNumber(checkpointBlockNumber)
|
||||
err := engine.EngineV2.Initial(blockchain, checkpointHeader)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
currentBlock = block
|
||||
}
|
||||
|
||||
// Update Signer as there is no previous signer assigned
|
||||
err = UpdateSigner(blockchain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock, signer, signFn, currentForkBlock
|
||||
}
|
||||
|
||||
// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) add penalty
|
||||
func PrepareXDCTestBlockChainWithPenaltyForV2Engine(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Preparation
|
||||
var err error
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
if err != nil {
|
||||
t.Fatal("Error while creating simulated wallet for generating singer address and signer fn: ", err)
|
||||
}
|
||||
backend := getCommonBackend(t, chainConfig)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
// Authorise
|
||||
blockchain.Engine().(*XDPoS.XDPoS).Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
// for v2 blocks, fill in correct coinbase
|
||||
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
|
||||
blockCoinBase = signer.Hex()
|
||||
}
|
||||
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
|
||||
// use signer itself as penalty
|
||||
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, signer[:], nil, "")
|
||||
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
currentBlock = block
|
||||
}
|
||||
|
||||
// Update Signer as there is no previous signer assigned
|
||||
err = UpdateSigner(blockchain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock, signer, signFn
|
||||
}
|
||||
|
||||
// V2 concensus engine, compared to PrepareXDCTestBlockChainForV2Engine: (1) no forking (2) 128 masternode candidates
|
||||
func PrepareXDCTestBlockChainWith128Candidates(t *testing.T, numOfBlocks int, chainConfig *params.ChainConfig) (*BlockChain, *backends.SimulatedBackend, *types.Block, common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Preparation
|
||||
var err error
|
||||
signer, signFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
if err != nil {
|
||||
t.Fatal("Error while creating simulated wallet for generating singer address and signer fn: ", err)
|
||||
}
|
||||
backend := getMultiCandidatesBackend(t, chainConfig, 128)
|
||||
blockchain := backend.GetBlockChain()
|
||||
blockchain.Client = backend
|
||||
|
||||
engine := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Authorise
|
||||
engine.Authorize(signer, signFn)
|
||||
|
||||
currentBlock := blockchain.Genesis()
|
||||
|
||||
go func() {
|
||||
for range core.CheckpointCh {
|
||||
checkpointChanMsg := <-core.CheckpointCh
|
||||
log.Info("[V2] Got a message from core CheckpointChan!", "msg", checkpointChanMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
// Insert initial blocks
|
||||
for i := 1; i <= numOfBlocks; i++ {
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i)
|
||||
// for v2 blocks, fill in correct coinbase
|
||||
if int64(i) > chainConfig.XDPoS.V2.SwitchBlock.Int64() {
|
||||
blockCoinBase = signer.Hex()
|
||||
}
|
||||
roundNumber := int64(i) - chainConfig.XDPoS.V2.SwitchBlock.Int64()
|
||||
block := CreateBlock(blockchain, chainConfig, currentBlock, i, roundNumber, blockCoinBase, signer, signFn, nil, nil, "b345a8560bd51926803dd17677c9f0751193914a851a4ec13063d6bf50220b53")
|
||||
err = blockchain.InsertBlock(block)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
currentBlock = block
|
||||
}
|
||||
|
||||
// Update Signer as there is no previous signer assigned
|
||||
err = UpdateSigner(blockchain)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return blockchain, backend, currentBlock, signer, signFn
|
||||
}
|
||||
|
||||
func CreateBlock(blockchain *BlockChain, chainConfig *params.ChainConfig, startingBlock *types.Block, blockNumber int, roundNumber int64, blockCoinBase string, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), penalties []byte, signersKey []*ecdsa.PrivateKey, merkleRoot string) *types.Block {
|
||||
currentBlock := startingBlock
|
||||
if len(merkleRoot) == 0 {
|
||||
merkleRoot = "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
}
|
||||
var header *types.Header
|
||||
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 1 { // Build engine v2 compatible extra data field
|
||||
extraInBytes := generateV2Extra(roundNumber, currentBlock, signer, signFn, signersKey)
|
||||
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
Extra: extraInBytes,
|
||||
}
|
||||
if int64(blockNumber) == (chainConfig.XDPoS.V2.SwitchBlock.Int64() + 1) { // This is the first v2 block, we need to copy the last v1 epoch master node list and inject into v2 validators
|
||||
// Get last master node list from last v1 block
|
||||
lastv1Block := blockchain.GetBlockByNumber(chainConfig.XDPoS.V2.SwitchBlock.Uint64())
|
||||
masternodesFromV1LastEpoch := decodeMasternodesFromHeaderExtra(lastv1Block.Header())
|
||||
for _, v := range masternodesFromV1LastEpoch {
|
||||
header.Validators = append(header.Validators, v[:]...)
|
||||
}
|
||||
} else if roundNumber%int64(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// epoch switch blocks, copy the master node list and inject into v2 validators
|
||||
// Get last master node list from last v1 block
|
||||
lastv1Block := blockchain.GetBlockByNumber(chainConfig.XDPoS.V2.SwitchBlock.Uint64())
|
||||
masternodesFromV1LastEpoch := decodeMasternodesFromHeaderExtra(lastv1Block.Header())
|
||||
for _, v := range masternodesFromV1LastEpoch {
|
||||
header.Validators = append(header.Validators, v[:]...)
|
||||
}
|
||||
if penalties != nil {
|
||||
header.Penalties = penalties
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// V1 block
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(blockNumber)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinBase),
|
||||
}
|
||||
|
||||
// Inject the hardcoded master node list for the last v1 epoch block and all v1 epoch switch blocks (excluding genesis)
|
||||
if big.NewInt(int64(blockNumber)).Cmp(chainConfig.XDPoS.V2.SwitchBlock) == 0 || blockNumber%int(chainConfig.XDPoS.Epoch) == 0 {
|
||||
// reset extra
|
||||
header.Extra = []byte{}
|
||||
if len(header.Extra) < utils.ExtraVanity {
|
||||
header.Extra = append(header.Extra, bytes.Repeat([]byte{0x00}, utils.ExtraVanity-len(header.Extra))...)
|
||||
}
|
||||
header.Extra = header.Extra[:utils.ExtraVanity]
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
// masternodesFromV1LastEpoch = masternodes
|
||||
for _, masternode := range masternodes {
|
||||
header.Extra = append(header.Extra, masternode[:]...)
|
||||
}
|
||||
header.Extra = append(header.Extra, make([]byte, utils.ExtraSeal)...)
|
||||
|
||||
// Sign all the things for v1 block use v1 sigHash function
|
||||
sighash, err := signFn(accounts.Account{Address: signer}, blockchain.Engine().(*XDPoS.XDPoS).SigHash(header).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error when sign last v1 block hash during test block creation"))
|
||||
}
|
||||
copy(header.Extra[len(header.Extra)-utils.ExtraSeal:], sighash)
|
||||
}
|
||||
}
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, chainConfig)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Fail to create block in test helper, %v", err))
|
||||
}
|
||||
return block
|
||||
}
|
||||
|
||||
func createBlockFromHeader(bc *BlockChain, customHeader *types.Header, txs []*types.Transaction, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (*types.Block, error) {
|
||||
if customHeader.Extra == nil {
|
||||
extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation
|
||||
customHeader.Extra, _ = hex.DecodeString(extraSubstring)
|
||||
}
|
||||
var difficulty *big.Int
|
||||
if customHeader.Difficulty == nil {
|
||||
difficulty = big.NewInt(1)
|
||||
} else {
|
||||
difficulty = customHeader.Difficulty
|
||||
}
|
||||
|
||||
if len(txs) != 0 {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c")
|
||||
} else {
|
||||
customHeader.ReceiptHash = common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
}
|
||||
|
||||
header := types.Header{
|
||||
ParentHash: customHeader.ParentHash,
|
||||
UncleHash: types.EmptyUncleHash,
|
||||
TxHash: types.EmptyRootHash,
|
||||
ReceiptHash: customHeader.ReceiptHash,
|
||||
Root: customHeader.Root,
|
||||
Coinbase: customHeader.Coinbase,
|
||||
Difficulty: difficulty,
|
||||
Number: customHeader.Number,
|
||||
GasLimit: 1200000000,
|
||||
Time: big.NewInt(time.Now().Unix() - 1000000 + int64(customHeader.Number.Uint64()*10)),
|
||||
Extra: customHeader.Extra,
|
||||
Validator: customHeader.Validator,
|
||||
Validators: customHeader.Validators,
|
||||
Penalties: customHeader.Penalties,
|
||||
}
|
||||
var block *types.Block
|
||||
if len(txs) == 0 {
|
||||
// Sign all the things and seal it
|
||||
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
|
||||
header.Coinbase = signerAddress
|
||||
sealHeader(bc, &header, signerAddress, signerFunction)
|
||||
|
||||
block = types.NewBlockWithHeader(&header)
|
||||
} else {
|
||||
// Prepare Receipt
|
||||
statedb, err := bc.StateAt(bc.GetBlockByNumber(customHeader.Number.Uint64() - 1).Root()) //Get parent root
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when get state", err)
|
||||
}
|
||||
gp := new(GasPool).AddGas(header.GasLimit)
|
||||
|
||||
var gasUsed = new(uint64)
|
||||
var receipts types.Receipts
|
||||
for i, tx := range txs {
|
||||
statedb.Prepare(tx.Hash(), header.Hash(), i)
|
||||
receipt, _, err, _ := ApplyTransaction(bc.Config(), nil, bc, &header.Coinbase, gp, statedb, nil, &header, tx, gasUsed, vm.Config{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%v when applying transaction", err)
|
||||
}
|
||||
receipts = append(receipts, receipt)
|
||||
}
|
||||
|
||||
header.GasUsed = *gasUsed
|
||||
|
||||
// Sign all the things and seal it
|
||||
signerAddress, signerFunction := findSignerAndSignFn(bc, &header, signer, signFn, config)
|
||||
header.Coinbase = signerAddress
|
||||
sealHeader(bc, &header, signerAddress, signerFunction)
|
||||
|
||||
block = types.NewBlock(&header, txs, nil, receipts)
|
||||
}
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// Get masternodes address from checkpoint Header. Only used for v1 last block
|
||||
func decodeMasternodesFromHeaderExtra(checkpointHeader *types.Header) []common.Address {
|
||||
masternodes := make([]common.Address, (len(checkpointHeader.Extra)-utils.ExtraVanity-utils.ExtraSeal)/common.AddressLength)
|
||||
for i := 0; i < len(masternodes); i++ {
|
||||
copy(masternodes[i][:], checkpointHeader.Extra[utils.ExtraVanity+i*common.AddressLength:])
|
||||
}
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func findSignerAndSignFn(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), config *params.ChainConfig) (common.Address, func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
addressToSign := signer
|
||||
addressedSignFn := signFn
|
||||
|
||||
// If v2 block, we need to use extra data's round to find who is creating the block in order to verify the validator
|
||||
if header.Number.Cmp(config.XDPoS.V2.SwitchBlock) > 0 {
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(header.Extra, &decodedExtraField)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("fail to seal header for v2 block"))
|
||||
}
|
||||
round := decodedExtraField.Round
|
||||
masterNodes := getMasternodesList(signer)
|
||||
|
||||
index := uint64(round) % config.XDPoS.Epoch % uint64(len(masterNodes))
|
||||
// index 0 to 2 are acc1Addr, acc2Addr, acc3Addr
|
||||
addressToSign = masterNodes[index]
|
||||
if index == 0 {
|
||||
_, signFn, err = getSignerAndSignFn(acc1Key)
|
||||
} else if index == 1 {
|
||||
_, signFn, err = getSignerAndSignFn(acc2Key)
|
||||
} else if index == 2 {
|
||||
_, signFn, err = getSignerAndSignFn(acc3Key)
|
||||
} else if index == 3 {
|
||||
// Skip signing anything for voterAddress to simulate penalty
|
||||
return signer, signFn
|
||||
}
|
||||
addressedSignFn = signFn
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error trying to use one of the pre-defined private key to sign"))
|
||||
}
|
||||
}
|
||||
|
||||
return addressToSign, addressedSignFn
|
||||
}
|
||||
|
||||
func sealHeader(bc *BlockChain, header *types.Header, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error)) {
|
||||
// Sign all the things and seal it
|
||||
signedBlockHeader := bc.Engine().(*XDPoS.XDPoS).SigHash(header)
|
||||
|
||||
signature, err := signFn(accounts.Account{Address: signer}, signedBlockHeader.Bytes())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
header.Validator = signature
|
||||
}
|
||||
|
||||
func getMasternodesList(signer common.Address) []common.Address {
|
||||
var masternodes []common.Address
|
||||
// Place the test's signer address to the last
|
||||
masternodes = append(masternodes, acc1Addr, acc2Addr, acc3Addr, voterAddr, signer)
|
||||
return masternodes
|
||||
}
|
||||
|
||||
func generateV2Extra(roundNumber int64, currentBlock *types.Block, signer common.Address, signFn func(account accounts.Account, hash []byte) ([]byte, error), accKeys []*ecdsa.PrivateKey) []byte {
|
||||
var extraField types.ExtraFields_v2
|
||||
var round types.Round
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
round = types.Round(0)
|
||||
} else {
|
||||
round = extraField.Round
|
||||
}
|
||||
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: round,
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
gapNumber := currentBlock.Number().Uint64() - currentBlock.Number().Uint64()%params.TestXDPoSMockChainConfig.XDPoS.Epoch - params.TestXDPoSMockChainConfig.XDPoS.Gap
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
|
||||
}
|
||||
var signatures []types.Signature
|
||||
if len(accKeys) == 0 {
|
||||
// Sign from acc 1, 2, 3 by default
|
||||
accKeys = append(accKeys, acc1Key, acc2Key, acc3Key, voterKey)
|
||||
}
|
||||
for _, acc := range accKeys {
|
||||
h := SignHashByPK(acc, types.VoteSigHash(voteForSign).Bytes())
|
||||
signatures = append(signatures, h)
|
||||
}
|
||||
signatures = append(signatures, signedHash)
|
||||
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: gapNumber,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(roundNumber),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
return extraInBytes
|
||||
}
|
||||
126
consensus/tests/engine_v2_tests/initial_test.go
Normal file
126
consensus/tests/engine_v2_tests/initial_test.go
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestInitialFirstV2Block(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
header := currentBlock.Header()
|
||||
|
||||
// snapshot should not be created before initial
|
||||
snap, _ := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, snap)
|
||||
|
||||
err := adaptor.EngineV2.Initial(blockchain, header)
|
||||
assert.Nil(t, err)
|
||||
|
||||
round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker()
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: header.Hash(),
|
||||
Round: types.Round(0),
|
||||
Number: header.Number,
|
||||
}
|
||||
expectedQuorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: nil,
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
assert.Equal(t, types.Round(1), round)
|
||||
assert.Equal(t, expectedQuorumCert, highQC)
|
||||
|
||||
// Test snapshot
|
||||
snap, err = adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(450), snap.Number)
|
||||
|
||||
// Test Running channels
|
||||
minePeriod := <-adaptor.MinePeriodCh
|
||||
assert.Equal(t, params.TestXDPoSMockChainConfig.XDPoS.V2.CurrentConfig.MinePeriod, minePeriod)
|
||||
|
||||
t.Logf("Waiting %d secs for timeout to happen", params.TestXDPoSMockChainConfig.XDPoS.V2.CurrentConfig.TimeoutPeriod)
|
||||
timeoutMsg := <-adaptor.EngineV2.BroadcastCh
|
||||
assert.NotNil(t, timeoutMsg)
|
||||
assert.Equal(t, types.Round(1), timeoutMsg.(*types.Timeout).Round)
|
||||
}
|
||||
|
||||
func TestInitialOtherV2Block(t *testing.T) {
|
||||
// insert new block with new extra fields
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 900, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
for blockNum := 901; blockNum <= 910; blockNum++ {
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, int64(blockNum-900), blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
// v2
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Header().Hash(),
|
||||
Round: types.Round(10),
|
||||
Number: big.NewInt(910),
|
||||
}
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: nil, // after decode it got default value []utils.Signature{}
|
||||
GapNumber: 450,
|
||||
}
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: 11,
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraBytes, err := extra.EncodeToBytes()
|
||||
assert.Nil(t, err)
|
||||
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash("35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"),
|
||||
Number: big.NewInt(int64(911)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress("0x111000000000000000000000000000000123"),
|
||||
}
|
||||
header.Extra = extraBytes
|
||||
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, blockchain.Config())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
// Initialise
|
||||
err = adaptor.EngineV2.Initial(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
|
||||
round, _, highQC, _, _, _ := adaptor.EngineV2.GetPropertiesFaker()
|
||||
expectedQuorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signatures: []types.Signature{},
|
||||
GapNumber: blockchain.Config().XDPoS.V2.SwitchBlock.Uint64() - blockchain.Config().XDPoS.Gap,
|
||||
}
|
||||
assert.Equal(t, types.Round(11), round)
|
||||
assert.Equal(t, expectedQuorumCert, highQC)
|
||||
|
||||
// Test snapshot
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(450), snap.Number)
|
||||
}
|
||||
|
||||
func TestSnapshotShouldAlreadyCreatedByUpdateM1(t *testing.T) {
|
||||
// insert new block with new extra fields
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 1800, params.TestXDPoSMockChainConfig, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, uint64(1350), snap.Number)
|
||||
}
|
||||
284
consensus/tests/engine_v2_tests/mine_test.go
Normal file
284
consensus/tests/engine_v2_tests/mine_test.go
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestYourTurnInitialV2(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, parentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)-1, config, nil)
|
||||
minePeriod := config.XDPoS.V2.CurrentConfig.MinePeriod
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Insert block 900
|
||||
t.Logf("Inserting block with propose at 900...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000000900"
|
||||
//Get from block validator error message
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(900)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
Extra: common.Hex2Bytes("d7830100018358444388676f312e31352e38856c696e757800000000000000000278c350152e15fa6ffc712a5a73d704ce73e2e103d9e17ae3ff2c6712e44e25b09ac5ee91f6c9ff065551f0dcac6f00cae11192d462db709be3758ccef312ee5eea8d7bad5374c6a652150515d744508b61c1a4deb4e4e7bf057e4e3824c11fd2569bcb77a52905cda63b5a58507910bed335e4c9d87ae0ecdfafd400"),
|
||||
}
|
||||
block900, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block900)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
|
||||
// YourTurn is called before mine first v2 block
|
||||
b, err := adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc0278C350152e15fa6FFC712a5A73D704Ce73E2E1"))
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, b)
|
||||
b, err = adaptor.YourTurn(blockchain, block900.Header(), common.HexToAddress("xdc03d9e17Ae3fF2c6712E44e25B09Ac5ee91f6c9ff"))
|
||||
assert.Nil(t, err)
|
||||
// round=1, so masternode[1] has YourTurn = True
|
||||
assert.True(t, b)
|
||||
assert.Equal(t, adaptor.EngineV2.GetCurrentRoundFaker(), types.Round(1))
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, block900.Header())
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, snap)
|
||||
masterNodes := adaptor.EngineV1.GetMasternodesFromCheckpointHeader(block900.Header())
|
||||
for i := 0; i < len(masterNodes); i++ {
|
||||
assert.Equal(t, masterNodes[i].Hex(), snap.NextEpochMasterNodes[i].Hex())
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldMineOncePerRound(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, block910, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
minePeriod := config.XDPoS.V2.CurrentConfig.MinePeriod
|
||||
|
||||
// Make sure we seal the parentBlock 910
|
||||
_, err := adaptor.Seal(blockchain, block910, nil)
|
||||
assert.Nil(t, err)
|
||||
time.Sleep(time.Duration(minePeriod) * time.Second)
|
||||
b, err := adaptor.YourTurn(blockchain, block910.Header(), signer)
|
||||
assert.False(t, b)
|
||||
assert.Equal(t, utils.ErrAlreadyMined, err)
|
||||
}
|
||||
|
||||
func TestUpdateMasterNodes(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
x := adaptor.EngineV2
|
||||
snap, err := x.GetSnapshot(blockchain, currentBlock.Header())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 450, int(snap.Number))
|
||||
|
||||
// Insert block 1350
|
||||
t.Logf("Inserting block with propose at 1350...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000001350"
|
||||
// NOTE: voterAddr never exist in the Masternode list, but all acc1,2,3 already does
|
||||
tx, err := voteTX(37117, 0, voterAddr.String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
//Get from block validator error message
|
||||
merkleRoot := "ef9198eb14b003774a505033f6cdcea2d357cbf7a7e7b004d8034d4e2a9770ee"
|
||||
header := &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(1350)),
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbaseA),
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(450, currentBlock, signer, signFn, nil)
|
||||
|
||||
parentBlock, err := createBlockFromHeader(blockchain, header, []*types.Transaction{tx}, signer, signFn, config)
|
||||
assert.Nil(t, err)
|
||||
err = blockchain.InsertBlock(parentBlock)
|
||||
assert.Nil(t, err)
|
||||
// 1350 is a gap block, need to update the snapshot
|
||||
err = blockchain.UpdateM1()
|
||||
assert.Nil(t, err)
|
||||
t.Logf("Inserting block from 1351 to 1800...")
|
||||
for i := 1351; i <= 1800; i++ {
|
||||
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
|
||||
//Get from block validator error message
|
||||
header = &types.Header{
|
||||
Root: common.HexToHash(merkleRoot),
|
||||
Number: big.NewInt(int64(i)),
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Coinbase: common.HexToAddress(blockCoinbase),
|
||||
}
|
||||
|
||||
header.Extra = generateV2Extra(int64(i), currentBlock, signer, signFn, nil)
|
||||
|
||||
block, err := createBlockFromHeader(blockchain, header, nil, signer, signFn, config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
parentBlock = block
|
||||
}
|
||||
|
||||
snap, err = x.GetSnapshot(blockchain, parentBlock.Header())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, snap.IsMasterNodes(voterAddr))
|
||||
assert.Equal(t, int(snap.Number), 1350)
|
||||
}
|
||||
|
||||
func TestPrepareFail(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
tstamp := time.Now().Unix()
|
||||
|
||||
notReadyToProposeHeader := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
err := adaptor.Prepare(blockchain, notReadyToProposeHeader)
|
||||
assert.Equal(t, consensus.ErrNotReadyToPropose, err)
|
||||
|
||||
notReadyToMine := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
// trigger initial which will set the highestQC
|
||||
_, err = adaptor.YourTurn(blockchain, currentBlock.Header(), signer)
|
||||
assert.Nil(t, err)
|
||||
err = adaptor.Prepare(blockchain, notReadyToMine)
|
||||
assert.Equal(t, consensus.ErrNotReadyToMine, err)
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
header901WithoutCoinbase := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
}
|
||||
|
||||
err = adaptor.Prepare(blockchain, header901WithoutCoinbase)
|
||||
assert.Equal(t, consensus.ErrCoinbaseMismatch, err)
|
||||
}
|
||||
|
||||
func TestPrepareHappyPath(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch), config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
// trigger initial
|
||||
_, err := adaptor.YourTurn(blockchain, currentBlock.Header(), signer)
|
||||
assert.Nil(t, err)
|
||||
|
||||
tstamp := time.Now().Unix()
|
||||
|
||||
header901 := &types.Header{
|
||||
ParentHash: currentBlock.Hash(),
|
||||
Number: big.NewInt(int64(901)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: signer,
|
||||
}
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
err = adaptor.Prepare(blockchain, header901)
|
||||
assert.Nil(t, err)
|
||||
|
||||
snap, err := adaptor.EngineV2.GetSnapshot(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
validators := []byte{}
|
||||
for _, v := range snap.NextEpochMasterNodes {
|
||||
validators = append(validators, v[:]...)
|
||||
}
|
||||
assert.Equal(t, validators, header901.Validators)
|
||||
|
||||
var decodedExtraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(header901.Extra, &decodedExtraField)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, types.Round(4), decodedExtraField.Round)
|
||||
assert.Equal(t, types.Round(0), decodedExtraField.QuorumCert.ProposedBlockInfo.Round)
|
||||
}
|
||||
|
||||
// test if we have 128 candidates, then snapshot will store all of them, and when preparing (and verifying) candidates is truncated to MaxMasternodes
|
||||
func TestUpdateMultipleMasterNodes(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, currentBlock, signer, signFn := PrepareXDCTestBlockChainWith128Candidates(t, int(config.XDPoS.Epoch+config.XDPoS.Gap)-1, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
x := adaptor.EngineV2
|
||||
// Insert block 1350
|
||||
t.Logf("Inserting block with propose at 1350...")
|
||||
blockCoinbaseA := "0xaaa0000000000000000000000000000000001350"
|
||||
//Get from block validator error message
|
||||
merkleRoot := "b345a8560bd51926803dd17677c9f0751193914a851a4ec13063d6bf50220b53"
|
||||
parentBlock := CreateBlock(blockchain, config, currentBlock, 1350, 450, blockCoinbaseA, signer, signFn, nil, nil, merkleRoot)
|
||||
err := blockchain.InsertBlock(parentBlock)
|
||||
assert.Nil(t, err)
|
||||
// 1350 is a gap block, need to update the snapshot
|
||||
err = blockchain.UpdateM1()
|
||||
assert.Nil(t, err)
|
||||
// but we wait until 1800 to test the snapshot
|
||||
|
||||
t.Logf("Inserting block from 1351 to 1800...")
|
||||
for i := 1351; i <= 1800; i++ {
|
||||
blockCoinbase := fmt.Sprintf("0xaaa000000000000000000000000000000000%4d", i)
|
||||
block := CreateBlock(blockchain, config, parentBlock, i, int64(i-900), blockCoinbase, signer, signFn, nil, nil, merkleRoot)
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
if i < 1800 {
|
||||
parentBlock = block
|
||||
}
|
||||
if i == 1800 {
|
||||
snap, err := x.GetSnapshot(blockchain, block.Header())
|
||||
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1350, int(snap.Number))
|
||||
assert.Equal(t, 128, len(snap.NextEpochMasterNodes)) // 128 is all masternode candidates, not limited by MaxMasternodes
|
||||
}
|
||||
}
|
||||
|
||||
tstamp := time.Now().Unix()
|
||||
|
||||
header1800 := &types.Header{
|
||||
ParentHash: parentBlock.Hash(),
|
||||
Number: big.NewInt(int64(1800)),
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: big.NewInt(tstamp),
|
||||
Coinbase: voterAddr,
|
||||
}
|
||||
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(900), false)
|
||||
blockInfo := &types.BlockInfo{Hash: parentBlock.Hash(), Round: types.Round(900 - 1), Number: parentBlock.Number()}
|
||||
signature := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
signatures := []types.Signature{signature}
|
||||
quorumCert := &types.QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures, GapNumber: 1350}
|
||||
adaptor.EngineV2.ProcessQCFaker(blockchain, quorumCert)
|
||||
adaptor.EngineV2.AuthorizeFaker(voterAddr)
|
||||
err = adaptor.Prepare(blockchain, header1800)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, common.MaxMasternodesV2, len(header1800.Validators)/common.AddressLength) // although 128 masternode candidates, we can only pick MaxMasternodes
|
||||
assert.Equal(t, 0, len(header1800.Penalties)/common.AddressLength)
|
||||
}
|
||||
153
consensus/tests/engine_v2_tests/penalty_test.go
Normal file
153
consensus/tests/engine_v2_tests/penalty_test.go
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHookPenaltyV2Mining(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*3, config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2100 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
// when we prepare the chain, we include all 5 signers as coinbase except one signer
|
||||
// header2100 records 5 masternodes, so penalty contains 5-4=1 address
|
||||
assert.Equal(t, 1, len(penalty))
|
||||
contains := false
|
||||
for _, mn := range common.RemoveItemFromArray(masternodes, penalty) {
|
||||
if mn == header901.Coinbase {
|
||||
contains = true
|
||||
}
|
||||
}
|
||||
assert.True(t, contains)
|
||||
// set adaptor round/qc to that of 6299
|
||||
err = utils.DecodeBytesExtraFields(header2100.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
err = adaptor.EngineV2.ProcessQCFaker(blockchain, extraField.QuorumCert)
|
||||
assert.Nil(t, err)
|
||||
// coinbase is a faker signer
|
||||
headerMining := &types.Header{
|
||||
ParentHash: header2100.ParentHash,
|
||||
Number: header2100.Number,
|
||||
GasLimit: params.TargetGasLimit,
|
||||
Time: header2100.Time,
|
||||
Coinbase: acc1Addr,
|
||||
}
|
||||
// Force to make the node to be at its round to mine, otherwise won't pass the yourturn masternodes check
|
||||
// We have 19 nodes in total (20 candidates in snapshot - 1 penalty) and the fake signer is always at the 18th(last) in the list.
|
||||
// Hence int(config.XDPoS.Epoch)*3+18-900, the +18 means is to force to next 18 round and -900 is the relative round number to block number int(config.XDPoS.Epoch)*3
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(int(config.XDPoS.Epoch)*3+18-900), false)
|
||||
// The test default signer is not in the masternodes, so we set the faker signer
|
||||
adaptor.EngineV2.AuthorizeFaker(acc1Addr)
|
||||
err = adaptor.Prepare(blockchain, headerMining)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(headerMining.Penalties)/common.AddressLength)
|
||||
// 20 candidates (set by PrepareXDCTestBlockChainForV2Engine) - 1 penalty = 19
|
||||
assert.Equal(t, 19, len(headerMining.Validators)/common.AddressLength)
|
||||
}
|
||||
|
||||
func TestHookPenaltyV2Comeback(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, signer, signFn := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2100 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
// miner (coinbase) is in comeback. so all addresses are in penalty
|
||||
assert.Equal(t, 2, len(penalty))
|
||||
header2085 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - common.MergeSignRange)
|
||||
// forcely insert signing tx into cache, to cancel comeback. since no comeback, penalty is 3
|
||||
tx, err := signingTxWithSignerFn(header2085, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header2085.Hash(), []*types.Transaction{tx})
|
||||
penalty, err = adaptor.EngineV2.HookPenalty(blockchain, big.NewInt(int64(config.XDPoS.Epoch*3)), header2100.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 1, len(penalty))
|
||||
}
|
||||
|
||||
func TestHookPenaltyV2Jump(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
end := int(config.XDPoS.Epoch)*3 - common.MergeSignRange
|
||||
blockchain, _, _, _, _ := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header2685 := blockchain.GetHeaderByNumber(uint64(end))
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(config.XDPoS.Epoch*3), false)
|
||||
// round 2685-2700 miss blocks, penalty should work as usual
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header2685.Number, header2685.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(penalty))
|
||||
}
|
||||
|
||||
// Test calculate penalty under startRange blocks, currently is 150
|
||||
func TestHookPenaltyV2LessThen150Blocks(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, _, _ := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookPenalty)
|
||||
var extraField types.ExtraFields_v2
|
||||
// 901 is the first v2 block
|
||||
header901 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 1)
|
||||
err := utils.DecodeBytesExtraFields(header901.Extra, &extraField)
|
||||
assert.Nil(t, err)
|
||||
masternodes := adaptor.GetMasternodesFromCheckpointHeader(header901)
|
||||
assert.Equal(t, 5, len(masternodes))
|
||||
header1900 := blockchain.GetHeaderByNumber(1900)
|
||||
adaptor.EngineV2.SetNewRoundFaker(blockchain, types.Round(config.XDPoS.Epoch*3), false)
|
||||
// penalty count from 1900
|
||||
penalty, err := adaptor.EngineV2.HookPenalty(blockchain, header1900.Number, header1900.ParentHash, masternodes)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, 2, len(penalty))
|
||||
}
|
||||
|
||||
func TestGetPenalties(t *testing.T) {
|
||||
config := params.TestXDPoSMockChainConfig
|
||||
blockchain, _, _, _, _ := PrepareXDCTestBlockChainWithPenaltyForV2Engine(t, int(config.XDPoS.Epoch)*3, config)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
header2699 := blockchain.GetHeaderByNumber(2699)
|
||||
header1801 := blockchain.GetHeaderByNumber(1801)
|
||||
|
||||
penalty2699 := adaptor.EngineV2.GetPenalties(blockchain, header2699)
|
||||
penalty1801 := adaptor.EngineV2.GetPenalties(blockchain, header1801)
|
||||
|
||||
assert.Equal(t, 1, len(penalty2699))
|
||||
assert.Equal(t, 1, len(penalty1801))
|
||||
}
|
||||
394
consensus/tests/engine_v2_tests/proposed_block_test.go
Normal file
394
consensus/tests/engine_v2_tests/proposed_block_test.go
Normal file
|
|
@ -0,0 +1,394 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldSendVoteMsgAndCommitGrandGrandParentBlock(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
poolSize := engineV2.GetVotePoolSizeFaker(voteMsg.(*types.Vote))
|
||||
|
||||
assert.Equal(t, poolSize, 1)
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(1), round)
|
||||
// Should not update the highestQC
|
||||
assert.Equal(t, types.Round(0), highestQC.ProposedBlockInfo.Round)
|
||||
|
||||
// Insert another Block, but it won't trigger commit
|
||||
blockNum := 902
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block902.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(2), round)
|
||||
assert.Equal(t, types.Round(1), highestQC.ProposedBlockInfo.Round)
|
||||
|
||||
// Insert one more Block, but still won't trigger commit
|
||||
blockNum = 903
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block903 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block902, blockNum, 3, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block903)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block903.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// Shoud NOT trigger setNewRound as the new block parent QC is round 1 but the currentRound is already 2
|
||||
assert.Equal(t, types.Round(3), round)
|
||||
assert.Equal(t, types.Round(2), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Nil(t, highestCommitBlock)
|
||||
|
||||
// Insert one more Block, this time will trigger commit
|
||||
blockNum = 904
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block904 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block903, blockNum, 4, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block904)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block904.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.Equal(t, types.Round(4), round)
|
||||
assert.Equal(t, types.Round(3), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, currentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, currentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(1), highestCommitBlock.Round)
|
||||
}
|
||||
|
||||
func TestShouldNotCommitIfRoundsNotContinousFor3Rounds(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
|
||||
grandGrandParentBlock := blockchain.GetBlockByNumber(902)
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(5), round)
|
||||
assert.Equal(t, types.Round(4), highestQC.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(2), highestCommitBlock.Round)
|
||||
|
||||
// Injecting new block which have gaps in the round number (Round 7 instead of 6)
|
||||
blockNum := 906
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block906 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 7, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block906)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block906.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
grandGrandParentBlock = blockchain.GetBlockByNumber(903)
|
||||
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(5), highestQC.ProposedBlockInfo.Round)
|
||||
// It commit its grandgrandparent block
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
|
||||
blockNum = 907
|
||||
blockCoinBase = fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block907 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, block906, blockNum, 8, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block907)
|
||||
assert.Nil(t, err)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, block907.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Trigger send vote again but for a new round
|
||||
voteMsg = <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
round, _, highestQC, _, _, highestCommitBlock = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.Equal(t, types.Round(8), round)
|
||||
assert.Equal(t, types.Round(7), highestQC.ProposedBlockInfo.Round)
|
||||
// Should NOT commit, the `grandGrandParentBlock` is still on blockNum 903
|
||||
assert.Equal(t, grandGrandParentBlock.Hash(), highestCommitBlock.Hash)
|
||||
assert.Equal(t, grandGrandParentBlock.Number(), highestCommitBlock.Number)
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
|
||||
}
|
||||
|
||||
func TestProposedBlockMessageHandlerSuccessfullyGenerateVote(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures)
|
||||
}
|
||||
|
||||
// Should not set new round if proposedBlockInfo round is less than currentRound.
|
||||
// NOTE: This shall not even happen because we have `verifyQC` before being passed into ProposedBlockHandler
|
||||
func TestShouldNotSetNewRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 6
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(6), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
round, _, highestQC, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud not trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, extraField.QuorumCert.Signatures, highestQC.Signatures)
|
||||
}
|
||||
|
||||
func TestShouldNotSendVoteMessageIfAlreadyVoteForThisRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
err := engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
voteMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, voteMsg)
|
||||
assert.Equal(t, currentBlock.Hash(), voteMsg.(*types.Vote).ProposedBlockInfo.Hash)
|
||||
|
||||
round, _, _, _, highestVotedRound, _ := engineV2.GetPropertiesFaker()
|
||||
// Shoud trigger setNewRound
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(6), highestVotedRound)
|
||||
|
||||
// Let's send again, this time, it shall not broadcast any vote message, because HigestVoteRound is same as currentRound
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler again", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(3 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, highestVotedRound, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
assert.Equal(t, types.Round(6), highestVotedRound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldNotSendVoteMsgIfBlockInfoRoundNotEqualCurrentRound(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set current round to 8
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(8), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(3 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(8), round)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Block and round relationship diagram for this test
|
||||
... - 13(3) - 14(4) - 15(5) - 16(6)
|
||||
\ 14'(7)
|
||||
*/
|
||||
func TestShouldNotSendVoteMsgIfBlockNotExtendedFromAncestor(t *testing.T) {
|
||||
// Block number 905, 906 have forks and forkedBlock is the 906th
|
||||
var numOfForks = new(int)
|
||||
*numOfForks = 3
|
||||
blockchain, _, currentBlock, _, _, forkedBlock := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, &ForkedBlockOptions{numOfForkedBlocks: numOfForks})
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(forkedBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
assert.Equal(t, types.Round(9), extraField.Round)
|
||||
// Set the lockQC and other pre-requist properties by block 906
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Error while handling block 16", err)
|
||||
}
|
||||
vote := <-engineV2.BroadcastCh
|
||||
assert.Equal(t, types.Round(6), vote.(*types.Vote).ProposedBlockInfo.Round)
|
||||
|
||||
// Find the first forked block at block 14th
|
||||
firstForkedBlock := blockchain.GetBlockByHash(blockchain.GetBlockByHash(forkedBlock.ParentHash()).ParentHash())
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(7), false)
|
||||
err = engineV2.ProposedBlockHandler(blockchain, firstForkedBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(3 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), round)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldSendVoteMsg(t *testing.T) {
|
||||
// Block number 15, 16 have forks and forkedBlock is the 16th
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 903, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Block 901 is first v2 block
|
||||
for i := 901; i < 904; i++ {
|
||||
blockHeader := blockchain.GetBlockByNumber(uint64(i)).Header()
|
||||
err := engineV2.ProposedBlockHandler(blockchain, blockHeader)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(i-900), round)
|
||||
vote := <-engineV2.BroadcastCh
|
||||
assert.Equal(t, round, vote.(*types.Vote).ProposedBlockInfo.Round)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProposedBlockMessageHandlerNotGenerateVoteIfSignerNotInMNlist(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 906, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
differentSigner, differentSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
// Let's change the address
|
||||
engineV2.Authorize(differentSigner, differentSignFn)
|
||||
|
||||
// Set current round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err = utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
err = engineV2.ProposedBlockHandler(blockchain, currentBlock.Header())
|
||||
if err != nil {
|
||||
t.Fatal("Fail propose proposedBlock handler", err)
|
||||
}
|
||||
|
||||
// Should not receive anything from the channel
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatal("Should not trigger vote")
|
||||
case <-time.After(2 * time.Second):
|
||||
// Shoud not trigger setNewRound
|
||||
round, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), round)
|
||||
}
|
||||
}
|
||||
163
consensus/tests/engine_v2_tests/reward_test.go
Normal file
163
consensus/tests/engine_v2_tests/reward_test.go
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/eth/hooks"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHookRewardV2(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
|
||||
config.XDPoS.V2.SwitchBlock.SetUint64(1800)
|
||||
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*5, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookReward)
|
||||
// forcely insert signing tx into cache, to give rewards.
|
||||
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
|
||||
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
|
||||
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
|
||||
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
|
||||
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
|
||||
statedb, err := blockchain.StateAt(header1799.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState := statedb.Copy()
|
||||
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(reward))
|
||||
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
|
||||
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
statedb, err = blockchain.StateAt(header2699.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
|
||||
assert.Nil(t, err)
|
||||
owner := state.GetCandidateOwner(parentState, signer)
|
||||
result := reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 1, len(result))
|
||||
for _, x := range result {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
}
|
||||
header2685 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 885)
|
||||
header2716 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 + 16)
|
||||
header3599 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*4 - 1)
|
||||
header3600 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 4)
|
||||
tx, err = signingTxWithSignerFn(header2685, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
// signed block hash and block contains tx are in different epoch, we should get same rewards
|
||||
adaptor.CacheSigningTxs(header2716.Hash(), []*types.Transaction{tx})
|
||||
statedb, err = blockchain.StateAt(header3599.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header3600)
|
||||
assert.Nil(t, err)
|
||||
result = reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 1, len(result))
|
||||
for _, x := range result {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
a, _ := big.NewInt(0).SetString("225000000000000000000", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("25000000000000000000", 10)
|
||||
assert.Zero(t, b.Cmp(r[config.XDPoS.FoudationWalletAddr]))
|
||||
}
|
||||
// if no signing tx, then reward will be 0
|
||||
header4499 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*5 - 1)
|
||||
header4500 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 5)
|
||||
statedb, err = blockchain.StateAt(header4499.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header4500)
|
||||
assert.Nil(t, err)
|
||||
result = reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 0, len(result))
|
||||
}
|
||||
|
||||
func TestHookRewardV2SplitReward(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// set switch to 1800, so that it covers 901-1799, 1800-2700 two epochs
|
||||
config.XDPoS.V2.SwitchBlock.SetUint64(1800)
|
||||
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, int(config.XDPoS.Epoch)*3, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
hooks.AttachConsensusV2Hooks(adaptor, blockchain, &config)
|
||||
assert.NotNil(t, adaptor.EngineV2.HookReward)
|
||||
// forcely insert signing tx into cache, to give rewards.
|
||||
header915 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 15)
|
||||
header916 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 16)
|
||||
// header917 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch + 17)
|
||||
header1785 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 15)
|
||||
header1799 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 - 1)
|
||||
header1801 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*2 + 1)
|
||||
tx, err := signingTxWithSignerFn(header915, 0, signer, signFn)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header916.Hash(), []*types.Transaction{tx})
|
||||
tx2, err := signingTxWithKey(header915, 0, acc1Key)
|
||||
assert.Nil(t, err)
|
||||
tx3, err := signingTxWithKey(header1785, 0, acc1Key)
|
||||
assert.Nil(t, err)
|
||||
adaptor.CacheSigningTxs(header1799.Hash(), []*types.Transaction{tx2, tx3})
|
||||
|
||||
statedb, err := blockchain.StateAt(header1799.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState := statedb.Copy()
|
||||
reward, err := adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header1801)
|
||||
assert.Nil(t, err)
|
||||
assert.Zero(t, len(reward))
|
||||
header2699 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch*3 - 1)
|
||||
header2700 := blockchain.GetHeaderByNumber(config.XDPoS.Epoch * 3)
|
||||
statedb, err = blockchain.StateAt(header2699.Root)
|
||||
assert.Nil(t, err)
|
||||
parentState = statedb.Copy()
|
||||
reward, err = adaptor.EngineV2.HookReward(blockchain, statedb, parentState, header2700)
|
||||
assert.Nil(t, err)
|
||||
result := reward["rewards"].(map[common.Address]interface{})
|
||||
assert.Equal(t, 2, len(result))
|
||||
// two signing account, 3 txs, reward is split by 1:2 (total reward is 250...000)
|
||||
for addr, x := range result {
|
||||
if addr == acc1Addr {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
owner := state.GetCandidateOwner(parentState, acc1Addr)
|
||||
a, _ := big.NewInt(0).SetString("149999999999999999999", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("16666666666666666666", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
} else if addr == signer {
|
||||
r := x.(map[common.Address]*big.Int)
|
||||
owner := state.GetCandidateOwner(parentState, signer)
|
||||
a, _ := big.NewInt(0).SetString("74999999999999999999", 10)
|
||||
assert.Zero(t, a.Cmp(r[owner]))
|
||||
b, _ := big.NewInt(0).SetString("8333333333333333333", 10)
|
||||
assert.Zero(t, b.Cmp(r[common.HexToAddress("0x0000000000000000000000000000000000000068")]))
|
||||
}
|
||||
}
|
||||
}
|
||||
102
consensus/tests/engine_v2_tests/sync_info_test.go
Normal file
102
consensus/tests/engine_v2_tests/sync_info_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSyncInfoShouldSuccessfullyUpdateByQC(t *testing.T) {
|
||||
// Block 901 is the first v2 block with starting round of 0
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: &types.TimeoutCert{
|
||||
Round: types.Round(2),
|
||||
Signatures: []types.Signature{},
|
||||
},
|
||||
}
|
||||
|
||||
err = engineV2.SyncInfoHandler(blockchain, syncInfoMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// QC is parent block's qc, which is pointing at round 4, hence 4 + 1 = 5
|
||||
assert.Equal(t, types.Round(5), round)
|
||||
assert.Equal(t, extraField.QuorumCert, highestQuorumCert)
|
||||
assert.Equal(t, types.Round(2), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(902), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestSyncInfoShouldSuccessfullyUpdateByTC(t *testing.T) {
|
||||
// Block 901 is the first v2 block with starting round of 0
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(currentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
highestTC := &types.TimeoutCert{
|
||||
Round: types.Round(6),
|
||||
Signatures: []types.Signature{},
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: highestTC,
|
||||
}
|
||||
|
||||
err = engineV2.SyncInfoHandler(blockchain, syncInfoMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
round, _, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), round)
|
||||
assert.Equal(t, extraField.QuorumCert, highestQuorumCert)
|
||||
}
|
||||
|
||||
func TestSkipVerifySyncInfoIfBothQcTcNotQualified(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Make the Highest QC in syncInfo point to an old block to simulate it's no longer qualified
|
||||
parentBlock := blockchain.GetBlockByNumber(903)
|
||||
var extraField types.ExtraFields_v2
|
||||
err := utils.DecodeBytesExtraFields(parentBlock.Extra(), &extraField)
|
||||
if err != nil {
|
||||
t.Fatal("Fail to decode extra data", err)
|
||||
}
|
||||
|
||||
highestTC := &types.TimeoutCert{
|
||||
Round: types.Round(5),
|
||||
Signatures: []types.Signature{},
|
||||
}
|
||||
|
||||
syncInfoMsg := &types.SyncInfo{
|
||||
HighestQuorumCert: extraField.QuorumCert,
|
||||
HighestTimeoutCert: highestTC,
|
||||
}
|
||||
|
||||
engineV2.SetPropertiesFaker(syncInfoMsg.HighestQuorumCert, syncInfoMsg.HighestTimeoutCert)
|
||||
|
||||
verified, err := engineV2.VerifySyncInfoMessage(blockchain, syncInfoMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
352
consensus/tests/engine_v2_tests/timeout_test.go
Normal file
352
consensus/tests/engine_v2_tests/timeout_test.go
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCountdownTimeoutToSendTimeoutMessage(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
timeoutMsg := <-engineV2.BroadcastCh
|
||||
poolSize := engineV2.GetTimeoutPoolSizeFaker(timeoutMsg.(*types.Timeout))
|
||||
assert.Equal(t, poolSize, 1)
|
||||
assert.NotNil(t, timeoutMsg)
|
||||
assert.Equal(t, uint64(450), timeoutMsg.(*types.Timeout).GapNumber)
|
||||
assert.Equal(t, types.Round(1), timeoutMsg.(*types.Timeout).Round)
|
||||
}
|
||||
|
||||
func TestCountdownTimeoutNotToSendTimeoutMessageIfNotInMasternodeList(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
differentSigner, differentSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
// Let's change the address
|
||||
engineV2.Authorize(differentSigner, differentSignFn)
|
||||
|
||||
engineV2.SetNewRoundFaker(blockchain, 1, true)
|
||||
|
||||
select {
|
||||
case <-engineV2.BroadcastCh:
|
||||
t.Fatalf("Not suppose to receive timeout msg")
|
||||
case <-time.After(10 * time.Second): //Countdown is only 1s wait, let's wait for 3s here
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncInfoAfterReachTimeoutSyncThreadhold(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
engineV2.SetNewRoundFaker(blockchain, 1, true)
|
||||
|
||||
// Because messages are sending async and on random order, so use this way to test
|
||||
var timeoutCounter, syncInfoCounter int
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 2, timeoutCounter)
|
||||
assert.Equal(t, 1, syncInfoCounter)
|
||||
|
||||
t.Log("waiting for another consecutive period")
|
||||
// another consecutive period
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, 4, timeoutCounter)
|
||||
assert.Equal(t, 2, syncInfoCounter)
|
||||
}
|
||||
|
||||
func TestTimeoutPeriodAndThreadholdConfigChange(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 1799, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
// engineV2.SetNewRoundFaker(blockchain, 1, true)
|
||||
|
||||
// Because messages are sending async and on random order, so use this way to test
|
||||
var timeoutCounter, syncInfoCounter int
|
||||
for i := 0; i < 3; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
|
||||
assert.Equal(t, 2, timeoutCounter)
|
||||
assert.Equal(t, 1, syncInfoCounter)
|
||||
|
||||
// Create another block to trigger update parameters
|
||||
blockNum := 1800
|
||||
blockCoinBase := "0x111000000000000000000000000000000123"
|
||||
currentBlock = CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 900, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
currentBlockHeader := currentBlock.Header()
|
||||
currentBlockHeader.Time = big.NewInt(time.Now().Unix())
|
||||
err := blockchain.InsertBlock(currentBlock)
|
||||
assert.Nil(t, err)
|
||||
|
||||
engineV2.UpdateParams(currentBlockHeader) // it will be triggered automatically on the real code by other process
|
||||
|
||||
t.Log("waiting for another consecutive period")
|
||||
// another consecutive period
|
||||
t1 := time.Now()
|
||||
for i := 0; i < 5; i++ {
|
||||
obj := <-engineV2.BroadcastCh
|
||||
switch v := obj.(type) {
|
||||
case *types.Timeout:
|
||||
timeoutCounter++
|
||||
case *types.SyncInfo:
|
||||
syncInfoCounter++
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
t2 := time.Now()
|
||||
timediff := t2.Sub(t1).Seconds()
|
||||
assert.Equal(t, 6, timeoutCounter)
|
||||
assert.Equal(t, 2, syncInfoCounter)
|
||||
assert.Less(t, timediff, float64(20))
|
||||
}
|
||||
|
||||
// Timeout handler
|
||||
func TestTimeoutMessageHandlerSuccessfullyGenerateTCandSyncInfo(t *testing.T) {
|
||||
params.TestXDPoSMockChainConfig.XDPoS.V2.CurrentConfig = params.UnitTestV2Configs[0]
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 1
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
// Create two timeout message which will not reach timeout pool threshold
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{2},
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Send a timeout with different gap number, it shall not trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{3},
|
||||
GapNumber: 1350,
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Create a timeout message that should trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
Signature: []byte{4},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
syncInfoMsg := <-engineV2.BroadcastCh
|
||||
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
|
||||
assert.NotNil(t, syncInfoMsg)
|
||||
|
||||
// Shouldn't have QC, however, we did not inilise it, hence will show default empty value
|
||||
qc := syncInfoMsg.(*types.SyncInfo).HighestQuorumCert
|
||||
assert.Equal(t, types.Round(0), qc.ProposedBlockInfo.Round)
|
||||
|
||||
tc := syncInfoMsg.(*types.SyncInfo).HighestTimeoutCert
|
||||
assert.NotNil(t, tc)
|
||||
assert.Equal(t, tc.Round, types.Round(1))
|
||||
assert.Equal(t, uint64(450), tc.GapNumber)
|
||||
// The signatures shall not include the byte{3} from a different gap number
|
||||
sigatures := []types.Signature{[]byte{1}, []byte{2}, []byte{4}}
|
||||
assert.ElementsMatch(t, tc.Signatures, sigatures)
|
||||
assert.Equal(t, types.Round(2), currentRound)
|
||||
}
|
||||
|
||||
func TestThrowErrorIfTimeoutMsgRoundNotEqualToCurrentRound(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 11, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 3
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(3), false)
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(2),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err := engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
// Timeout msg round > currentRound
|
||||
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 3", err.Error())
|
||||
|
||||
// Set round to 1
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
// Timeout msg round < currentRound
|
||||
assert.Equal(t, "timeout message round number: 2 does not match currentRound: 1", err.Error())
|
||||
}
|
||||
|
||||
func TestShouldVerifyTimeoutMessageForFirstV2Block(t *testing.T) {
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(1),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
assert.Nil(t, err)
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(1),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Equal(t, timeoutMsg.GetSigner(), signer)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
|
||||
signedHash, err = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(2),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
assert.Nil(t, err)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(2),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err = engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Equal(t, timeoutMsg.GetSigner(), signer)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
}
|
||||
|
||||
func TestShouldVerifyTimeoutMessage(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 2251, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
signedHash := SignHashByPK(acc1Key, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(5000),
|
||||
GapNumber: 2250,
|
||||
}).Bytes())
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5000),
|
||||
GapNumber: 2250,
|
||||
Signature: signedHash,
|
||||
}
|
||||
|
||||
verified, err := engineV2.VerifyTimeoutMessage(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
assert.True(t, verified)
|
||||
}
|
||||
|
||||
func TestTimeoutPoolKeyGoodHygiene(t *testing.T) {
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Inject the first timeout with round 5
|
||||
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(5),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Inject a second timeout with round 16
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(16),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(16),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(16), false)
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Inject a third timeout with round 17
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, types.TimeoutSigHash(&types.TimeoutForSign{
|
||||
Round: types.Round(17),
|
||||
GapNumber: 450,
|
||||
}).Bytes())
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(17),
|
||||
GapNumber: 450,
|
||||
Signature: signedHash,
|
||||
}
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(17), false)
|
||||
engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
|
||||
// Let's keep good Hygiene
|
||||
engineV2.HygieneTimeoutPoolFaker()
|
||||
// Let's wait for 5 second for the goroutine
|
||||
<-time.After(5 * time.Second)
|
||||
keyList := engineV2.GetTimeoutPoolKeyListFaker()
|
||||
|
||||
assert.Equal(t, 2, len(keyList))
|
||||
for _, k := range keyList {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
assert.Nil(t, err)
|
||||
if keyedRound < 25-10 {
|
||||
assert.Fail(t, "Did not clean up the timeout pool")
|
||||
}
|
||||
}
|
||||
}
|
||||
64
consensus/tests/engine_v2_tests/verify_blockinfo_test.go
Normal file
64
consensus/tests/engine_v2_tests/verify_blockinfo_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldVerifyBlockInfo(t *testing.T) {
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err := engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Insert another Block, but it won't trigger commit
|
||||
blockNum := 902
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block902 := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 2, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
err = blockchain.InsertBlock(block902)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: block902.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.Nil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(3),
|
||||
Number: block902.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: block902.Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: currentBlock.Number(),
|
||||
}
|
||||
err = engineV2.VerifyBlockInfo(blockchain, blockInfo, nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
453
consensus/tests/engine_v2_tests/verify_header_test.go
Normal file
453
consensus/tests/engine_v2_tests/verify_header_test.go
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestShouldVerifyBlock(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Happy path
|
||||
happyPathHeader := blockchain.GetBlockByNumber(901).Header()
|
||||
err = adaptor.VerifyHeader(blockchain, happyPathHeader, true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Unhappy path
|
||||
|
||||
// Verify non-epoch switch block
|
||||
err = adaptor.VerifyHeader(blockchain, blockchain.GetBlockByNumber(902).Header(), true)
|
||||
assert.Nil(t, err)
|
||||
|
||||
nonEpochSwitchWithValidators := blockchain.GetBlockByNumber(902).Header()
|
||||
nonEpochSwitchWithValidators.Validators = acc1Addr.Bytes()
|
||||
err = adaptor.VerifyHeader(blockchain, nonEpochSwitchWithValidators, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
noValidatorBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
noValidatorBlock.Validator = []byte{}
|
||||
err = adaptor.VerifyHeader(blockchain, noValidatorBlock, true)
|
||||
assert.Equal(t, consensus.ErrNoValidatorSignature, err)
|
||||
|
||||
blockFromFuture := blockchain.GetBlockByNumber(902).Header()
|
||||
blockFromFuture.Time = big.NewInt(time.Now().Unix() + 10000)
|
||||
err = adaptor.VerifyHeader(blockchain, blockFromFuture, true)
|
||||
assert.Equal(t, consensus.ErrFutureBlock, err)
|
||||
|
||||
invalidQcBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidQcBlock.Extra = []byte{2}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidQcBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidV2Extra, err)
|
||||
|
||||
// Epoch switch
|
||||
invalidAuthNonceBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
invalidAuthNonceBlock.Nonce = types.BlockNonce{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidAuthNonceBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidVote, err)
|
||||
|
||||
emptyValidatorsBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
emptyValidatorsBlock.Validators = []byte{}
|
||||
err = adaptor.VerifyHeader(blockchain, emptyValidatorsBlock, true)
|
||||
assert.Equal(t, utils.ErrEmptyEpochSwitchValidators, err)
|
||||
|
||||
invalidValidatorsSignerBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
invalidValidatorsSignerBlock.Validators = []byte{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidValidatorsSignerBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidCheckpointSigners, err)
|
||||
|
||||
// non-epoch switch
|
||||
invalidValidatorsExistBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidValidatorsExistBlock.Validators = []byte{123}
|
||||
err = adaptor.VerifyHeader(blockchain, invalidValidatorsExistBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
invalidPenaltiesExistBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidPenaltiesExistBlock.Penalties = common.Hex2BytesFixed("123131231", 20)
|
||||
err = adaptor.VerifyHeader(blockchain, invalidPenaltiesExistBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidFieldInNonEpochSwitch, err)
|
||||
|
||||
merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb123"
|
||||
parentNotExistBlock := blockchain.GetBlockByNumber(901).Header()
|
||||
parentNotExistBlock.ParentHash = common.HexToHash(merkleRoot)
|
||||
err = adaptor.VerifyHeader(blockchain, parentNotExistBlock, true)
|
||||
assert.Equal(t, consensus.ErrUnknownAncestor, err)
|
||||
|
||||
block901 := blockchain.GetBlockByNumber(901).Header()
|
||||
tooFastMinedBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
tooFastMinedBlock.Time = big.NewInt(block901.Time.Int64() - 10)
|
||||
err = adaptor.VerifyHeader(blockchain, tooFastMinedBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidTimestamp, err)
|
||||
|
||||
invalidDifficultyBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidDifficultyBlock.Difficulty = big.NewInt(2)
|
||||
err = adaptor.VerifyHeader(blockchain, invalidDifficultyBlock, true)
|
||||
assert.Equal(t, utils.ErrInvalidDifficulty, err)
|
||||
|
||||
// Create an invalid QC round
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: blockchain.GetBlockByNumber(902).Hash(),
|
||||
Round: types.Round(2),
|
||||
Number: blockchain.GetBlockByNumber(902).Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
// Genrate QC
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error generate QC by creating signedHash: %v", err))
|
||||
}
|
||||
// Sign from acc 1, 2, 3
|
||||
acc1SignedHash := SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc2SignedHash := SignHashByPK(acc2Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc3SignedHash := SignHashByPK(acc3Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
var signatures []types.Signature
|
||||
signatures = append(signatures, signedHash, acc1SignedHash, acc2SignedHash, acc3SignedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(2),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
|
||||
invalidRoundBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
invalidRoundBlock.Extra = extraInBytes
|
||||
err = adaptor.VerifyHeader(blockchain, invalidRoundBlock, true)
|
||||
assert.Equal(t, utils.ErrRoundInvalid, err)
|
||||
|
||||
// Not valid validator
|
||||
coinbaseValidatorMismatchBlock := blockchain.GetBlockByNumber(902).Header()
|
||||
notQualifiedSigner, notQualifiedSignFn, err := getSignerAndSignFn(voterKey)
|
||||
assert.Nil(t, err)
|
||||
sealHeader(blockchain, coinbaseValidatorMismatchBlock, notQualifiedSigner, notQualifiedSignFn)
|
||||
err = adaptor.VerifyHeader(blockchain, coinbaseValidatorMismatchBlock, true)
|
||||
assert.Equal(t, utils.ErrCoinbaseAndValidatorMismatch, err)
|
||||
|
||||
// Make the validators not legit by adding something to the validator
|
||||
validatorsNotLegit := blockchain.GetBlockByNumber(901).Header()
|
||||
validatorsNotLegit.Validators = append(validatorsNotLegit.Validators, acc1Addr[:]...)
|
||||
err = adaptor.VerifyHeader(blockchain, validatorsNotLegit, true)
|
||||
assert.Equal(t, utils.ErrValidatorsNotLegit, err)
|
||||
|
||||
// Make the penalties not legit by adding something to the penalty
|
||||
penaltiesNotLegit := blockchain.GetBlockByNumber(901).Header()
|
||||
penaltiesNotLegit.Penalties = append(penaltiesNotLegit.Penalties, acc1Addr[:]...)
|
||||
err = adaptor.VerifyHeader(blockchain, penaltiesNotLegit, true)
|
||||
assert.Equal(t, utils.ErrPenaltiesNotLegit, err)
|
||||
}
|
||||
|
||||
func TestConfigSwitchOnDifferentCertThreshold(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Genrate 911 QC
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: blockchain.GetBlockByNumber(911).Hash(),
|
||||
Round: types.Round(11),
|
||||
Number: blockchain.GetBlockByNumber(911).Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// Sign from acc 1, 2, 3
|
||||
acc1SignedHash := SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc2SignedHash := SignHashByPK(acc2Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc3SignedHash := SignHashByPK(acc3Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
var signaturesFirst []types.Signature
|
||||
signaturesFirst = append(signaturesFirst, acc1SignedHash, acc2SignedHash, acc3SignedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signaturesFirst,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(12),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, _ := extra.EncodeToBytes()
|
||||
|
||||
// after 910 require 5 signs, but we only give 3 signs
|
||||
block912 := blockchain.GetBlockByNumber(912).Header()
|
||||
block912.Extra = extraInBytes
|
||||
err = adaptor.VerifyHeader(blockchain, block912, true)
|
||||
|
||||
assert.Equal(t, utils.ErrInvalidQCSignatures, err)
|
||||
|
||||
// Make we verification process use the corresponding config
|
||||
// Genrate 910 QC
|
||||
proposedBlockInfo = &types.BlockInfo{
|
||||
Hash: blockchain.GetBlockByNumber(910).Hash(),
|
||||
Round: types.Round(10),
|
||||
Number: blockchain.GetBlockByNumber(910).Number(),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// Sign from acc 1, 2, 3
|
||||
acc1SignedHash = SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc2SignedHash = SignHashByPK(acc2Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc3SignedHash = SignHashByPK(acc3Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
|
||||
var signaturesThr []types.Signature
|
||||
signaturesThr = append(signaturesThr, acc1SignedHash, acc2SignedHash, acc3SignedHash)
|
||||
quorumCert = &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signaturesThr,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra = types.ExtraFields_v2{
|
||||
Round: types.Round(11),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, _ = extra.EncodeToBytes()
|
||||
|
||||
// QC contains 910, so it requires 3 signatures, not use block number to determine which config to use
|
||||
block911 := blockchain.GetBlockByNumber(911).Header()
|
||||
block911.Extra = extraInBytes
|
||||
err = adaptor.VerifyHeader(blockchain, block911, true)
|
||||
|
||||
// error ErrValidatorNotWithinMasternodes means verifyQC is passed and move to next verification process
|
||||
assert.Equal(t, utils.ErrValidatorNotWithinMasternodes, err)
|
||||
}
|
||||
|
||||
func TestConfigSwitchOnDifferentMindPeriod(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, &config, nil)
|
||||
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Genrate 911 QC
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: blockchain.GetBlockByNumber(911).Hash(),
|
||||
Round: types.Round(11),
|
||||
Number: blockchain.GetBlockByNumber(911).Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// Sign from acc 1, 2, 3
|
||||
acc1SignedHash := SignHashByPK(acc1Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc2SignedHash := SignHashByPK(acc2Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
acc3SignedHash := SignHashByPK(acc3Key, types.VoteSigHash(voteForSign).Bytes())
|
||||
var signaturesFirst []types.Signature
|
||||
signaturesFirst = append(signaturesFirst, acc1SignedHash, acc2SignedHash, acc3SignedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signaturesFirst,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(12),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, _ := extra.EncodeToBytes()
|
||||
|
||||
// after 910 require 5 signs, but we only give 3 signs
|
||||
block911 := blockchain.GetBlockByNumber(911).Header()
|
||||
block911.Extra = extraInBytes
|
||||
block911.Time = big.NewInt(blockchain.GetBlockByNumber(910).Time().Int64() + 2) //2 is previous config, should get the right config from round
|
||||
err = adaptor.VerifyHeader(blockchain, block911, true)
|
||||
|
||||
assert.Equal(t, utils.ErrInvalidTimestamp, err)
|
||||
}
|
||||
|
||||
func TestShouldFailIfNotEnoughQCSignatures(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 902, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
parentBlock := blockchain.GetBlockByNumber(901)
|
||||
proposedBlockInfo := &types.BlockInfo{
|
||||
Hash: parentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: parentBlock.Number(),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
assert.Nil(t, err)
|
||||
var signatures []types.Signature
|
||||
// Duplicate the signatures
|
||||
signatures = append(signatures, signedHash, signedHash, signedHash, signedHash, signedHash, signedHash)
|
||||
quorumCert := &types.QuorumCert{
|
||||
ProposedBlockInfo: proposedBlockInfo,
|
||||
Signatures: signatures,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
extra := types.ExtraFields_v2{
|
||||
Round: types.Round(2),
|
||||
QuorumCert: quorumCert,
|
||||
}
|
||||
extraInBytes, err := extra.EncodeToBytes()
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("Error encode extra into bytes: %v", err))
|
||||
}
|
||||
headerWithDuplicatedSignatures := currentBlock.Header()
|
||||
headerWithDuplicatedSignatures.Extra = extraInBytes
|
||||
// Happy path
|
||||
err = adaptor.VerifyHeader(blockchain, headerWithDuplicatedSignatures, true)
|
||||
assert.Equal(t, utils.ErrInvalidQCSignatures, err)
|
||||
|
||||
}
|
||||
|
||||
func TestShouldVerifyHeaders(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
// Happy path
|
||||
var happyPathHeaders []*types.Header
|
||||
happyPathHeaders = append(happyPathHeaders, blockchain.GetBlockByNumber(899).Header(), blockchain.GetBlockByNumber(900).Header(), blockchain.GetBlockByNumber(901).Header(), blockchain.GetBlockByNumber(902).Header())
|
||||
// Randomly set full verify
|
||||
var fullVerifies []bool
|
||||
fullVerifies = append(fullVerifies, false, true, true, false)
|
||||
_, results := adaptor.VerifyHeaders(blockchain, happyPathHeaders, fullVerifies)
|
||||
var verified []bool
|
||||
for {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
panic("Error received while verifying headers")
|
||||
}
|
||||
verified = append(verified, true)
|
||||
case <-time.After(time.Duration(5) * time.Second): // It should be very fast to verify headers
|
||||
if len(verified) == len(happyPathHeaders) {
|
||||
return
|
||||
} else {
|
||||
panic("Suppose to have verified 3 block headers")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldVerifyHeadersEvenIfParentsNotYetWrittenIntoDB(t *testing.T) {
|
||||
b, err := json.Marshal(params.TestXDPoSMockChainConfig)
|
||||
assert.Nil(t, err)
|
||||
configString := string(b)
|
||||
|
||||
var config params.ChainConfig
|
||||
err = json.Unmarshal([]byte(configString), &config)
|
||||
assert.Nil(t, err)
|
||||
// Enable verify
|
||||
config.XDPoS.V2.SkipV2Validation = false
|
||||
// Block 901 is the first v2 block with round of 1
|
||||
blockchain, _, block910, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 910, &config, nil)
|
||||
adaptor := blockchain.Engine().(*XDPoS.XDPoS)
|
||||
|
||||
var headersTobeVerified []*types.Header
|
||||
|
||||
// Create block 911 but don't write into DB
|
||||
blockNumber := 911
|
||||
roundNumber := int64(blockNumber) - config.XDPoS.V2.SwitchBlock.Int64()
|
||||
block911 := CreateBlock(blockchain, &config, block910, blockNumber, roundNumber, signer.Hex(), signer, signFn, nil, nil, "")
|
||||
|
||||
// Create block 912 and not write into DB as well
|
||||
blockNumber = 912
|
||||
roundNumber = int64(blockNumber) - config.XDPoS.V2.SwitchBlock.Int64()
|
||||
block912 := CreateBlock(blockchain, &config, block911, blockNumber, roundNumber, signer.Hex(), signer, signFn, nil, nil, "")
|
||||
|
||||
headersTobeVerified = append(headersTobeVerified, block910.Header(), block911.Header(), block912.Header())
|
||||
// Randomly set full verify
|
||||
var fullVerifies []bool
|
||||
fullVerifies = append(fullVerifies, true, true, true)
|
||||
_, results := adaptor.VerifyHeaders(blockchain, headersTobeVerified, fullVerifies)
|
||||
|
||||
var verified []bool
|
||||
for {
|
||||
select {
|
||||
case result := <-results:
|
||||
if result != nil {
|
||||
panic("Error received while verifying headers")
|
||||
}
|
||||
verified = append(verified, true)
|
||||
case <-time.After(time.Duration(5) * time.Second): // It should be very fast to verify headers
|
||||
if len(verified) == len(headersTobeVerified) {
|
||||
return
|
||||
} else {
|
||||
panic("Suppose to have verified 3 block headers")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
679
consensus/tests/engine_v2_tests/vote_test.go
Normal file
679
consensus/tests/engine_v2_tests/vote_test.go
Normal file
|
|
@ -0,0 +1,679 @@
|
|||
package engine_v2_tests
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// VoteHandler
|
||||
func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQCForFistV2Round(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 901, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(1),
|
||||
Number: big.NewInt(901),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 1
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(1), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
signedHash = SignHashByPK(acc2Key, voteSigningHash.Bytes())
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(1), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(0), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
// Check round has now changed from 1 to 2
|
||||
assert.Equal(t, types.Round(2), currentRound)
|
||||
}
|
||||
|
||||
func TestVoteMessageHandlerSuccessfullyGeneratedAndProcessQC(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, err := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create another vote which is signed by someone not from the master node list
|
||||
|
||||
randomSigner, randomSignFn, err := backends.SimulateWalletAddressAndSignFn()
|
||||
assert.Nil(t, err)
|
||||
randomlySignedHash, err := randomSignFn(accounts.Account{Address: randomSigner}, voteSigningHash.Bytes())
|
||||
assert.Nil(t, err)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: randomlySignedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook and increment the round to 6
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(4), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
// Check round has now changed from 5 to 6
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
// Should trigger ProcessQC and trying to commit from blockNum of 16's grandgrandparent which is blockNum 903 with round 3
|
||||
assert.Equal(t, types.Round(3), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(903), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestThrowErrorIfVoteMsgRoundIsMoreThanOneRoundAwayFromCurrentRound(t *testing.T) {
|
||||
blockchain, _, _, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: common.HexToHash("0x1"),
|
||||
Round: types.Round(6),
|
||||
Number: big.NewInt(999),
|
||||
}
|
||||
|
||||
// Set round to 7
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(7), false)
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// voteRound > currentRound
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 7", err.Error())
|
||||
|
||||
// Set round to 5, it's 1 round away, should not trigger failure
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(4), false)
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "vote message round number: 6 is too far away from currentRound: 4", err.Error())
|
||||
|
||||
}
|
||||
|
||||
func TestProcessVoteMsgThenTimeoutMsg(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
// Start with vote messages
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
// Create two vote message which will not reach vote pool threshold
|
||||
signedHash := SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
// Check round has now changed from 5 to 6
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(4), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// We shall have highestQuorumCert in engine now, let's do timeout msg to see if we can broadcast SyncInfo which contains both highestQuorumCert and HighestTimeoutCert
|
||||
|
||||
// First, all incoming old timeout msg shall not be processed
|
||||
timeoutMsg := &types.Timeout{
|
||||
Round: types.Round(5),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.NotNil(t, err)
|
||||
assert.Equal(t, "timeout message round number: 5 does not match currentRound: 6", err.Error())
|
||||
|
||||
// Ok, let's do the timeout msg which is on the same round as the current round by creating two timeout message which will not reach timeout pool threshold
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{1},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{2},
|
||||
}
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// Create a timeout message that should trigger timeout pool hook
|
||||
timeoutMsg = &types.Timeout{
|
||||
Round: types.Round(6),
|
||||
Signature: []byte{3},
|
||||
}
|
||||
|
||||
err = engineV2.TimeoutHandler(blockchain, timeoutMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
syncInfoMsg := <-engineV2.BroadcastCh
|
||||
assert.NotNil(t, syncInfoMsg)
|
||||
|
||||
// Should have HighestQuorumCert from previous round votes
|
||||
qc := syncInfoMsg.(*types.SyncInfo).HighestQuorumCert
|
||||
assert.NotNil(t, qc)
|
||||
assert.Equal(t, types.Round(5), qc.ProposedBlockInfo.Round)
|
||||
|
||||
tc := syncInfoMsg.(*types.SyncInfo).HighestTimeoutCert
|
||||
assert.NotNil(t, tc)
|
||||
assert.Equal(t, types.Round(6), tc.Round)
|
||||
sigatures := []types.Signature{[]byte{1}, []byte{2}, []byte{3}}
|
||||
assert.ElementsMatch(t, tc.Signatures, sigatures)
|
||||
// Round shall be +1 now
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(7), currentRound)
|
||||
}
|
||||
|
||||
func TestVoteMessageShallNotThrowErrorIfBlockNotYetExist(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Create a new block but don't inject it into the chain yet
|
||||
blockNum := 906
|
||||
blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", blockNum)
|
||||
block := CreateBlock(blockchain, params.TestXDPoSMockChainConfig, currentBlock, blockNum, 6, blockCoinBase, signer, signFn, nil, nil, "")
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: block.Header().Hash(),
|
||||
Round: types.Round(6),
|
||||
Number: big.NewInt(906),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 6
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(6), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc1Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook, but it shall not produce any QC yet
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// Still using the initlised value because we did not yet go to the next round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(6), currentRound)
|
||||
|
||||
// Now, inject the block into the chain
|
||||
err = blockchain.InsertBlock(block)
|
||||
assert.Nil(t, err)
|
||||
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(voterKey, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, highestCommitBlock := engineV2.GetPropertiesFaker()
|
||||
// The lockQC shall be the parent's QC round number
|
||||
assert.Equal(t, types.Round(5), lockQuorumCert.ProposedBlockInfo.Round)
|
||||
// The highestQC proposedBlockInfo shall be the same as the one from its votes
|
||||
assert.Equal(t, highestQuorumCert.ProposedBlockInfo, voteMsg.ProposedBlockInfo)
|
||||
assert.Equal(t, types.Round(7), currentRound)
|
||||
// Should trigger ProcessQC and trying to commit from blockNum of 16's grandgrandparent which is blockNum 904 with round 4
|
||||
assert.Equal(t, types.Round(4), highestCommitBlock.Round)
|
||||
assert.Equal(t, big.NewInt(904), highestCommitBlock.Number)
|
||||
}
|
||||
|
||||
func TestProcessVoteMsgFailIfVerifyBlockInfoFail(t *testing.T) {
|
||||
blockchain, _, currentBlock, _, _, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
|
||||
// Start with vote messages
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.ParentHash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
// Create two vote message which will not reach vote pool threshold
|
||||
signedHash := SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, lockQuorumCert, highestQuorumCert, _, _, _ := engineV2.GetPropertiesFaker()
|
||||
// initialised with nil and 0 round
|
||||
assert.Nil(t, lockQuorumCert)
|
||||
assert.Equal(t, types.Round(0), highestQuorumCert.ProposedBlockInfo.Round)
|
||||
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc2Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
assert.Nil(t, err)
|
||||
currentRound, _, _, _, _, _ = engineV2.GetPropertiesFaker()
|
||||
assert.Equal(t, types.Round(5), currentRound)
|
||||
|
||||
// Create a vote message that should trigger vote pool hook
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: SignHashByPK(acc3Key, voteSigningHash.Bytes()),
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
err = engineV2.VoteHandler(blockchain, voteMsg)
|
||||
expectedError := fmt.Errorf("[VerifyBlockInfo] chain header number does not match for the received blockInfo at hash: %v", blockInfo.Hash.Hex())
|
||||
assert.Equal(t, expectedError, err)
|
||||
}
|
||||
|
||||
func TestVerifyVoteMsg(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(14),
|
||||
Number: big.NewInt(915),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
// Valid message but disqualified as the round does not match
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: []byte{1},
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(15), false)
|
||||
verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Nil(t, err)
|
||||
|
||||
// Invalid vote message with wrong signature
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(14), false)
|
||||
verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.Equal(t, "Error while verifying message: invalid signature length", err.Error())
|
||||
|
||||
// Valid vote message from a master node
|
||||
signHash, _ := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
verified, err = engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.Equal(t, voteMsg.GetSigner(), signer)
|
||||
assert.True(t, verified)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestVoteMsgMissingSnapshot(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 915, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(14),
|
||||
Number: big.NewInt(915),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
signHash, _ := signFn(accounts.Account{Address: signer}, types.VoteSigHash(voteForSign).Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signHash,
|
||||
GapNumber: 1350, // missing 1350 snapshot
|
||||
}
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(14), false)
|
||||
verified, err := engineV2.VerifyVoteMessage(blockchain, voteMsg)
|
||||
assert.False(t, verified)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestVoteMessageHandlerWrongGapNumber(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
signedHash = SignHashByPK(acc1Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Create a vote message that has wrong gap number
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 451,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
signedHash = SignHashByPK(acc3Key, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 451,
|
||||
}
|
||||
|
||||
err := engineV2.VoteHandler(blockchain, voteMsg)
|
||||
// Shall not even trigger the vote threashold as vote pool key also contains the gapNumber
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
func TestVotePoolKeepGoodHygiene(t *testing.T) {
|
||||
blockchain, _, currentBlock, signer, signFn, _ := PrepareXDCTestBlockChainForV2Engine(t, 905, params.TestXDPoSMockChainConfig, nil)
|
||||
engineV2 := blockchain.Engine().(*XDPoS.XDPoS).EngineV2
|
||||
|
||||
blockInfo := &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(5),
|
||||
Number: big.NewInt(905),
|
||||
}
|
||||
voteForSign := &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash := types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 5
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(5), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ := signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg := &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Inject a second vote with round 16
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(16),
|
||||
Number: big.NewInt(906),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 16
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(16), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Inject a second vote with round 25, which is less than 10 rounds difference to the last vote round
|
||||
blockInfo = &types.BlockInfo{
|
||||
Hash: currentBlock.Hash(),
|
||||
Round: types.Round(25),
|
||||
Number: big.NewInt(907),
|
||||
}
|
||||
voteForSign = &types.VoteForSign{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
GapNumber: 450,
|
||||
}
|
||||
voteSigningHash = types.VoteSigHash(voteForSign)
|
||||
|
||||
// Set round to 25
|
||||
engineV2.SetNewRoundFaker(blockchain, types.Round(25), false)
|
||||
// Create two vote messages which will not reach vote pool threshold
|
||||
signedHash, _ = signFn(accounts.Account{Address: signer}, voteSigningHash.Bytes())
|
||||
voteMsg = &types.Vote{
|
||||
ProposedBlockInfo: blockInfo,
|
||||
Signature: signedHash,
|
||||
GapNumber: 450,
|
||||
}
|
||||
engineV2.VoteHandler(blockchain, voteMsg)
|
||||
|
||||
// Let's keep good Hygiene
|
||||
engineV2.HygieneVotePoolFaker()
|
||||
// Let's wait for 5 second for the goroutine
|
||||
<-time.After(5 * time.Second)
|
||||
keyList := engineV2.GetVotePoolKeyListFaker()
|
||||
|
||||
assert.Equal(t, 2, len(keyList))
|
||||
for _, k := range keyList {
|
||||
keyedRound, err := strconv.ParseInt(strings.Split(k, ":")[0], 10, 64)
|
||||
assert.Nil(t, err)
|
||||
if keyedRound < 25-10 {
|
||||
assert.Fail(t, "Did not clean up the vote pool")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@ const (
|
|||
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
|
||||
)
|
||||
|
||||
type rewardLog struct {
|
||||
type RewardLog struct {
|
||||
Sign uint64 `json:"sign"`
|
||||
Reward *big.Int `json:"reward"`
|
||||
}
|
||||
|
|
@ -319,13 +319,13 @@ func DecryptRandomizeFromSecretsAndOpening(secrets [][32]byte, opening [32]byte)
|
|||
}
|
||||
|
||||
// Calculate reward for reward checkpoint.
|
||||
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*rewardLog, error) {
|
||||
func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header *types.Header, rCheckpoint uint64, totalSigner *uint64) (map[common.Address]*RewardLog, error) {
|
||||
// Not reward for singer of genesis block and only calculate reward at checkpoint block.
|
||||
number := header.Number.Uint64()
|
||||
prevCheckpoint := number - (rCheckpoint * 2)
|
||||
startBlockNumber := prevCheckpoint + 1
|
||||
endBlockNumber := startBlockNumber + rCheckpoint - 1
|
||||
signers := make(map[common.Address]*rewardLog)
|
||||
signers := make(map[common.Address]*RewardLog)
|
||||
mapBlkHash := map[uint64]common.Hash{}
|
||||
|
||||
data := make(map[common.Hash][]common.Address)
|
||||
|
|
@ -352,7 +352,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
}
|
||||
}
|
||||
header = chain.GetHeader(header.ParentHash, prevCheckpoint)
|
||||
masternodes := utils.GetMasternodesFromCheckpointHeader(header)
|
||||
masternodes := c.GetMasternodesFromCheckpointHeader(header)
|
||||
|
||||
for i := startBlockNumber; i <= endBlockNumber; i++ {
|
||||
if i%common.MergeSignRange == 0 || !chain.Config().IsTIP2019(big.NewInt(int64(i))) {
|
||||
|
|
@ -376,7 +376,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
if exist {
|
||||
signers[addr].Sign++
|
||||
} else {
|
||||
signers[addr] = &rewardLog{1, new(big.Int)}
|
||||
signers[addr] = &RewardLog{1, new(big.Int)}
|
||||
}
|
||||
*totalSigner++
|
||||
}
|
||||
|
|
@ -390,7 +390,7 @@ func GetRewardForCheckpoint(c *XDPoS.XDPoS, chain consensus.ChainReader, header
|
|||
}
|
||||
|
||||
// Calculate reward for signers.
|
||||
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*rewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
|
||||
func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*RewardLog, totalSigner uint64) (map[common.Address]*big.Int, error) {
|
||||
resultSigners := make(map[common.Address]*big.Int)
|
||||
// Add reward for signers.
|
||||
if totalSigner > 0 {
|
||||
|
|
@ -404,12 +404,11 @@ func CalculateRewardForSigner(chainReward *big.Int, signers map[common.Address]*
|
|||
resultSigners[signer] = calcReward
|
||||
}
|
||||
}
|
||||
jsonSigners, err := json.Marshal(signers)
|
||||
if err != nil {
|
||||
log.Error("Fail to parse json signers", "error", err)
|
||||
return nil, err
|
||||
|
||||
log.Info("Signers data", "totalSigner", totalSigner, "totalReward", chainReward)
|
||||
for addr, signer := range signers {
|
||||
log.Info("Signer reward", "signer", addr, "sign", signer.Sign, "reward", signer.Reward)
|
||||
}
|
||||
log.Info("Signers data", "signers", string(jsonSigners), "totalSigner", totalSigner, "totalReward", chainReward)
|
||||
|
||||
return resultSigners, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,11 +17,12 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
|
@ -352,6 +353,13 @@ func (bc *BlockChain) loadLastState() error {
|
|||
}
|
||||
bc.hc.SetCurrentHeader(currentHeader)
|
||||
|
||||
if engine, ok := bc.Engine().(*XDPoS.XDPoS); ok {
|
||||
err := engine.Initial(bc, currentHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Restore the last known head fast block
|
||||
bc.currentFastBlock.Store(currentBlock)
|
||||
if head := GetHeadFastBlockHash(bc.db); head != (common.Hash{}) {
|
||||
|
|
@ -974,7 +982,20 @@ func (bc *BlockChain) procFutureBlocks() {
|
|||
|
||||
// Insert one by one as chain insertion needs contiguous ancestry between blocks
|
||||
for i := range blocks {
|
||||
bc.InsertChain(blocks[i : i+1])
|
||||
_, err := bc.InsertChain(blocks[i : i+1])
|
||||
// let consensus engine handle the last block (e.g. for voting)
|
||||
if i == len(blocks)-1 && err == nil {
|
||||
engine, ok := bc.Engine().(*XDPoS.XDPoS)
|
||||
if ok {
|
||||
go func() {
|
||||
header := blocks[i].Header()
|
||||
err = engine.HandleProposedBlock(bc, header)
|
||||
if err != nil {
|
||||
log.Info("[procFutureBlocks] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1392,7 +1413,7 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.
|
|||
if bc.chainConfig.XDPoS != nil && ((block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap)) {
|
||||
err := bc.UpdateM1()
|
||||
if err != nil {
|
||||
log.Crit("Error when update masternodes set. Stopping node", "err", err)
|
||||
log.Crit("Error when update masternodes set. Stopping node", "err", err, "blockNum", block.NumberU64())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1590,8 +1611,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
|||
bc.reportBlock(block, nil, err)
|
||||
return i, events, coalescedLogs, err
|
||||
}
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
|
||||
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[insertChain] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
|
||||
return i, events, coalescedLogs, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -1708,9 +1734,13 @@ func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*ty
|
|||
stats.report(chain, i, dirty)
|
||||
if bc.chainConfig.XDPoS != nil {
|
||||
// epoch block
|
||||
if (chain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
isEpochSwithBlock, _, err := engine.IsEpochSwitch(chain[i].Header())
|
||||
if err != nil {
|
||||
log.Error("[insertChain] Error while checking and notifying channel CheckpointCh if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
CheckpointCh <- 1
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1855,8 +1885,15 @@ func (bc *BlockChain) getResultBlock(block *types.Block, verifiedM2 bool) (*Resu
|
|||
bc.reportBlock(block, nil, err)
|
||||
return nil, err
|
||||
}
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(block.NumberU64()/bc.chainConfig.XDPoS.Epoch, tradingState, statedb); err != nil {
|
||||
|
||||
isEpochSwithBlock, epochNumber, err := engine.IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[getResultBlock] Error while checking block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
|
||||
if isEpochSwithBlock {
|
||||
if err := tradingService.UpdateMediumPriceBeforeEpoch(epochNumber, tradingState, statedb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
|
|
@ -2029,9 +2066,13 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L
|
|||
stats.report(types.Blocks{block}, 0, dirty)
|
||||
if bc.chainConfig.XDPoS != nil {
|
||||
// epoch block
|
||||
if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 {
|
||||
isEpochSwithBlock, _, err := bc.Engine().(*XDPoS.XDPoS).IsEpochSwitch(block.Header())
|
||||
if err != nil {
|
||||
log.Error("[insertBlock] Error while checking if the incoming block is epoch switch block", "Hash", block.Hash(), "Number", block.Number())
|
||||
bc.reportBlock(block, nil, err)
|
||||
}
|
||||
if isEpochSwithBlock {
|
||||
CheckpointCh <- 1
|
||||
|
||||
}
|
||||
}
|
||||
// Append a single chain head event if we've progressed the chain
|
||||
|
|
@ -2159,6 +2200,31 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
|||
return fmt.Errorf("Invalid new chain")
|
||||
}
|
||||
}
|
||||
// Ensure XDPoS engine committed block will be not reverted
|
||||
if xdpos, ok := bc.Engine().(*XDPoS.XDPoS); ok {
|
||||
latestCommittedBlock := xdpos.EngineV2.GetLatestCommittedBlockInfo()
|
||||
if latestCommittedBlock != nil {
|
||||
currentBlock := bc.CurrentBlock()
|
||||
currentBlock.Number().Cmp(latestCommittedBlock.Number)
|
||||
cmp := commonBlock.Number().Cmp(latestCommittedBlock.Number)
|
||||
if cmp < 0 {
|
||||
for _, oldBlock := range oldChain {
|
||||
if oldBlock.Number().Cmp(latestCommittedBlock.Number) == 0 {
|
||||
if oldBlock.Hash() != latestCommittedBlock.Hash {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "committed hash", latestCommittedBlock.Hash)
|
||||
} else {
|
||||
log.Warn("Stop reorg, blockchain is under forking attack", "old committed num", oldBlock.Number(), "old committed hash", oldBlock.Hash())
|
||||
return fmt.Errorf("stop reorg, blockchain is under forking attack. old committed num %d, hash %x", oldBlock.Number(), oldBlock.Hash())
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if cmp == 0 {
|
||||
if commonBlock.Hash() != latestCommittedBlock.Hash {
|
||||
log.Error("Impossible reorg, please file an issue", "oldnum", commonBlock.Number(), "oldhash", commonBlock.Hash(), "committed hash", latestCommittedBlock.Hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure the user sees large reorgs
|
||||
if len(oldChain) > 0 && len(newChain) > 0 {
|
||||
logFn := log.Warn
|
||||
|
|
@ -2184,7 +2250,7 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
|
|||
if bc.chainConfig.XDPoS != nil && ((newChain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap)) {
|
||||
err := bc.UpdateM1()
|
||||
if err != nil {
|
||||
log.Crit("Error when update masternodes set. Stopping node", "err", err)
|
||||
log.Crit("Error when update masternodes set. Stopping node", "err", err, "blockNumber", newChain[i].NumberU64())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2273,6 +2339,15 @@ func (bc *BlockChain) addBadBlock(block *types.Block) {
|
|||
func (bc *BlockChain) reportBlock(block *types.Block, receipts types.Receipts, err error) {
|
||||
bc.addBadBlock(block)
|
||||
|
||||
// V2 specific logs
|
||||
config, _ := json.Marshal(bc.chainConfig)
|
||||
|
||||
var roundNumber = types.Round(0)
|
||||
engine, ok := bc.Engine().(*XDPoS.XDPoS)
|
||||
if ok {
|
||||
roundNumber, err = engine.EngineV2.GetRoundNumber(block.Header())
|
||||
}
|
||||
|
||||
var receiptString string
|
||||
for _, receipt := range receipts {
|
||||
receiptString += fmt.Sprintf("\t%v\n", receipt)
|
||||
|
|
@ -2285,9 +2360,10 @@ Number: %v
|
|||
Hash: 0x%x
|
||||
%v
|
||||
|
||||
Round: %v
|
||||
Error: %v
|
||||
##############################
|
||||
`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err))
|
||||
`, string(config), block.Number(), block.Hash(), receiptString, roundNumber, err))
|
||||
}
|
||||
|
||||
// InsertHeaderChain attempts to insert the given header chain in to the local
|
||||
|
|
@ -2390,6 +2466,11 @@ func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header {
|
|||
return bc.hc.GetHeaderByNumber(number)
|
||||
}
|
||||
|
||||
// Set config for testing purpose function
|
||||
func (bc *BlockChain) SetConfig(config *params.ChainConfig) {
|
||||
bc.chainConfig = config
|
||||
}
|
||||
|
||||
// Config retrieves the blockchain's chain configuration.
|
||||
func (bc *BlockChain) Config() *params.ChainConfig { return bc.chainConfig }
|
||||
|
||||
|
|
@ -2459,16 +2540,12 @@ func (bc *BlockChain) UpdateM1() error {
|
|||
// if can't get anything, request from contracts
|
||||
stateDB, err := bc.State()
|
||||
if err != nil {
|
||||
|
||||
candidates, err = validator.GetCandidates(opts)
|
||||
if err != nil {
|
||||
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
candidates = state.GetCandidates(stateDB)
|
||||
|
||||
}
|
||||
|
||||
var ms []utils.Masternode
|
||||
|
|
@ -2498,20 +2575,7 @@ func (bc *BlockChain) UpdateM1() error {
|
|||
log.Info("Updating new set of masternodes")
|
||||
// get block header
|
||||
header := bc.CurrentHeader()
|
||||
var maxMasternodes int
|
||||
// check if block number is increase ms checkpoint
|
||||
if bc.chainConfig.IsTIPIncreaseMasternodes(header.Number) {
|
||||
// using new masterndoes
|
||||
maxMasternodes = common.MaxMasternodesV2
|
||||
} else {
|
||||
// using old masterndoes
|
||||
maxMasternodes = common.MaxMasternodes
|
||||
}
|
||||
if len(ms) > maxMasternodes {
|
||||
err = engine.UpdateMasternodes(bc, bc.CurrentHeader(), ms[:maxMasternodes])
|
||||
} else {
|
||||
err = engine.UpdateMasternodes(bc, bc.CurrentHeader(), ms)
|
||||
}
|
||||
err = engine.UpdateMasternodes(bc, header, ms)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -188,10 +188,11 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
|||
}
|
||||
return newcfg, stored, err
|
||||
}
|
||||
// Special case: don't change the existing config of a non-mainnet chain if no new
|
||||
|
||||
// Special case: don't change the existing config of a non-xinfin chain if no new
|
||||
// config is supplied. These chains would get AllProtocolChanges (and a compat error)
|
||||
// if we just continued here.
|
||||
if genesis == nil && stored != params.MainnetGenesisHash {
|
||||
if genesis == nil && newcfg == params.AllEthashProtocolChanges {
|
||||
return storedcfg, stored, nil
|
||||
}
|
||||
|
||||
|
|
@ -211,12 +212,19 @@ func SetupGenesisBlock(db ethdb.Database, genesis *Genesis) (*params.ChainConfig
|
|||
func (g *Genesis) configOrDefault(ghash common.Hash) *params.ChainConfig {
|
||||
switch {
|
||||
case g != nil:
|
||||
log.Info("[configOrDefault] load orignal config", "hash", ghash)
|
||||
return g.Config
|
||||
case ghash == params.MainnetGenesisHash:
|
||||
case ghash == params.XDCMainnetGenesisHash:
|
||||
log.Info("[configOrDefault] load mainnetconfig")
|
||||
return params.XDCMainnetChainConfig
|
||||
case ghash == params.TestnetGenesisHash:
|
||||
log.Info("[configOrDefault] load TestnetChainConfig")
|
||||
return params.TestnetChainConfig
|
||||
case ghash == params.DevnetGenesisHash:
|
||||
log.Info("[configOrDefault] load DevnetChainConfig")
|
||||
return params.DevnetChainConfig
|
||||
default:
|
||||
log.Info("[configOrDefault] load AllEthashProtocolChanges", "hash", ghash)
|
||||
return params.AllEthashProtocolChanges
|
||||
}
|
||||
}
|
||||
|
|
@ -312,26 +320,30 @@ func GenesisBlockForTesting(db ethdb.Database, addr common.Address, balance *big
|
|||
|
||||
// DefaultGenesisBlock returns the Ethereum main net genesis block.
|
||||
func DefaultGenesisBlock() *Genesis {
|
||||
config := params.XDCMainnetChainConfig
|
||||
return &Genesis{
|
||||
Config: params.XDCMainnetChainConfig,
|
||||
Config: config,
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000001b82c4bf317fcafe3d77e8b444c82715d216afe845b7bd987fa22c9bac89b71f0ded03f6e150ba31ad670b2b166684657ffff95f4810380ae7381e9bce41231d5dd8cdd7499e418b648c00af75d184a2f9aba09a6fa4a46fb1a6a3919b027d9cac5aa6890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
ExtraData: hexutil.MustDecode("0x000000000000000000000000000000000000000000000000000000000000000025c65b4b379ac37cf78357c4915f73677022eaffc7d49d0a2cf198deebd6ce581af465944ec8b2bbcfccdea1006a5cfa7d9484b5b293b46964c265c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 4700000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Alloc: DecodeMainnet(),
|
||||
Timestamp: 1544771829,
|
||||
Alloc: DecodeAllocJson(XDCAllocData),
|
||||
Timestamp: 1559211559,
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultTestnetGenesisBlock returns the Ropsten network genesis block.
|
||||
func DefaultTestnetGenesisBlock() *Genesis {
|
||||
config := params.TestnetChainConfig
|
||||
config.XDPoS.V2 = nil
|
||||
return &Genesis{
|
||||
Config: params.TestnetChainConfig,
|
||||
Nonce: 66,
|
||||
ExtraData: hexutil.MustDecode("0x3535353535353535353535353535353535353535353535353535353535353535"),
|
||||
GasLimit: 16777216,
|
||||
Difficulty: big.NewInt(1048576),
|
||||
Alloc: decodePrealloc(testnetAllocData),
|
||||
Nonce: 0,
|
||||
ExtraData: hexutil.MustDecode("0x00000000000000000000000000000000000000000000000000000000000000003ea0a3555f9b1de983572bff6444aeb1899ec58c4f7900282f3d371d585ab1361205b0940ab1789c942a5885a8844ee5587c8ac5e371fc39ffe618960000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
|
||||
GasLimit: 4700000,
|
||||
Difficulty: big.NewInt(1),
|
||||
Alloc: DecodeAllocJson(XDCTestAllocData),
|
||||
Timestamp: 1560417871,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,8 +398,8 @@ func decodePrealloc(data string) GenesisAlloc {
|
|||
return ga
|
||||
}
|
||||
|
||||
func DecodeMainnet() GenesisAlloc {
|
||||
mainnetAlloc := GenesisAlloc{}
|
||||
json.Unmarshal([]byte(XDCAllocData), &mainnetAlloc)
|
||||
return mainnetAlloc
|
||||
func DecodeAllocJson(s string) GenesisAlloc {
|
||||
alloc := GenesisAlloc{}
|
||||
json.Unmarshal([]byte(s), &alloc)
|
||||
return alloc
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -17,14 +17,15 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/vm"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
|
|
|
|||
137
core/types/consensus_v2.go
Normal file
137
core/types/consensus_v2.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
// Round number type in XDPoS 2.0
|
||||
type Round uint64
|
||||
type Signature []byte
|
||||
|
||||
// Block Info struct in XDPoS 2.0, used for vote message, etc.
|
||||
type BlockInfo struct {
|
||||
Hash common.Hash
|
||||
Round Round
|
||||
Number *big.Int
|
||||
}
|
||||
|
||||
// Vote message in XDPoS 2.0
|
||||
type Vote struct {
|
||||
signer common.Address
|
||||
ProposedBlockInfo *BlockInfo
|
||||
Signature Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func (v *Vote) Hash() common.Hash {
|
||||
return rlpHash(v)
|
||||
}
|
||||
|
||||
func (v *Vote) PoolKey() string {
|
||||
// return the voted block hash
|
||||
return fmt.Sprint(v.ProposedBlockInfo.Round, ":", v.GapNumber, ":", v.ProposedBlockInfo.Number, ":", v.ProposedBlockInfo.Hash.Hex())
|
||||
}
|
||||
|
||||
func (v *Vote) GetSigner() common.Address {
|
||||
return v.signer
|
||||
}
|
||||
|
||||
func (v *Vote) SetSigner(signer common.Address) {
|
||||
v.signer = signer
|
||||
}
|
||||
|
||||
// Timeout message in XDPoS 2.0
|
||||
type Timeout struct {
|
||||
signer common.Address
|
||||
Round Round
|
||||
Signature Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func (t *Timeout) Hash() common.Hash {
|
||||
return rlpHash(t)
|
||||
}
|
||||
|
||||
func (t *Timeout) PoolKey() string {
|
||||
// timeout pool key is round:gapNumber
|
||||
return fmt.Sprint(t.Round, ":", t.GapNumber)
|
||||
}
|
||||
|
||||
func (t *Timeout) GetSigner() common.Address {
|
||||
return t.signer
|
||||
}
|
||||
|
||||
func (t *Timeout) SetSigner(signer common.Address) {
|
||||
t.signer = signer
|
||||
}
|
||||
|
||||
// BFT Sync Info message in XDPoS 2.0
|
||||
type SyncInfo struct {
|
||||
HighestQuorumCert *QuorumCert
|
||||
HighestTimeoutCert *TimeoutCert
|
||||
}
|
||||
|
||||
func (s *SyncInfo) Hash() common.Hash {
|
||||
return rlpHash(s)
|
||||
}
|
||||
|
||||
// Quorum Certificate struct in XDPoS 2.0
|
||||
type QuorumCert struct {
|
||||
ProposedBlockInfo *BlockInfo
|
||||
Signatures []Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// Timeout Certificate struct in XDPoS 2.0
|
||||
type TimeoutCert struct {
|
||||
Round Round
|
||||
Signatures []Signature
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
// The parsed extra fields in block header in XDPoS 2.0 (excluding the version byte)
|
||||
// The version byte (consensus version) is the first byte in header's extra and it's only valid with value >= 2
|
||||
type ExtraFields_v2 struct {
|
||||
Round Round
|
||||
QuorumCert *QuorumCert
|
||||
}
|
||||
|
||||
// Encode XDPoS 2.0 extra fields into bytes
|
||||
func (e *ExtraFields_v2) EncodeToBytes() ([]byte, error) {
|
||||
bytes, err := rlp.EncodeToBytes(e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versionByte := []byte{2}
|
||||
return append(versionByte, bytes...), nil
|
||||
}
|
||||
|
||||
type EpochSwitchInfo struct {
|
||||
Penalties []common.Address
|
||||
Standbynodes []common.Address
|
||||
Masternodes []common.Address
|
||||
EpochSwitchBlockInfo *BlockInfo
|
||||
EpochSwitchParentBlockInfo *BlockInfo
|
||||
}
|
||||
|
||||
type VoteForSign struct {
|
||||
ProposedBlockInfo *BlockInfo
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func VoteSigHash(m *VoteForSign) common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
|
||||
type TimeoutForSign struct {
|
||||
Round Round
|
||||
GapNumber uint64
|
||||
}
|
||||
|
||||
func TimeoutSigHash(m *TimeoutForSign) common.Hash {
|
||||
return rlpHash(m)
|
||||
}
|
||||
114
core/types/consensus_v2_test.go
Normal file
114
core/types/consensus_v2_test.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Decode extra fields for consensus version >= 2 (XDPoS 2.0 and future versions)
|
||||
func DecodeBytesExtraFields(b []byte, val interface{}) error {
|
||||
if len(b) == 0 {
|
||||
return fmt.Errorf("extra field is 0 length")
|
||||
}
|
||||
switch b[0] {
|
||||
case 1:
|
||||
return fmt.Errorf("consensus version 1 is not applicable for decoding extra fields")
|
||||
case 2:
|
||||
return rlp.DecodeBytes(b[1:], val)
|
||||
default:
|
||||
return fmt.Errorf("consensus version %d is not defined", b[0])
|
||||
}
|
||||
}
|
||||
|
||||
func toyExtraFields() *ExtraFields_v2 {
|
||||
round := Round(307)
|
||||
blockInfo := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(900)}
|
||||
signature := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
signatures := []Signature{signature}
|
||||
quorumCert := &QuorumCert{ProposedBlockInfo: blockInfo, Signatures: signatures, GapNumber: 450}
|
||||
e := &ExtraFields_v2{Round: round, QuorumCert: quorumCert}
|
||||
return e
|
||||
}
|
||||
func TestExtraFieldsEncodeDecode(t *testing.T) {
|
||||
extraFields := toyExtraFields()
|
||||
encoded, err := extraFields.EncodeToBytes()
|
||||
if err != nil {
|
||||
t.Errorf("Error when encoding extra fields")
|
||||
}
|
||||
var decoded ExtraFields_v2
|
||||
err = DecodeBytesExtraFields(encoded, &decoded)
|
||||
if err != nil {
|
||||
t.Errorf("Error when decoding extra fields")
|
||||
}
|
||||
if !reflect.DeepEqual(*extraFields, decoded) {
|
||||
t.Fatalf("Decoded not equal to original extra field, original: %v; decoded: %v", extraFields, decoded)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHashAndSigHash(t *testing.T) {
|
||||
round := Round(307)
|
||||
gapNumer := uint64(450)
|
||||
blockInfo1 := &BlockInfo{Hash: common.BigToHash(big.NewInt(2047)), Round: round - 1, Number: big.NewInt(900)}
|
||||
blockInfo2 := &BlockInfo{Hash: common.BigToHash(big.NewInt(4095)), Round: round - 1, Number: big.NewInt(900)}
|
||||
voteForSign1 := &VoteForSign{ProposedBlockInfo: blockInfo1, GapNumber: gapNumer}
|
||||
voteForSign2 := &VoteForSign{ProposedBlockInfo: blockInfo2, GapNumber: gapNumer}
|
||||
signature1 := []byte{1, 2, 3, 4, 5, 6, 7, 8}
|
||||
signature2 := []byte{1, 2, 3, 4, 5, 6, 7, 7}
|
||||
signatures1 := []Signature{signature1}
|
||||
signatures2 := []Signature{signature2}
|
||||
quorumCert1 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures1, GapNumber: 450}
|
||||
quorumCert2 := &QuorumCert{ProposedBlockInfo: blockInfo1, Signatures: signatures2, GapNumber: 450}
|
||||
vote1 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature1, GapNumber: gapNumer}
|
||||
vote2 := Vote{ProposedBlockInfo: blockInfo1, Signature: signature2, GapNumber: gapNumer}
|
||||
if vote1.Hash() == vote2.Hash() {
|
||||
t.Fatalf("Hash of two votes shouldn't equal")
|
||||
}
|
||||
timeout1 := Timeout{Round: 10, Signature: signature1}
|
||||
timeout2 := Timeout{Round: 10, Signature: signature2}
|
||||
if timeout1.Hash() == timeout2.Hash() {
|
||||
t.Fatalf("Hash of two timeouts shouldn't equal")
|
||||
}
|
||||
syncInfo1 := SyncInfo{HighestQuorumCert: quorumCert1}
|
||||
syncInfo2 := SyncInfo{HighestQuorumCert: quorumCert2}
|
||||
if syncInfo1.Hash() == syncInfo2.Hash() {
|
||||
t.Fatalf("Hash of two sync info shouldn't equal")
|
||||
}
|
||||
if VoteSigHash(voteForSign1) == VoteSigHash(voteForSign2) {
|
||||
t.Fatalf("SigHash of two block info shouldn't equal")
|
||||
}
|
||||
round2 := Round(999)
|
||||
if TimeoutSigHash(&TimeoutForSign{
|
||||
Round: round,
|
||||
GapNumber: 450,
|
||||
}) == TimeoutSigHash(&TimeoutForSign{
|
||||
Round: round2,
|
||||
GapNumber: 450,
|
||||
}) {
|
||||
t.Fatalf("SigHash of two round shouldn't equal")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPoolKeyFormat(t *testing.T) {
|
||||
voteMsg := &Vote{
|
||||
ProposedBlockInfo: &BlockInfo{
|
||||
Hash: common.Hash{1},
|
||||
Round: 5,
|
||||
Number: big.NewInt(4),
|
||||
},
|
||||
Signature: []byte{},
|
||||
GapNumber: 450,
|
||||
}
|
||||
|
||||
voteKey := strings.Split(voteMsg.PoolKey(), ":")
|
||||
assert.Equal(t, "5", voteKey[0])
|
||||
assert.Equal(t, "450", voteKey[1])
|
||||
assert.Equal(t, "4", voteKey[2])
|
||||
assert.Equal(t, common.Hash{1}.String(), voteKey[3])
|
||||
}
|
||||
33
core/types/forensics.go
Normal file
33
core/types/forensics.go
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
package types
|
||||
|
||||
import "github.com/XinFinOrg/XDPoSChain/common"
|
||||
|
||||
type ForensicsInfo struct {
|
||||
HashPath []string `json:"hashPath"`
|
||||
QuorumCert QuorumCert `json:"quorumCert"`
|
||||
SignerAddresses []string `json:"signerAddresses"`
|
||||
}
|
||||
|
||||
type ForensicsContent struct {
|
||||
DivergingBlockNumber uint64 `json:"divergingBlockNumber"`
|
||||
DivergingBlockHash string `json:"divergingBlockHash"`
|
||||
AcrossEpoch bool `json:"acrossEpoch"`
|
||||
SmallerRoundInfo *ForensicsInfo `json:"smallerRoundInfo"`
|
||||
LargerRoundInfo *ForensicsInfo `json:"largerRoundInfo"`
|
||||
}
|
||||
|
||||
type VoteEquivocationContent struct {
|
||||
SmallerRoundVote *Vote `json:"smallerRoundVote"`
|
||||
LargerRoundVote *Vote `json:"largerRoundVote"`
|
||||
Signer common.Address `json:"signer"`
|
||||
}
|
||||
|
||||
type ForensicProof struct {
|
||||
Id string `json:"id"`
|
||||
ForensicsType string `json:"forensicsType"` // QC or VOTE
|
||||
Content string `json:"content"` // Json string of the forensics data
|
||||
}
|
||||
|
||||
type ForensicsEvent struct {
|
||||
ForensicsProof *ForensicProof
|
||||
}
|
||||
|
|
@ -176,7 +176,7 @@ func (c *dataCopy) RequiredGas(input []byte) uint64 {
|
|||
return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas
|
||||
}
|
||||
func (c *dataCopy) Run(in []byte) ([]byte, error) {
|
||||
return in, nil
|
||||
return common.CopyBytes(in), nil
|
||||
}
|
||||
|
||||
// bigModExp implements a native big integer exponential modular operation.
|
||||
|
|
|
|||
31610
coverage.txt
31610
coverage.txt
File diff suppressed because it is too large
Load diff
|
|
@ -12,7 +12,7 @@
|
|||
UMULH R1, R8, c4 \
|
||||
ADCS ZR, c4 \
|
||||
\
|
||||
MUL R2, R5, R25 \
|
||||
MUL R2, R5, R1 \
|
||||
UMULH R2, R5, R26 \
|
||||
MUL R2, R6, R0 \
|
||||
ADDS R0, R26 \
|
||||
|
|
@ -24,13 +24,13 @@
|
|||
ADCS R0, R29 \
|
||||
UMULH R2, R8, c5 \
|
||||
ADCS ZR, c5 \
|
||||
ADDS R25, c1 \
|
||||
ADDS R1, c1 \
|
||||
ADCS R26, c2 \
|
||||
ADCS R27, c3 \
|
||||
ADCS R29, c4 \
|
||||
ADCS ZR, c5 \
|
||||
\
|
||||
MUL R3, R5, R25 \
|
||||
MUL R3, R5, R1 \
|
||||
UMULH R3, R5, R26 \
|
||||
MUL R3, R6, R0 \
|
||||
ADDS R0, R26 \
|
||||
|
|
@ -42,13 +42,13 @@
|
|||
ADCS R0, R29 \
|
||||
UMULH R3, R8, c6 \
|
||||
ADCS ZR, c6 \
|
||||
ADDS R25, c2 \
|
||||
ADDS R1, c2 \
|
||||
ADCS R26, c3 \
|
||||
ADCS R27, c4 \
|
||||
ADCS R29, c5 \
|
||||
ADCS ZR, c6 \
|
||||
\
|
||||
MUL R4, R5, R25 \
|
||||
MUL R4, R5, R1 \
|
||||
UMULH R4, R5, R26 \
|
||||
MUL R4, R6, R0 \
|
||||
ADDS R0, R26 \
|
||||
|
|
@ -60,7 +60,7 @@
|
|||
ADCS R0, R29 \
|
||||
UMULH R4, R8, c7 \
|
||||
ADCS ZR, c7 \
|
||||
ADDS R25, c3 \
|
||||
ADDS R1, c3 \
|
||||
ADCS R26, c4 \
|
||||
ADCS R27, c5 \
|
||||
ADCS R29, c6 \
|
||||
|
|
@ -69,15 +69,15 @@
|
|||
#define gfpReduce() \
|
||||
\ // m = (T * N') mod R, store m in R1:R2:R3:R4
|
||||
MOVD ·np+0(SB), R17 \
|
||||
MOVD ·np+8(SB), R18 \
|
||||
MOVD ·np+8(SB), R25 \
|
||||
MOVD ·np+16(SB), R19 \
|
||||
MOVD ·np+24(SB), R20 \
|
||||
\
|
||||
MUL R9, R17, R1 \
|
||||
UMULH R9, R17, R2 \
|
||||
MUL R9, R18, R0 \
|
||||
MUL R9, R25, R0 \
|
||||
ADDS R0, R2 \
|
||||
UMULH R9, R18, R3 \
|
||||
UMULH R9, R25, R3 \
|
||||
MUL R9, R19, R0 \
|
||||
ADCS R0, R3 \
|
||||
UMULH R9, R19, R4 \
|
||||
|
|
@ -86,9 +86,9 @@
|
|||
\
|
||||
MUL R10, R17, R21 \
|
||||
UMULH R10, R17, R22 \
|
||||
MUL R10, R18, R0 \
|
||||
MUL R10, R25, R0 \
|
||||
ADDS R0, R22 \
|
||||
UMULH R10, R18, R23 \
|
||||
UMULH R10, R25, R23 \
|
||||
MUL R10, R19, R0 \
|
||||
ADCS R0, R23 \
|
||||
ADDS R21, R2 \
|
||||
|
|
@ -97,7 +97,7 @@
|
|||
\
|
||||
MUL R11, R17, R21 \
|
||||
UMULH R11, R17, R22 \
|
||||
MUL R11, R18, R0 \
|
||||
MUL R11, R25, R0 \
|
||||
ADDS R0, R22 \
|
||||
ADDS R21, R3 \
|
||||
ADCS R22, R4 \
|
||||
|
|
@ -107,19 +107,19 @@
|
|||
\
|
||||
\ // m * N
|
||||
loadModulus(R5,R6,R7,R8) \
|
||||
mul(R17,R18,R19,R20,R21,R22,R23,R24) \
|
||||
mul(R17,R25,R19,R20,R21,R22,R23,R24) \
|
||||
\
|
||||
\ // Add the 512-bit intermediate to m*N
|
||||
MOVD ZR, R25 \
|
||||
MOVD ZR, R0 \
|
||||
ADDS R9, R17 \
|
||||
ADCS R10, R18 \
|
||||
ADCS R10, R25 \
|
||||
ADCS R11, R19 \
|
||||
ADCS R12, R20 \
|
||||
ADCS R13, R21 \
|
||||
ADCS R14, R22 \
|
||||
ADCS R15, R23 \
|
||||
ADCS R16, R24 \
|
||||
ADCS ZR, R25 \
|
||||
ADCS ZR, R0 \
|
||||
\
|
||||
\ // Our output is R21:R22:R23:R24. Reduce mod p if necessary.
|
||||
SUBS R5, R21, R10 \
|
||||
|
|
@ -130,4 +130,4 @@
|
|||
CSEL CS, R10, R21, R1 \
|
||||
CSEL CS, R11, R22, R2 \
|
||||
CSEL CS, R12, R23, R3 \
|
||||
CSEL CS, R13, R24, R4
|
||||
CSEL CS, R13, R24, R4
|
||||
|
|
@ -189,6 +189,7 @@ func (api *PrivateMinerAPI) SetGasPrice(gasPrice hexutil.Big) bool {
|
|||
|
||||
// SetEtherbase sets the etherbase of the miner
|
||||
func (api *PrivateMinerAPI) SetEtherbase(etherbase common.Address) bool {
|
||||
log.Info("[PrivateMinerAPI] SetEtherbase", "addr", etherbase)
|
||||
api.e.SetEtherbase(etherbase)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,8 +56,9 @@ import (
|
|||
|
||||
// EthApiBackend implements ethapi.Backend for full nodes
|
||||
type EthApiBackend struct {
|
||||
eth *Ethereum
|
||||
gpo *gasprice.Oracle
|
||||
eth *Ethereum
|
||||
gpo *gasprice.Oracle
|
||||
XDPoS *XDPoS.XDPoS
|
||||
}
|
||||
|
||||
func (b *EthApiBackend) ChainConfig() *params.ChainConfig {
|
||||
|
|
@ -81,6 +82,22 @@ func (b *EthApiBackend) HeaderByNumber(ctx context.Context, blockNr rpc.BlockNum
|
|||
// Otherwise resolve and return the block
|
||||
if blockNr == rpc.LatestBlockNumber {
|
||||
return b.eth.blockchain.CurrentBlock().Header(), nil
|
||||
} else if blockNr == rpc.CommittedBlockNumber {
|
||||
if b.eth.chainConfig.XDPoS == nil {
|
||||
return nil, errors.New("PoW does not support confirmed block lookup")
|
||||
}
|
||||
current := b.eth.blockchain.CurrentBlock().Header()
|
||||
if b.eth.blockchain.Config().XDPoS.BlockConsensusVersion(
|
||||
current.Number,
|
||||
current.Extra,
|
||||
XDPoS.ExtraFieldCheck,
|
||||
) == params.ConsensusEngineVersion2 {
|
||||
// TO CHECK: why calling config in XDPoS is blocked (not field and method)
|
||||
confirmedHash := b.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
|
||||
return b.eth.blockchain.GetHeaderByHash(confirmedHash), nil
|
||||
} else {
|
||||
return nil, errors.New("PoS V1 does not support confirmed block lookup")
|
||||
}
|
||||
}
|
||||
return b.eth.blockchain.GetHeaderByNumber(uint64(blockNr)), nil
|
||||
}
|
||||
|
|
@ -93,6 +110,22 @@ func (b *EthApiBackend) BlockByNumber(ctx context.Context, blockNr rpc.BlockNumb
|
|||
// Otherwise resolve and return the block
|
||||
if blockNr == rpc.LatestBlockNumber {
|
||||
return b.eth.blockchain.CurrentBlock(), nil
|
||||
} else if blockNr == rpc.CommittedBlockNumber {
|
||||
if b.eth.chainConfig.XDPoS == nil {
|
||||
return nil, errors.New("PoW does not support confirmed block lookup")
|
||||
}
|
||||
current := b.eth.blockchain.CurrentBlock().Header()
|
||||
if b.eth.blockchain.Config().XDPoS.BlockConsensusVersion(
|
||||
current.Number,
|
||||
current.Extra,
|
||||
XDPoS.ExtraFieldCheck,
|
||||
) == params.ConsensusEngineVersion2 {
|
||||
// TO CHECK: why calling config in XDPoS is blocked (not field and method)
|
||||
confirmedHash := b.XDPoS.EngineV2.GetLatestCommittedBlockInfo().Hash
|
||||
return b.eth.blockchain.GetBlockByHash(confirmedHash), nil
|
||||
} else {
|
||||
return nil, errors.New("PoS V1 does not support confirmed block lookup")
|
||||
}
|
||||
}
|
||||
return b.eth.blockchain.GetBlockByNumber(uint64(blockNr)), nil
|
||||
}
|
||||
|
|
@ -303,7 +336,17 @@ func (b *EthApiBackend) GetVotersRewards(masternodeAddr common.Address) map[comm
|
|||
number := block.Number().Uint64()
|
||||
engine := b.GetEngine().(*XDPoS.XDPoS)
|
||||
foundationWalletAddr := chain.Config().XDPoS.FoudationWalletAddr
|
||||
lastCheckpointNumber := number - (number % b.ChainConfig().XDPoS.Epoch) - b.ChainConfig().XDPoS.Epoch // calculate for 2 epochs ago
|
||||
|
||||
// calculate for 2 epochs ago
|
||||
currentCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, block.Number())
|
||||
if err != nil {
|
||||
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for current checkpoint block", "block", block)
|
||||
}
|
||||
lastCheckpointNumber, _, err := engine.GetCurrentEpochSwitchBlock(chain, big.NewInt(int64(currentCheckpointNumber-1)))
|
||||
if err != nil {
|
||||
log.Error("[GetVotersRewards] Fail to get GetCurrentEpochSwitchBlock for last checkpoint block", "block", block)
|
||||
}
|
||||
|
||||
lastCheckpointBlock := chain.GetBlockByNumber(lastCheckpointNumber)
|
||||
rCheckpoint := chain.Config().XDPoS.RewardCheckpoint
|
||||
|
||||
|
|
|
|||
|
|
@ -121,6 +121,9 @@ func (api *PrivateDebugAPI) TraceChain(ctx context.Context, start, end rpc.Block
|
|||
if to == nil {
|
||||
return nil, fmt.Errorf("end block #%d not found", end)
|
||||
}
|
||||
if from.Number().Cmp(to.Number()) >= 0 {
|
||||
return nil, fmt.Errorf("end block (#%d) needs to come after start block (#%d)", end, start)
|
||||
}
|
||||
return api.traceChain(ctx, from, to, config)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,11 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine, ctx.GetConfig().AnnounceTxs)
|
||||
eth.miner.SetExtra(makeExtraData(config.ExtraData))
|
||||
|
||||
eth.ApiBackend = &EthApiBackend{eth, nil}
|
||||
if eth.chainConfig.XDPoS != nil {
|
||||
eth.ApiBackend = &EthApiBackend{eth, nil, eth.engine.(*XDPoS.XDPoS)}
|
||||
} else {
|
||||
eth.ApiBackend = &EthApiBackend{eth, nil, nil}
|
||||
}
|
||||
gpoParams := config.GPO
|
||||
if gpoParams.Default == nil {
|
||||
gpoParams.Default = config.GasPrice
|
||||
|
|
@ -267,7 +271,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
return block, false, err
|
||||
}
|
||||
header := block.Header()
|
||||
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, XDPoS.SigHash(header).Bytes())
|
||||
sighash, err := wallet.SignHash(accounts.Account{Address: eb}, c.SigHash(header).Bytes())
|
||||
if err != nil || sighash == nil {
|
||||
log.Error("Can't get signature hash of m2", "sighash", sighash, "err", err)
|
||||
return block, false, err
|
||||
|
|
@ -285,6 +289,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
XDPoS1.0 Specific hooks
|
||||
*/
|
||||
hooks.AttachConsensusV1Hooks(c, eth.blockchain, chainConfig)
|
||||
hooks.AttachConsensusV2Hooks(c, eth.blockchain, chainConfig)
|
||||
|
||||
eth.txPool.IsSigner = func(address common.Address) bool {
|
||||
currentHeader := eth.blockchain.CurrentHeader()
|
||||
|
|
@ -296,7 +301,7 @@ func New(ctx *node.ServiceContext, config *Config, XDCXServ *XDCx.XDCX, lendingS
|
|||
// not genesis block
|
||||
header = parentHeader
|
||||
}
|
||||
return c.IsAuthorisedAddress(header, eth.blockchain, address)
|
||||
return c.IsAuthorisedAddress(eth.blockchain, header, address)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -333,7 +338,7 @@ func CreateDB(ctx *node.ServiceContext, config *Config, name string) (ethdb.Data
|
|||
func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chainConfig *params.ChainConfig, db ethdb.Database) consensus.Engine {
|
||||
// If delegated-proof-of-stake is requested, set it up
|
||||
if chainConfig.XDPoS != nil {
|
||||
return XDPoS.New(chainConfig.XDPoS, db)
|
||||
return XDPoS.New(chainConfig, db)
|
||||
}
|
||||
|
||||
// Otherwise assume proof-of-work
|
||||
|
|
@ -364,7 +369,7 @@ func CreateConsensusEngine(ctx *node.ServiceContext, config *ethash.Config, chai
|
|||
// APIs returns the collection of RPC services the ethereum package offers.
|
||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||
func (s *Ethereum) APIs() []rpc.API {
|
||||
apis := ethapi.GetAPIs(s.ApiBackend)
|
||||
apis := ethapi.GetAPIs(s.ApiBackend, s.BlockChain())
|
||||
|
||||
// Append any APIs exposed explicitly by the consensus engine
|
||||
apis = append(apis, s.engine.APIs(s.BlockChain())...)
|
||||
|
|
@ -464,7 +469,7 @@ func (s *Ethereum) ValidateMasternode() (bool, error) {
|
|||
//check if miner's wallet is in set of validators
|
||||
c := s.engine.(*XDPoS.XDPoS)
|
||||
|
||||
authorized := c.IsAuthorisedAddress(s.blockchain.CurrentHeader(), s.blockchain, eb)
|
||||
authorized := c.IsAuthorisedAddress(s.blockchain, s.blockchain.CurrentHeader(), eb)
|
||||
if !authorized {
|
||||
//This miner doesn't belong to set of validators
|
||||
return false, nil
|
||||
|
|
@ -506,7 +511,7 @@ func (s *Ethereum) StartStaking(local bool) error {
|
|||
if XDPoS, ok := s.engine.(*XDPoS.XDPoS); ok {
|
||||
wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
|
||||
if wallet == nil || err != nil {
|
||||
log.Error("Etherbase account unavailable locally", "err", err)
|
||||
log.Error("Etherbase account unavailable locally", "address", eb, "err", err)
|
||||
return fmt.Errorf("signer missing: %v", err)
|
||||
}
|
||||
XDPoS.Authorize(eb, wallet.SignHash)
|
||||
|
|
|
|||
198
eth/bft/bft_handler.go
Normal file
198
eth/bft/bft_handler.go
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
package bft
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
const maxBlockDist = 7 // Maximum allowed backward distance from the chain head, 7 is just a magic number indicate very close block
|
||||
|
||||
//Define Boradcast Group functions
|
||||
type broadcastVoteFn func(*types.Vote)
|
||||
type broadcastTimeoutFn func(*types.Timeout)
|
||||
type broadcastSyncInfoFn func(*types.SyncInfo)
|
||||
|
||||
// chainHeightFn is a callback type to retrieve the current chain height.
|
||||
type chainHeightFn func() uint64
|
||||
|
||||
type Bfter struct {
|
||||
epoch uint64
|
||||
|
||||
blockChainReader consensus.ChainReader
|
||||
broadcastCh chan interface{}
|
||||
quit chan struct{}
|
||||
consensus ConsensusFns
|
||||
broadcast BroadcastFns
|
||||
chainHeight chainHeightFn // Retrieves the current chain's height
|
||||
}
|
||||
|
||||
type ConsensusFns struct {
|
||||
verifyVote func(consensus.ChainReader, *types.Vote) (bool, error)
|
||||
voteHandler func(consensus.ChainReader, *types.Vote) error
|
||||
|
||||
verifyTimeout func(consensus.ChainReader, *types.Timeout) (bool, error)
|
||||
timeoutHandler func(consensus.ChainReader, *types.Timeout) error
|
||||
|
||||
verifySyncInfo func(consensus.ChainReader, *types.SyncInfo) (bool, error)
|
||||
syncInfoHandler func(consensus.ChainReader, *types.SyncInfo) error
|
||||
}
|
||||
|
||||
type BroadcastFns struct {
|
||||
Vote broadcastVoteFn
|
||||
Timeout broadcastTimeoutFn
|
||||
SyncInfo broadcastSyncInfoFn
|
||||
}
|
||||
|
||||
func New(broadcasts BroadcastFns, blockChainReader *core.BlockChain, chainHeight chainHeightFn) *Bfter {
|
||||
return &Bfter{
|
||||
broadcast: broadcasts,
|
||||
blockChainReader: blockChainReader,
|
||||
chainHeight: chainHeight,
|
||||
|
||||
quit: make(chan struct{}),
|
||||
broadcastCh: make(chan interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Create this function to avoid massive test change
|
||||
func (b *Bfter) InitEpochNumber() {
|
||||
b.epoch = b.blockChainReader.Config().XDPoS.Epoch
|
||||
}
|
||||
|
||||
func (b *Bfter) SetConsensusFuns(engine consensus.Engine) {
|
||||
e := engine.(*XDPoS.XDPoS)
|
||||
b.broadcastCh = e.EngineV2.BroadcastCh
|
||||
b.consensus = ConsensusFns{
|
||||
verifySyncInfo: e.EngineV2.VerifySyncInfoMessage,
|
||||
verifyVote: e.EngineV2.VerifyVoteMessage,
|
||||
verifyTimeout: e.EngineV2.VerifyTimeoutMessage,
|
||||
|
||||
voteHandler: e.EngineV2.VoteHandler,
|
||||
timeoutHandler: e.EngineV2.TimeoutHandler,
|
||||
syncInfoHandler: e.EngineV2.SyncInfoHandler,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bfter) Vote(peer string, vote *types.Vote) error {
|
||||
log.Trace("Receive Vote", "hash", vote.Hash().Hex(), "voted block hash", vote.ProposedBlockInfo.Hash.Hex(), "number", vote.ProposedBlockInfo.Number, "round", vote.ProposedBlockInfo.Round)
|
||||
|
||||
voteBlockNum := vote.ProposedBlockInfo.Number.Int64()
|
||||
if dist := voteBlockNum - int64(b.chainHeight()); dist < -maxBlockDist || dist > maxBlockDist {
|
||||
log.Debug("Discarded propagated vote, too far away", "peer", peer, "number", voteBlockNum, "hash", vote.ProposedBlockInfo.Hash, "distance", dist)
|
||||
return nil
|
||||
}
|
||||
|
||||
verified, err := b.consensus.verifyVote(b.blockChainReader, vote)
|
||||
|
||||
if err != nil {
|
||||
log.Error("Verify BFT Vote", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- vote
|
||||
|
||||
if verified {
|
||||
err = b.consensus.voteHandler(b.blockChainReader, vote)
|
||||
if err != nil {
|
||||
if _, ok := err.(*utils.ErrIncomingMessageRoundTooFarFromCurrentRound); ok {
|
||||
log.Debug("vote round not equal", "error", err, "vote", vote.Hash())
|
||||
return err
|
||||
}
|
||||
log.Error("handle BFT Vote", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (b *Bfter) Timeout(peer string, timeout *types.Timeout) error {
|
||||
log.Debug("Receive Timeout", "timeout", timeout)
|
||||
|
||||
gapNum := timeout.GapNumber
|
||||
|
||||
// dist times 3, ex: timeout message's gap number is based on block and find out it's epoch switch number, then mod 900 then minus 450
|
||||
if dist := int64(gapNum) - int64(b.chainHeight()); dist < -int64(b.epoch)*3 || dist > int64(b.epoch)*3 {
|
||||
log.Debug("Discarded propagated timeout, too far away", "peer", peer, "gapNumber", gapNum, "hash", timeout.Hash, "distance", dist)
|
||||
return nil
|
||||
}
|
||||
|
||||
verified, err := b.consensus.verifyTimeout(b.blockChainReader, timeout)
|
||||
if err != nil {
|
||||
log.Error("Verify BFT Timeout", "timeoutRound", timeout.Round, "timeoutGapNum", gapNum, "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- timeout
|
||||
if verified {
|
||||
err = b.consensus.timeoutHandler(b.blockChainReader, timeout)
|
||||
if err != nil {
|
||||
if _, ok := err.(*utils.ErrIncomingMessageRoundNotEqualCurrentRound); ok {
|
||||
log.Debug("timeout round not equal", "error", err)
|
||||
return err
|
||||
}
|
||||
log.Error("handle BFT Timeout", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (b *Bfter) SyncInfo(peer string, syncInfo *types.SyncInfo) error {
|
||||
log.Debug("Receive SyncInfo", "syncInfo", syncInfo)
|
||||
|
||||
qcBlockNum := syncInfo.HighestQuorumCert.ProposedBlockInfo.Number.Int64()
|
||||
if dist := qcBlockNum - int64(b.chainHeight()); dist < -maxBlockDist || dist > maxBlockDist {
|
||||
log.Debug("Discarded propagated syncInfo, too far away", "peer", peer, "blockNum", qcBlockNum, "hash", syncInfo.Hash, "distance", dist)
|
||||
return nil
|
||||
}
|
||||
|
||||
verified, err := b.consensus.verifySyncInfo(b.blockChainReader, syncInfo)
|
||||
if err != nil {
|
||||
log.Error("Verify BFT SyncInfo", "error", err)
|
||||
return err
|
||||
}
|
||||
|
||||
b.broadcastCh <- syncInfo
|
||||
// Process only if verified and qualified
|
||||
if verified {
|
||||
err = b.consensus.syncInfoHandler(b.blockChainReader, syncInfo)
|
||||
if err != nil {
|
||||
log.Error("handle BFT SyncInfo", "error", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start Bft receiver
|
||||
func (b *Bfter) Start() {
|
||||
go b.loop()
|
||||
}
|
||||
func (b *Bfter) Stop() {
|
||||
close(b.quit)
|
||||
}
|
||||
func (b *Bfter) loop() {
|
||||
log.Info("BFT Loop Start")
|
||||
for {
|
||||
select {
|
||||
case <-b.quit:
|
||||
log.Warn("BFT Loop Close")
|
||||
return
|
||||
case obj := <-b.broadcastCh:
|
||||
switch v := obj.(type) {
|
||||
case *types.Vote:
|
||||
go b.broadcast.Vote(v)
|
||||
case *types.Timeout:
|
||||
go b.broadcast.Timeout(v)
|
||||
case *types.SyncInfo:
|
||||
go b.broadcast.SyncInfo(v)
|
||||
default:
|
||||
log.Error("Unknown message type received", "value", v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
395
eth/bft/bft_handler_test.go
Normal file
395
eth/bft/bft_handler_test.go
Normal file
|
|
@ -0,0 +1,395 @@
|
|||
package bft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/engines/engine_v2"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
|
||||
"github.com/XinFinOrg/XDPoSChain/core"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const peerID = "abc"
|
||||
|
||||
// make different votes based on Signatures
|
||||
func makeVotes(n int) []types.Vote {
|
||||
var votes []types.Vote
|
||||
for i := 0; i < n; i++ {
|
||||
votes = append(votes, types.Vote{
|
||||
ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(1350)},
|
||||
Signature: []byte{byte(i)},
|
||||
GapNumber: 450,
|
||||
})
|
||||
}
|
||||
return votes
|
||||
}
|
||||
|
||||
// bfterTester is a test simulator for mocking out bfter worker.
|
||||
type bfterTester struct {
|
||||
bfter *Bfter
|
||||
}
|
||||
|
||||
// newTester creates a new bft fetcher test mocker.
|
||||
func newTester() *bfterTester {
|
||||
testConsensus := &XDPoS.XDPoS{EngineV2: &engine_v2.XDPoS_v2{}}
|
||||
broadcasts := BroadcastFns{}
|
||||
blockChain := &core.BlockChain{}
|
||||
blockChain.SetConfig(params.TestXDPoSMockChainConfig)
|
||||
chainHeight := func() uint64 {
|
||||
return 1351
|
||||
}
|
||||
|
||||
tester := &bfterTester{}
|
||||
tester.bfter = New(broadcasts, blockChain, chainHeight)
|
||||
tester.bfter.InitEpochNumber()
|
||||
tester.bfter.SetConsensusFuns(testConsensus)
|
||||
tester.bfter.broadcastCh = make(chan interface{})
|
||||
tester.bfter.Start()
|
||||
|
||||
return tester
|
||||
}
|
||||
|
||||
// Tests that a bfter accepts vote and process verfiy and broadcast
|
||||
func TestSequentialVotes(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 10
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
votes := makeVotes(targetVotes)
|
||||
for _, vote := range votes {
|
||||
err := tester.bfter.Vote(peerID, &vote)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that avoid boardcast if there is bad vote
|
||||
func TestNotBoardcastInvalidVote(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 0
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
return false, fmt.Errorf("This is invalid vote")
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
vote := types.Vote{ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(1)}}
|
||||
tester.bfter.Vote(peerID, &vote)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedVotes(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 0
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
vote := types.Vote{ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(1350)}}
|
||||
tester.bfter.Vote(peerID, &vote)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetVotes || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedTimeout(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetTimeout := 0
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(chain consensus.ChainReader, timeout *types.Timeout) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
timeout := types.Timeout{GapNumber: 450}
|
||||
tester.bfter.Timeout(peerID, &timeout)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetTimeout || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoardcastButNotProcessDisqualifiedSyncInfo(t *testing.T) {
|
||||
tester := newTester()
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetSyncInfo := 0
|
||||
|
||||
tester.bfter.consensus.verifySyncInfo = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) (bool, error) {
|
||||
return false, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.syncInfoHandler = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.SyncInfo = func(*types.SyncInfo) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
syncInfo := types.SyncInfo{HighestQuorumCert: &types.QuorumCert{ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(1350)}}}
|
||||
tester.bfter.SyncInfo(peerID, &syncInfo)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on handler, %v on broadcast, want %v", handlerCounter, broadcastCounter, targetSyncInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutHandler(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetVotes := 1
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *types.Timeout) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
timeoutMsg := &types.Timeout{GapNumber: 450}
|
||||
|
||||
err := tester.bfter.Timeout(peerID, timeoutMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimeoutHandlerRoundNotEqual(t *testing.T) {
|
||||
tester := newTester()
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *types.Timeout) (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
return &utils.ErrIncomingMessageRoundNotEqualCurrentRound{
|
||||
Type: "timeout",
|
||||
IncomingRound: types.Round(1),
|
||||
CurrentRound: types.Round(2),
|
||||
}
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {}
|
||||
|
||||
timeoutMsg := &types.Timeout{}
|
||||
|
||||
err := tester.bfter.Timeout(peerID, timeoutMsg)
|
||||
assert.Equal(t, "timeout message round number: 1 does not match currentRound: 2", err.Error())
|
||||
}
|
||||
|
||||
func TestSyncInfoHandler(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetSyncInfo := 1
|
||||
|
||||
tester.bfter.consensus.verifySyncInfo = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.syncInfoHandler = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.SyncInfo = func(*types.SyncInfo) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
syncInfo := types.SyncInfo{HighestQuorumCert: &types.QuorumCert{ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(1350)}}}
|
||||
tester.bfter.SyncInfo(peerID, &syncInfo)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(verifyCounter) != targetSyncInfo || int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != 1 {
|
||||
t.Fatalf("count mismatch: have %v on verify, have %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetSyncInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTooFarVotes(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
numberVotes := 10
|
||||
targetVotes := 0
|
||||
|
||||
tester.bfter.consensus.verifyVote = func(chain consensus.ChainReader, vote *types.Vote) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.voteHandler = func(chain consensus.ChainReader, vote *types.Vote) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Vote = func(*types.Vote) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
tester.bfter.chainHeight = func() uint64 { return 10000 }
|
||||
|
||||
votes := makeVotes(numberVotes)
|
||||
for _, vote := range votes {
|
||||
err := tester.bfter.Vote(peerID, &vote)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
if int(verifyCounter) != targetVotes || int(handlerCounter) != targetVotes || int(broadcastCounter) != targetVotes {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetVotes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTooFarTimeout(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetTimeout := 1
|
||||
|
||||
tester.bfter.consensus.verifyTimeout = func(consensus.ChainReader, *types.Timeout) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
tester.bfter.consensus.timeoutHandler = func(chain consensus.ChainReader, timeout *types.Timeout) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
|
||||
tester.bfter.broadcast.Timeout = func(*types.Timeout) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
tester.bfter.chainHeight = func() uint64 { return 7175258 }
|
||||
|
||||
timeoutMsg := &types.Timeout{GapNumber: 7173450}
|
||||
|
||||
err := tester.bfter.Timeout(peerID, timeoutMsg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if int(verifyCounter) != targetTimeout || int(handlerCounter) != targetTimeout || int(broadcastCounter) != targetTimeout {
|
||||
t.Fatalf("count mismatch: have %v on verify, %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTooFarSyncInfo(t *testing.T) {
|
||||
tester := newTester()
|
||||
verifyCounter := uint32(0)
|
||||
handlerCounter := uint32(0)
|
||||
broadcastCounter := uint32(0)
|
||||
targetSyncInfo := 0
|
||||
|
||||
tester.bfter.consensus.verifySyncInfo = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) (bool, error) {
|
||||
atomic.AddUint32(&verifyCounter, 1)
|
||||
return true, nil // return false but with nil in error means the message is valid but disqualified
|
||||
}
|
||||
|
||||
tester.bfter.consensus.syncInfoHandler = func(chain consensus.ChainReader, syncInfo *types.SyncInfo) error {
|
||||
atomic.AddUint32(&handlerCounter, 1)
|
||||
return nil
|
||||
}
|
||||
tester.bfter.broadcast.SyncInfo = func(*types.SyncInfo) {
|
||||
atomic.AddUint32(&broadcastCounter, 1)
|
||||
}
|
||||
|
||||
syncInfo := types.SyncInfo{HighestQuorumCert: &types.QuorumCert{ProposedBlockInfo: &types.BlockInfo{Number: big.NewInt(100)}}}
|
||||
tester.bfter.SyncInfo(peerID, &syncInfo)
|
||||
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if int(verifyCounter) != targetSyncInfo || int(handlerCounter) != targetSyncInfo || int(broadcastCounter) != targetSyncInfo {
|
||||
t.Fatalf("count mismatch: have %v on verify, have %v on handler, %v on broadcast, want %v", verifyCounter, handlerCounter, broadcastCounter, targetSyncInfo)
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,9 @@ import (
|
|||
"github.com/XinFinOrg/XDPoSChain/params"
|
||||
)
|
||||
|
||||
// proposeBlockHandlerFn is a callback type to handle a block by the consensus
|
||||
type proposeBlockHandlerFn func(header *types.Header) error
|
||||
|
||||
var (
|
||||
MaxHashFetch = 512 // Amount of hashes to be fetched per retrieval request
|
||||
MaxBlockFetch = 128 // Amount of blocks to be fetched per retrieval request
|
||||
|
|
@ -114,7 +117,8 @@ type Downloader struct {
|
|||
blockchain BlockChain
|
||||
|
||||
// Callbacks
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
dropPeer peerDropFn // Drops a peer for misbehaving
|
||||
handleProposedBlock proposeBlockHandlerFn // Consensus v2 specific: Hanle new proposed block
|
||||
|
||||
// Status
|
||||
synchroniseMock func(id string, hash common.Hash) error // Replacement for synchronise during testing
|
||||
|
|
@ -199,31 +203,32 @@ type BlockChain interface {
|
|||
}
|
||||
|
||||
// New creates a new downloader to fetch hashes and blocks from remote peers.
|
||||
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn) *Downloader {
|
||||
func New(mode SyncMode, stateDb ethdb.Database, mux *event.TypeMux, chain BlockChain, lightchain LightChain, dropPeer peerDropFn, handleProposedBlock proposeBlockHandlerFn) *Downloader {
|
||||
if lightchain == nil {
|
||||
lightchain = chain
|
||||
}
|
||||
|
||||
dl := &Downloader{
|
||||
mode: mode,
|
||||
stateDB: stateDb,
|
||||
mux: mux,
|
||||
queue: newQueue(),
|
||||
peers: newPeerSet(),
|
||||
rttEstimate: uint64(rttMaxEstimate),
|
||||
rttConfidence: uint64(1000000),
|
||||
blockchain: chain,
|
||||
lightchain: lightchain,
|
||||
dropPeer: dropPeer,
|
||||
headerCh: make(chan dataPack, 1),
|
||||
bodyCh: make(chan dataPack, 1),
|
||||
receiptCh: make(chan dataPack, 1),
|
||||
bodyWakeCh: make(chan bool, 1),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
headerProcCh: make(chan []*types.Header, 1),
|
||||
quitCh: make(chan struct{}),
|
||||
stateCh: make(chan dataPack),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
mode: mode,
|
||||
stateDB: stateDb,
|
||||
mux: mux,
|
||||
queue: newQueue(),
|
||||
peers: newPeerSet(),
|
||||
rttEstimate: uint64(rttMaxEstimate),
|
||||
rttConfidence: uint64(1000000),
|
||||
blockchain: chain,
|
||||
lightchain: lightchain,
|
||||
dropPeer: dropPeer,
|
||||
handleProposedBlock: handleProposedBlock,
|
||||
headerCh: make(chan dataPack, 1),
|
||||
bodyCh: make(chan dataPack, 1),
|
||||
receiptCh: make(chan dataPack, 1),
|
||||
bodyWakeCh: make(chan bool, 1),
|
||||
receiptWakeCh: make(chan bool, 1),
|
||||
headerProcCh: make(chan []*types.Header, 1),
|
||||
quitCh: make(chan struct{}),
|
||||
stateCh: make(chan dataPack),
|
||||
stateSyncStart: make(chan *stateSync),
|
||||
syncStatsState: stateSyncStats{
|
||||
processed: core.GetTrieSyncProgress(stateDb),
|
||||
},
|
||||
|
|
@ -1393,7 +1398,13 @@ func (d *Downloader) importBlockResults(results []*fetchResult) error {
|
|||
log.Debug("Downloaded item processing failed", "number", results[index].Header.Number, "hash", results[index].Header.Hash(), "err", err)
|
||||
return errInvalidChain
|
||||
}
|
||||
|
||||
if d.handleProposedBlock != nil {
|
||||
header := blocks[len(blocks)-1].Header()
|
||||
err := d.handleProposedBlock(header)
|
||||
if err != nil {
|
||||
log.Info("[downloader] handle proposed block has error", "err", err, "block hash", header.Hash(), "number", header.Number)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue