Merge pull request #295 from XinFinOrg/dev-upgrade

consensus version 2 and cicd pipeline
This commit is contained in:
Liam 2023-08-07 18:09:26 +10:00 committed by GitHub
commit ef5d064553
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
136 changed files with 13180 additions and 32594 deletions

2
.gitignore vendored
View file

@ -51,4 +51,4 @@ profile.cov
coverage.txt
go.sum
cicd/devnet/terraform/.terraform
cicd/devnet/tmp
cicd/devnet/tmp

View file

@ -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

View file

@ -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."

View file

@ -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.

View file

@ -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
View 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
View 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"]

View 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

File diff suppressed because one or more lines are too long

View 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
View 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

View 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

View 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"
}
]
}
]

View 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
}

View 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
}
}

View 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"
}
]
}
]

View 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}"
}
}

View 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}"
}
}

View 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}"
}
}

View 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
}

View 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"
}

View 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"
}

View file

@ -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, "")

View file

@ -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 })

View file

@ -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(&ethereum); 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 {

View file

@ -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)
}

View file

@ -116,15 +116,40 @@ func (w *wizard) makeGenesis() {
Period: 15,
Epoch: 30000,
Reward: 0,
V2: &params.V2{
SwitchBlock: big.NewInt(0),
CurrentConfig: &params.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()

View file

@ -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)
}
}
}

View file

@ -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)

View 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`

View 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,
}

View 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
}

View 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
}

View file

@ -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.

View file

@ -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")
}
}

View file

@ -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 = &params.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
*/

View file

@ -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)

View file

@ -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,
}
}
}

View 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)
}

View file

@ -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
}

View 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
}

View 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 := &params.ChainConfig{
XDPoS: &params.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)
}
}
}

View 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

View 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
}

View 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
}

View 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

View 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
}

View file

@ -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
}

View 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)
}
}

View 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
}

View 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()
}

View 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
}
}

View 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
}

View 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()
}

View file

@ -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
)

View file

@ -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)
}

View 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
}

View 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)
}

View file

@ -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
}

View file

@ -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 := &params.ChainConfig{
XDPoS: &params.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"),

View file

@ -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")
)

View 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)
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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)
}

View 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())
}

View 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)
}

View 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"))
}

View 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()
}
}
}

View 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
}

View 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)
}

View 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)
}

View 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))
}

View 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)
}
}

View 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")]))
}
}
}

View 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)
}

View 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")
}
}
}

View 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)
}

View 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")
}
}
}
}

View 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")
}
}
}

View file

@ -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
}

View file

@ -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"

View file

@ -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
}

View file

@ -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

View file

@ -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
View 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)
}

View 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
View 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
}

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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)
}

View file

@ -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
View 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
View 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)
}
}

View file

@ -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