Merge pull request #70 from maticnetwork/develop

Merge develop (Apr 20 - May 27) to master
This commit is contained in:
Jaynti Kanani 2020-05-27 11:14:45 +05:30 committed by GitHub
commit 555e67a551
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1255 additions and 588 deletions

View file

@ -3,7 +3,7 @@ name: Linux package
on:
push:
tags:
- 'v*.*.*'
- "v*.*.*"
jobs:
build:
runs-on: ubuntu-latest
@ -30,7 +30,7 @@ jobs:
sudo apt-get -yqq install libpq-dev build-essential
gem install --no-document fpm
fpm --version
make bor
cat > bor.service <<- "EOF"
@ -39,8 +39,7 @@ jobs:
[Service]
WorkingDirectory=/etc/bor/
EnvironmentFile=/etc/bor/metadata
ExecStartPre=/bin/mkdir -p /var/log/matic-logs/
ExecStart=/bin/bash -c "/usr/bin/bor --datadir /etc/bor/dataDir --port '30303' --rpc --rpcaddr '0.0.0.0' --rpcvhosts '*' --rpccorsdomain '*' --rpcport '8545' --ipcpath /etc/bor/geth.ipc --rpcapi 'bor,db,eth,net,web3,txpool' --networkid ${NETWORK_ID} --gasprice '0' --keystore /etc/bor/dataDir/keystore --unlock ${VALIDATOR_ADDRESS} --password /etc/bor/dataDir/password.txt --allow-insecure-unlock --maxpeers 150 --mine > /var/log/matic-logs/bor.log 2>&1"
ExecStart=/bin/bash -c "/usr/bin/bor --datadir /etc/bor/dataDir --port '30303' --rpc --rpcaddr '0.0.0.0' --rpcvhosts '*' --rpccorsdomain '*' --rpcport '8545' --ipcpath /etc/bor/bor.ipc --rpcapi 'db,eth,net,web3,txpool,bor' --networkid ${NETWORK_ID} --miner.gaslimit '20000000' --txpool.nolocals --txpool.accountslots '128' --txpool.globalslots '20000' --txpool.lifetime '0h16m0s' --keystore /etc/bor/dataDir/keystore --unlock ${VALIDATOR_ADDRESS} --password /etc/bor/dataDir/password.txt --allow-insecure-unlock --maxpeers 150 --mine"
Type=simple
User=root
EOF
@ -60,7 +59,7 @@ jobs:
bor.service=/etc/systemd/system/ \
build/bin/bor=/usr/bin/ \
metadata=/etc/bor/
mkdir packages-v${{ env.RELEASE_VERSION }}
mv matic-bor_${{ env.RELEASE_VERSION }}_amd64.deb packages-v${{ env.RELEASE_VERSION }}/
@ -75,14 +74,14 @@ jobs:
AWS_S3_BUCKET: ${{ secrets.AWS_S3_BUCKET }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
AWS_REGION: 'us-east-1' # optional: defaults to us-east-1
SOURCE_DIR: 'packages-v${{ env.RELEASE_VERSION }}'
DEST_DIR: 'v${{ env.RELEASE_VERSION }}'
AWS_REGION: "us-east-1" # optional: defaults to us-east-1
SOURCE_DIR: "packages-v${{ env.RELEASE_VERSION }}"
DEST_DIR: "v${{ env.RELEASE_VERSION }}"
- name: Slack Notification
uses: rtCamp/action-slack-notify@v2.0.0
env:
SLACK_CHANNEL: code-releases
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_TITLE: 'New linux package for Bor v${{ env.RELEASE_VERSION }} just got released'
SLACK_MESSAGE: 'Package has been uploaded to S3 bucket for public use and available at https://matic-public.s3.amazonaws.com/v${{ env.RELEASE_VERSION }}/matic-bor_${{ env.RELEASE_VERSION }}_amd64.deb'
SLACK_TITLE: "New linux package for Bor v${{ env.RELEASE_VERSION }} just got released"
SLACK_MESSAGE: "Package has been uploaded to S3 bucket for public use and available at https://matic-public.s3.amazonaws.com/v${{ env.RELEASE_VERSION }}/matic-bor_${{ env.RELEASE_VERSION }}_amd64.deb"

View file

@ -41,11 +41,11 @@ import (
"sync"
"time"
pcsc "github.com/gballet/go-libpcsclite"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/event"
"github.com/maticnetwork/bor/log"
pcsc "github.com/gballet/go-libpcsclite"
)
// Scheme is the URI prefix for smartcard wallets.

View file

@ -25,8 +25,8 @@ import (
"crypto/sha512"
"fmt"
"github.com/maticnetwork/bor/crypto"
pcsc "github.com/gballet/go-libpcsclite"
"github.com/maticnetwork/bor/crypto"
"github.com/wsddn/go-ecdh"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/text/unicode/norm"

View file

@ -33,13 +33,13 @@ import (
"sync"
"time"
pcsc "github.com/gballet/go-libpcsclite"
ethereum "github.com/maticnetwork/bor"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/log"
pcsc "github.com/gballet/go-libpcsclite"
"github.com/status-im/keycard-go/derivationpath"
)

View file

@ -23,10 +23,10 @@ import (
"sync/atomic"
"time"
"github.com/karalabe/usb"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/event"
"github.com/maticnetwork/bor/log"
"github.com/karalabe/usb"
)
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.

View file

@ -27,13 +27,13 @@ import (
"io"
"math/big"
"github.com/golang/protobuf/proto"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/accounts/usbwallet/trezor"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/common/hexutil"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/log"
"github.com/golang/protobuf/proto"
)
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In

View file

@ -25,13 +25,13 @@ import (
"sync"
"time"
"github.com/karalabe/usb"
ethereum "github.com/maticnetwork/bor"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/log"
"github.com/karalabe/usb"
)
// Maximum time between wallet health checks to detect USB unplugs.

View file

@ -64,6 +64,7 @@ It expects the genesis file as argument.`,
ArgsUsage: "<filename> (<filename 2> ... <filename N>) <genesisPath>",
Flags: []cli.Flag{
utils.DataDirFlag,
utils.HeimdallURLFlag,
utils.CacheFlag,
utils.SyncModeFlag,
utils.GCModeFlag,

View file

@ -151,7 +151,7 @@ func enableWhisper(ctx *cli.Context) bool {
func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx)
cfg.Eth.HeimdallURL = ctx.String(utils.HeimdallURLFlag.Name)
cfg.Eth.HeimdallURL = ctx.GlobalString(utils.HeimdallURLFlag.Name)
log.Info("Connecting to heimdall service on...", "heimdallURL", cfg.Eth.HeimdallURL)
utils.RegisterEthService(stack, &cfg.Eth)

View file

@ -111,6 +111,7 @@ func (w *wizard) makeGenesis() {
Period: 1,
ProducerDelay: 5,
Sprint: 60,
BackupMultiplier: 1,
ValidatorContract: "0x0000000000000000000000000000000000001000",
StateReceiverContract: "0x0000000000000000000000000000000000001001",
}

View file

@ -1694,7 +1694,7 @@ func MakeChain(ctx *cli.Context, stack *node.Node) (chain *core.BlockChain, chai
if config.Clique != nil {
engine = clique.New(config.Clique, chainDb)
} else if config.Bor != nil {
cfg := &eth.Config{Genesis: genesis}
cfg := &eth.Config{Genesis: genesis, HeimdallURL: ctx.GlobalString(HeimdallURLFlag.Name)}
workspace, err := ioutil.TempDir("", "console-tester-")
if err != nil {
Fatalf("failed to create temporary keystore: %v", err)

View file

@ -17,17 +17,33 @@
package bor
import (
"encoding/hex"
"math"
"math/big"
"strconv"
"sync"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/rpc"
"github.com/xsleonard/go-merkle"
"golang.org/x/crypto/sha3"
)
var (
// MaxCheckpointLength is the maximum number of blocks that can be requested for constructing a checkpoint root hash
MaxCheckpointLength = uint64(math.Pow(2, 15))
)
// API is a user facing RPC API to allow controlling the signer and voting
// mechanisms of the proof-of-authority scheme.
type API struct {
chain consensus.ChainReader
bor *Bor
chain consensus.ChainReader
bor *Bor
rootHashCache *lru.ARCCache
}
// GetSnapshot retrieves the state snapshot at a given block.
@ -105,3 +121,71 @@ func (api *API) GetCurrentValidators() ([]*Validator, error) {
}
return snap.ValidatorSet.Validators, nil
}
// GetRootHash returns the merkle root of the start to end block headers
func (api *API) GetRootHash(start uint64, end uint64) (string, error) {
if err := api.initializeRootHashCache(); err != nil {
return "", err
}
key := getRootHashKey(start, end)
if root, known := api.rootHashCache.Get(key); known {
return root.(string), nil
}
length := uint64(end - start + 1)
if length > MaxCheckpointLength {
return "", &MaxCheckpointLengthExceededError{start, end}
}
currentHeaderNumber := api.chain.CurrentHeader().Number.Uint64()
if start > end || end > currentHeaderNumber {
return "", &InvalidStartEndBlockError{start, end, currentHeaderNumber}
}
blockHeaders := make([]*types.Header, end-start+1)
wg := new(sync.WaitGroup)
concurrent := make(chan bool, 20)
for i := start; i <= end; i++ {
wg.Add(1)
concurrent <- true
go func(number uint64) {
blockHeaders[number-start] = api.chain.GetHeaderByNumber(uint64(number))
<-concurrent
wg.Done()
}(i)
}
wg.Wait()
close(concurrent)
headers := make([][32]byte, nextPowerOfTwo(length))
for i := 0; i < len(blockHeaders); i++ {
blockHeader := blockHeaders[i]
header := crypto.Keccak256(appendBytes32(
blockHeader.Number.Bytes(),
new(big.Int).SetUint64(blockHeader.Time).Bytes(),
blockHeader.TxHash.Bytes(),
blockHeader.ReceiptHash.Bytes(),
))
var arr [32]byte
copy(arr[:], header)
headers[i] = arr
}
tree := merkle.NewTreeWithOpts(merkle.TreeOptions{EnableHashSorting: false, DisableHashLeaves: true})
if err := tree.Generate(convert(headers), sha3.NewLegacyKeccak256()); err != nil {
return "", err
}
root := hex.EncodeToString(tree.Root().Hash)
api.rootHashCache.Add(key, root)
return root, nil
}
func (api *API) initializeRootHashCache() error {
var err error
if api.rootHashCache == nil {
api.rootHashCache, err = lru.NewARC(10)
}
return err
}
func getRootHashKey(start uint64, end uint64) string {
return strconv.FormatUint(start, 10) + "-" + strconv.FormatUint(end, 10)
}

View file

@ -12,7 +12,6 @@ import (
"math/big"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -41,9 +40,6 @@ import (
"golang.org/x/crypto/sha3"
)
const validatorsetABI = `[{"constant":true,"inputs":[{"name":"span","type":"uint256"}],"name":"getSpan","outputs":[{"name":"number","type":"uint256"},{"name":"startBlock","type":"uint256"},{"name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"number","type":"uint256"}],"name":"getBorValidators","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"span","type":"uint256"},{"name":"signer","type":"address"}],"name":"isProducer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newSpan","type":"uint256"},{"name":"startBlock","type":"uint256"},{"name":"endBlock","type":"uint256"},{"name":"validatorBytes","type":"bytes"},{"name":"producerBytes","type":"bytes"}],"name":"commitSpan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"span","type":"uint256"},{"name":"signer","type":"address"}],"name":"isValidator","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"proposeSpan","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"currentSpanNumber","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNextSpan","outputs":[{"name":"number","type":"uint256"},{"name":"startBlock","type":"uint256"},{"name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitialValidators","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"spanProposalPending","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentSpan","outputs":[{"name":"number","type":"uint256"},{"name":"startBlock","type":"uint256"},{"name":"endBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"number","type":"uint256"}],"name":"getSpanByBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"","type":"address[]"},{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"vote","type":"bytes"},{"name":"sigs","type":"bytes"},{"name":"txBytes","type":"bytes"},{"name":"proof","type":"bytes"}],"name":"validateValidatorSet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]`
const stateReceiverABI = `[{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"states","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"recordBytes","type":"bytes"}],"name":"commitState","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getPendingStates","outputs":[{"name":"","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SYSTEM_ADDRESS","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"validatorSet","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"vote","type":"bytes"},{"name":"sigs","type":"bytes"},{"name":"txBytes","type":"bytes"},{"name":"proof","type":"bytes"}],"name":"validateValidatorSet","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isValidatorSetContract","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"stateId","type":"uint256"}],"name":"proposeState","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"signer","type":"address"}],"name":"isProducer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"signer","type":"address"}],"name":"isValidator","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"}]`
const (
checkpointInterval = 1024 // Number of blocks after which to save the vote snapshot to the database
inmemorySnapshots = 128 // Number of recent vote snapshots to keep in memory
@ -105,10 +101,6 @@ var (
// invalid list of validators (i.e. non divisible by 40 bytes).
errInvalidSpanValidators = errors.New("invalid validator list on sprint end block")
// errMismatchingSprintValidators is returned if a sprint block contains a
// list of validators different than the one the local node calculated.
errMismatchingSprintValidators = errors.New("mismatching validator list on sprint block")
// errInvalidMixDigest is returned if a block's mix digest is non-zero.
errInvalidMixDigest = errors.New("non-zero mix digest")
@ -118,10 +110,6 @@ var (
// errInvalidDifficulty is returned if the difficulty of a block neither 1 or 2.
errInvalidDifficulty = errors.New("invalid difficulty")
// errWrongDifficulty is returned if the difficulty of a block doesn't match the
// turn of the signer.
errWrongDifficulty = errors.New("wrong difficulty")
// ErrInvalidTimestamp is returned if the timestamp of a block is lower than
// the previous block's timestamp + the minimum block period.
ErrInvalidTimestamp = errors.New("invalid timestamp")
@ -129,13 +117,6 @@ var (
// errOutOfRangeChain is returned if an authorization list is attempted to
// be modified via out-of-range or non-contiguous headers.
errOutOfRangeChain = errors.New("out of range or non-contiguous chain")
// errUnauthorizedSigner is returned if a header is signed by a non-authorized entity.
errUnauthorizedSigner = errors.New("unauthorized signer")
// errRecentlySigned is returned if a header is signed by an authorized entity
// that already signed a header recently, thus is temporarily not allowed to.
errRecentlySigned = errors.New("recently signed")
)
// SignerFn is a signer callback function to request a header to be signed by a
@ -198,21 +179,18 @@ func encodeSigHeader(w io.Writer, header *types.Header) {
}
}
// CalcDifficulty is the difficulty adjustment algorithm. It returns the difficulty
// that a new block should have based on the previous blocks in the chain and the
// current signer.
func CalcDifficulty(snap *Snapshot, signer common.Address, epoch uint64) *big.Int {
return big.NewInt(0).SetUint64(snap.inturn(snap.Number+1, signer, epoch))
}
// CalcProducerDelay is the producer delay algorithm based on block time.
func CalcProducerDelay(snap *Snapshot, signer common.Address, period uint64, epoch uint64, producerDelay uint64) uint64 {
// if block is epoch start block, proposer will be inturn signer
if (snap.Number+1)%epoch == 0 {
return producerDelay
// CalcProducerDelay is the block delay algorithm based on block time, period, producerDelay and turn-ness of a signer
func CalcProducerDelay(number uint64, succession int, c *params.BorConfig) uint64 {
// When the block is the first block of the sprint, it is expected to be delayed by `producerDelay`.
// That is to allow time for block propagation in the last sprint
delay := c.Period
if number%c.Sprint == 0 {
delay = c.ProducerDelay
}
return period
if succession > 0 {
delay += uint64(succession) * c.BackupMultiplier
}
return delay
}
// BorRLP returns the rlp bytes which needs to be signed for the bor
@ -241,10 +219,11 @@ type Bor struct {
signFn SignerFn // Signer function to authorize hashes with
lock sync.RWMutex // Protects the signer fields
ethAPI *ethapi.PublicBlockChainAPI
validatorSetABI abi.ABI
stateReceiverABI abi.ABI
HeimdallClient IHeimdallClient
ethAPI *ethapi.PublicBlockChainAPI
GenesisContractsClient *GenesisContractsClient
validatorSetABI abi.ABI
stateReceiverABI abi.ABI
HeimdallClient IHeimdallClient
stateDataFeed event.Feed
scope event.SubscriptionScope
@ -273,17 +252,18 @@ func New(
vABI, _ := abi.JSON(strings.NewReader(validatorsetABI))
sABI, _ := abi.JSON(strings.NewReader(stateReceiverABI))
heimdallClient, _ := NewHeimdallClient(heimdallURL)
genesisContractsClient := NewGenesisContractsClient(chainConfig, borConfig.ValidatorContract, borConfig.StateReceiverContract, ethAPI)
c := &Bor{
chainConfig: chainConfig,
config: borConfig,
db: db,
ethAPI: ethAPI,
recents: recents,
signatures: signatures,
validatorSetABI: vABI,
stateReceiverABI: sABI,
HeimdallClient: heimdallClient,
chainConfig: chainConfig,
config: borConfig,
db: db,
ethAPI: ethAPI,
recents: recents,
signatures: signatures,
validatorSetABI: vABI,
stateReceiverABI: sABI,
GenesisContractsClient: genesisContractsClient,
HeimdallClient: heimdallClient,
}
return c
@ -335,12 +315,9 @@ func (c *Bor) verifyHeader(chain consensus.ChainReader, header *types.Header, pa
if header.Time > uint64(time.Now().Unix()) {
return consensus.ErrFutureBlock
}
// Check that the extra-data contains both the vanity and signature
if len(header.Extra) < extraVanity {
return errMissingVanity
}
if len(header.Extra) < extraVanity+extraSeal {
return errMissingSignature
if err := validateHeaderExtraField(header.Extra); err != nil {
return err
}
// check extr adata
@ -376,6 +353,18 @@ func (c *Bor) verifyHeader(chain consensus.ChainReader, header *types.Header, pa
return c.verifyCascadingFields(chain, header, parents)
}
// validateHeaderExtraField validates that the extra-data contains both the vanity and signature.
// header.Extra = header.Vanity + header.ProducerBytes (optional) + header.Seal
func validateHeaderExtraField(extraBytes []byte) error {
if len(extraBytes) < extraVanity {
return errMissingVanity
}
if len(extraBytes) < extraVanity+extraSeal {
return errMissingSignature
}
return nil
}
// verifyCascadingFields verifies all the header fields that are not standalone,
// rather depend on a batch of previous headers. The caller may optionally pass
// in a batch of parents (ascending order) to avoid looking those up from the
@ -409,8 +398,9 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainReader, header *types.H
return err
}
// If the block is a sprint end block, verify the validator list
if number%c.config.Sprint == 0 {
// verify the validator list in the last sprint block
if isSprintStart(number, c.config.Sprint) {
parentValidatorBytes := parent.Extra[extraVanity : len(parent.Extra)-extraSeal]
validatorsBytes := make([]byte, len(snap.ValidatorSet.Validators)*validatorHeaderBytesLength)
currentValidators := snap.ValidatorSet.Copy().Validators
@ -419,14 +409,9 @@ func (c *Bor) verifyCascadingFields(chain consensus.ChainReader, header *types.H
for i, validator := range currentValidators {
copy(validatorsBytes[i*validatorHeaderBytesLength:], validator.HeaderBytes())
}
extraSuffix := len(header.Extra) - extraSeal
// fmt.Println("validatorsBytes ==> verify seal ==> ", hex.EncodeToString(validatorsBytes))
// fmt.Println("header.Extra ==> verify seal ==> ", hex.EncodeToString(header.Extra[extraVanity:extraSuffix]))
if !bytes.Equal(header.Extra[extraVanity:extraSuffix], validatorsBytes) {
// return errMismatchingSprintValidators
// len(header.Extra) >= extraVanity+extraSeal has already been validated in validateHeaderExtraField, so this won't result in a panic
if !bytes.Equal(parentValidatorBytes, validatorsBytes) {
return &MismatchingValidatorsError{number - 1, validatorsBytes, parentValidatorBytes}
}
}
@ -463,7 +448,7 @@ func (c *Bor) snapshot(chain consensus.ChainReader, number uint64, hash common.H
// up more headers than allowed to be reorged (chain reinit from a freezer),
// consider the checkpoint trusted and snapshot it.
// TODO fix this
if number == 0 /* || (number%c.config.Sprint == 0 && (len(headers) > params.ImmutabilityThreshold || chain.GetHeaderByNumber(number-1) == nil)) */ {
if number == 0 {
checkpoint := chain.GetHeaderByNumber(number)
if checkpoint != nil {
// get checkpoint data
@ -568,37 +553,31 @@ func (c *Bor) verifySeal(chain consensus.ChainReader, header *types.Header, pare
return err
}
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return errUnauthorizedSigner
// Check the UnauthorizedSignerError.Error() msg to see why we pass number-1
return &UnauthorizedSignerError{number - 1, signer.Bytes()}
}
// check if signer is correct
validators := snap.ValidatorSet.Validators
// proposer will be the last signer if block is not epoch block
proposer := snap.ValidatorSet.GetProposer().Address
if number%c.config.Sprint != 0 {
// proposer = snap.Recents[number-1]
succession, err := snap.GetSignerSuccessionNumber(signer)
if err != nil {
return err
}
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
signerIndex, _ := snap.ValidatorSet.GetByAddress(signer)
limit := len(validators)/2 + 1
// temp index
tempIndex := signerIndex
if proposerIndex != tempIndex && limit > 0 {
if tempIndex < proposerIndex {
tempIndex = tempIndex + len(validators)
}
var parent *types.Header
if len(parents) > 0 { // if parents is nil, len(parents) is zero
parent = parents[len(parents)-1]
} else if number > 0 {
parent = chain.GetHeader(header.ParentHash, number-1)
}
if tempIndex-proposerIndex > limit {
return errRecentlySigned
}
if parent != nil && header.Time < parent.Time+CalcProducerDelay(number, succession, c.config) {
return &BlockTooSoonError{number, succession}
}
// Ensure that the difficulty corresponds to the turn-ness of the signer
if !c.fakeDiff {
difficulty := snap.inturn(header.Number.Uint64(), signer, c.config.Sprint)
difficulty := snap.Difficulty(signer)
if header.Difficulty.Uint64() != difficulty {
return errWrongDifficulty
return &WrongDifficultyError{number, difficulty, header.Difficulty.Uint64(), signer.Bytes()}
}
}
@ -620,7 +599,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
}
// Set the correct difficulty
header.Difficulty = CalcDifficulty(snap, c.signer, c.config.Sprint)
header.Difficulty = new(big.Int).SetUint64(snap.Difficulty(c.signer))
// Ensure the extra data has all it's components
if len(header.Extra) < extraVanity {
@ -654,7 +633,16 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
return consensus.ErrUnknownAncestor
}
header.Time = parent.Time + CalcProducerDelay(snap, c.signer, c.config.Period, c.config.Sprint, c.config.ProducerDelay)
var succession int
// if signer is not empty
if bytes.Compare(c.signer.Bytes(), common.Address{}.Bytes()) != 0 {
succession, err = snap.GetSignerSuccessionNumber(c.signer)
if err != nil {
return err
}
}
header.Time = parent.Time + CalcProducerDelay(number, succession, c.config)
if header.Time < uint64(time.Now().Unix()) {
header.Time = uint64(time.Now().Unix())
}
@ -664,9 +652,7 @@ func (c *Bor) Prepare(chain consensus.ChainReader, header *types.Header) error {
// Finalize implements consensus.Engine, ensuring no uncles are set, nor block
// rewards given.
func (c *Bor) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// commit span
headerNumber := header.Number.Uint64()
if headerNumber%c.config.Sprint == 0 {
cx := chainContext{Chain: chain, Bor: c}
// check and commit span
@ -674,6 +660,7 @@ func (c *Bor) Finalize(chain consensus.ChainReader, header *types.Header, state
log.Error("Error while committing span", "error", err)
return
}
// commit statees
if err := c.CommitStates(state, header, cx); err != nil {
log.Error("Error while committing states", "error", err)
@ -689,8 +676,8 @@ func (c *Bor) Finalize(chain consensus.ChainReader, header *types.Header, state
// FinalizeAndAssemble implements consensus.Engine, ensuring no uncles are set,
// nor block rewards given, and returns the final block.
func (c *Bor) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) {
// commit span
if header.Number.Uint64()%c.config.Sprint == 0 {
headerNumber := header.Number.Uint64()
if headerNumber%c.config.Sprint == 0 {
cx := chainContext{Chain: chain, Bor: c}
// check and commit span
@ -703,7 +690,7 @@ func (c *Bor) FinalizeAndAssemble(chain consensus.ChainReader, header *types.Hea
// commit statees
if err := c.CommitStates(state, header, cx); err != nil {
log.Error("Error while committing states", "error", err)
// return nil, err
return nil, err
}
}
@ -752,38 +739,19 @@ func (c *Bor) Seal(chain consensus.ChainReader, block *types.Block, results chan
// Bail out if we're unauthorized to sign a block
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return errUnauthorizedSigner
// Check the UnauthorizedSignerError.Error() msg to see why we pass number-1
return &UnauthorizedSignerError{number - 1, signer.Bytes()}
}
validators := snap.ValidatorSet.Validators
// proposer will be the last signer if block is not epoch block
proposer := snap.ValidatorSet.GetProposer().Address
if number%c.config.Sprint != 0 {
// proposer = snap.Recents[number-1]
}
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
signerIndex, _ := snap.ValidatorSet.GetByAddress(signer)
limit := len(validators)/2 + 1
// temp index
tempIndex := signerIndex
if tempIndex < proposerIndex {
tempIndex = tempIndex + len(validators)
}
if limit > 0 && tempIndex-proposerIndex > limit {
log.Info("Signed recently, must wait for others")
return nil
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
if err != nil {
return err
}
// Sweet, the protocol permits us to sign the block, wait for our time
delay := time.Unix(int64(header.Time), 0).Sub(time.Now()) // nolint: gosimple
wiggle := time.Duration(2*c.config.Period) * time.Second * time.Duration(tempIndex-proposerIndex)
delay += wiggle
log.Info("Out-of-turn signing requested", "wiggle", common.PrettyDuration(wiggle))
log.Info("Sealing block with", "number", number, "delay", delay, "headerDifficulty", header.Difficulty, "signer", signer.Hex(), "proposer", proposer.Hex())
// wiggle was already accounted for in header.Time, this is just for logging
wiggle := time.Duration(successionNumber) * time.Duration(c.config.BackupMultiplier) * time.Second
// Sign all the things!
sighash, err := signFn(accounts.Account{Address: signer}, accounts.MimetypeBor, BorRLP(header))
@ -797,17 +765,30 @@ func (c *Bor) Seal(chain consensus.ChainReader, block *types.Block, results chan
go func() {
select {
case <-stop:
log.Debug("Discarding sealing operation for block", "number", number)
return
case <-time.After(delay):
if wiggle > 0 {
log.Info(
"Sealing out-of-turn",
"number", number,
"wiggle", common.PrettyDuration(wiggle),
"in-turn-signer", snap.ValidatorSet.GetProposer().Address.Hex(),
)
}
log.Info(
"Sealing successful",
"number", number,
"delay", delay,
"headerDifficulty", header.Difficulty,
)
}
select {
case results <- block.WithSeal(header):
default:
log.Warn("Sealing result is not read by miner", "sealhash", SealHash(header))
log.Warn("Sealing result was not read by miner", "number", number, "sealhash", SealHash(header))
}
}()
return nil
}
@ -819,7 +800,7 @@ func (c *Bor) CalcDifficulty(chain consensus.ChainReader, time uint64, parent *t
if err != nil {
return nil
}
return CalcDifficulty(snap, c.signer, c.config.Sprint)
return new(big.Int).SetUint64(snap.Difficulty(c.signer))
}
// SealHash returns the hash of a block prior to it being sealed.
@ -843,42 +824,6 @@ func (c *Bor) Close() error {
return nil
}
// Checks if new span is pending
func (c *Bor) isSpanPending(snapshotNumber uint64) (bool, error) {
blockNr := rpc.BlockNumber(snapshotNumber)
method := "spanProposalPending"
// get packed data
data, err := c.validatorSetABI.Pack(method)
if err != nil {
log.Error("Unable to pack tx for spanProposalPending", "error", err)
return false, err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
// call
msgData := (hexutil.Bytes)(data)
toAddress := common.HexToAddress(c.config.ValidatorContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
result, err := c.ethAPI.Call(ctx, ethapi.CallArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
}, blockNr)
if err != nil {
return false, err
}
var ret0 = new(bool)
if err := c.validatorSetABI.Unpack(ret0, method, result); err != nil {
return false, err
}
return *ret0, nil
}
// GetCurrentSpan get current span from contract
func (c *Bor) GetCurrentSpan(snapshotNumber uint64) (*Span, error) {
// block
@ -893,14 +838,10 @@ func (c *Bor) GetCurrentSpan(snapshotNumber uint64) (*Span, error) {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
// call
msgData := (hexutil.Bytes)(data)
toAddress := common.HexToAddress(c.config.ValidatorContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
result, err := c.ethAPI.Call(ctx, ethapi.CallArgs{
result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
@ -943,14 +884,11 @@ func (c *Bor) GetCurrentValidators(snapshotNumber uint64, blockNumber uint64) ([
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
// call
msgData := (hexutil.Bytes)(data)
toAddress := common.HexToAddress(c.config.ValidatorContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
result, err := c.ethAPI.Call(ctx, ethapi.CallArgs{
result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
@ -990,37 +928,14 @@ func (c *Bor) checkAndCommitSpan(
chain core.ChainContext,
) error {
headerNumber := header.Number.Uint64()
pending := false
var span *Span = nil
errors := make(chan error)
go func() {
var err error
pending, err = c.isSpanPending(headerNumber - 1)
errors <- err
}()
go func() {
var err error
span, err = c.GetCurrentSpan(headerNumber - 1)
errors <- err
}()
var err error
for i := 0; i < 2; i++ {
err = <-errors
if err != nil {
close(errors)
return err
}
span, err := c.GetCurrentSpan(headerNumber - 1)
if err != nil {
return err
}
close(errors)
// commit span if there is new span pending or span is ending or end block is not set
if pending || c.needToCommitSpan(span, headerNumber) {
if c.needToCommitSpan(span, headerNumber) {
err := c.fetchAndCommitSpan(span.ID+1, state, header, chain)
return err
}
return nil
}
@ -1049,7 +964,7 @@ func (c *Bor) fetchAndCommitSpan(
header *types.Header,
chain core.ChainContext,
) error {
response, err := c.HeimdallClient.FetchWithRetry("bor", "span", strconv.FormatUint(newSpanID, 10))
response, err := c.HeimdallClient.FetchWithRetry(fmt.Sprintf("bor/span/%d", newSpanID), "")
if err != nil {
return err
@ -1133,13 +1048,10 @@ func (c *Bor) GetPendingStateProposals(snapshotNumber uint64) ([]*big.Int, error
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel() // cancel when we are finished consuming integers
msgData := (hexutil.Bytes)(data)
toAddress := common.HexToAddress(c.config.StateReceiverContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
result, err := c.ethAPI.Call(ctx, ethapi.CallArgs{
result, err := c.ethAPI.Call(context.Background(), ethapi.CallArgs{
Gas: &gas,
To: &toAddress,
Data: &msgData,
@ -1160,83 +1072,55 @@ func (c *Bor) GetPendingStateProposals(snapshotNumber uint64) ([]*big.Int, error
func (c *Bor) CommitStates(
state *state.StateDB,
header *types.Header,
chain core.ChainContext,
chain chainContext,
) error {
// get pending state proposals
stateIds, err := c.GetPendingStateProposals(header.Number.Uint64() - 1)
number := header.Number.Uint64()
_lastStateID, err := c.GenesisContractsClient.LastStateId(number - 1)
if err != nil {
return err
}
// state ids
if len(stateIds) > 0 {
log.Debug("Found new proposed states", "numberOfStates", len(stateIds))
}
to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.Sprint).Time), 0)
lastStateID := _lastStateID.Uint64()
log.Info(
"Fetching state updates from Heimdall",
"fromID", lastStateID+1,
"to", to.Format(time.RFC3339))
eventRecords, err := c.HeimdallClient.FetchStateSyncEvents(lastStateID+1, to.Unix())
method := "commitState"
// itereate through state ids
for _, stateID := range stateIds {
// fetch from heimdall
response, err := c.HeimdallClient.FetchWithRetry("clerk", "event-record", strconv.FormatUint(stateID.Uint64(), 10))
if err != nil {
return err
chainID := c.chainConfig.ChainID.String()
for _, eventRecord := range eventRecords {
if eventRecord.ID <= lastStateID {
continue
}
if err := validateEventRecord(eventRecord, number, to, lastStateID, chainID); err != nil {
log.Error(err.Error())
break
}
// get event record
var eventRecord EventRecord
if err := json.Unmarshal(response.Result, &eventRecord); err != nil {
return err
}
// check if chain id matches with event record
if eventRecord.ChainID != "" && eventRecord.ChainID != c.chainConfig.ChainID.String() {
return fmt.Errorf(
"Chain id proposed state in span, %s, and bor chain id, %s, doesn't match",
eventRecord.ChainID,
c.chainConfig.ChainID,
)
}
log.Info("→ committing new state",
"id", eventRecord.ID,
"contract", eventRecord.Contract,
"data", hex.EncodeToString(eventRecord.Data),
"txHash", eventRecord.TxHash,
"chainID", eventRecord.ChainID,
)
stateData := types.StateData{
Did: eventRecord.ID,
Contract: eventRecord.Contract,
Data: hex.EncodeToString(eventRecord.Data),
TxHash: eventRecord.TxHash,
}
go func() {
c.stateDataFeed.Send(core.NewStateChangeEvent{StateData: &stateData})
}()
recordBytes, err := rlp.EncodeToBytes(eventRecord)
if err != nil {
return err
}
// get packed data for commit state
data, err := c.stateReceiverABI.Pack(method, recordBytes)
if err != nil {
log.Error("Unable to pack tx for commitState", "error", err)
return err
}
// get system message
msg := getSystemMessage(common.HexToAddress(c.config.StateReceiverContract), data)
// apply message
if err := applyMessage(msg, state, header, c.chainConfig, chain); err != nil {
if err := c.GenesisContractsClient.CommitState(eventRecord, state, header, chain); err != nil {
return err
}
lastStateID++
}
return nil
}
func validateEventRecord(eventRecord *EventRecordWithTime, number uint64, to time.Time, lastStateID uint64, chainID string) error {
// event id should be sequential and event.Time should lie in the range [from, to)
if lastStateID+1 != eventRecord.ID || eventRecord.ChainID != chainID || !eventRecord.Time.Before(to) {
return &InvalidStateReceivedError{number, lastStateID, &to, eventRecord}
}
return nil
}
@ -1249,48 +1133,6 @@ func (c *Bor) SetHeimdallClient(h IHeimdallClient) {
c.HeimdallClient = h
}
func (c *Bor) IsValidatorAction(chain consensus.ChainReader, from common.Address, tx *types.Transaction) bool {
header := chain.CurrentHeader()
validators, err := c.GetCurrentValidators(header.Number.Uint64(), header.Number.Uint64()+1)
if err != nil {
log.Error("Failed fetching snapshot", err)
return false
}
isValidator := false
for _, validator := range validators {
if bytes.Compare(validator.Address.Bytes(), from.Bytes()) == 0 {
isValidator = true
break
}
}
return isValidator && (isProposeSpanAction(tx, chain.Config().Bor.ValidatorContract) ||
isProposeStateAction(tx, chain.Config().Bor.StateReceiverContract))
}
func isProposeSpanAction(tx *types.Transaction, validatorContract string) bool {
// keccak256('proposeSpan()').slice(0, 4)
proposeSpanSig, _ := hex.DecodeString("4b0e4d17")
if tx.Data() == nil || len(tx.Data()) < 4 {
return false
}
return bytes.Compare(proposeSpanSig, tx.Data()[:4]) == 0 &&
tx.To().String() == validatorContract
}
func isProposeStateAction(tx *types.Transaction, stateReceiverContract string) bool {
// keccak256('proposeState(uint256)').slice(0, 4)
proposeStateSig, _ := hex.DecodeString("ede01f17")
if tx.Data() == nil || len(tx.Data()) < 4 {
return false
}
return bytes.Compare(proposeStateSig, tx.Data()[:4]) == 0 &&
tx.To().String() == stateReceiverContract
}
//
// Private methods
//
@ -1403,3 +1245,7 @@ func getUpdatedValidatorSet(oldValidatorSet *ValidatorSet, newVals []*Validator)
v.UpdateWithChangeSet(changes)
return v
}
func isSprintStart(number, sprint uint64) bool {
return number%sprint == 0
}

View file

@ -2,130 +2,269 @@ package bortest
import (
"encoding/hex"
"encoding/json"
"math/big"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
"github.com/maticnetwork/bor/core/rawdb"
"github.com/stretchr/testify/assert"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/mocks"
)
func TestCommitSpan(t *testing.T) {
var (
spanPath = "bor/span/1"
clerkPath = "clerk/event-record/list"
clerkQueryParams = "from-time=%d&to-time=%d&page=%d&limit=50"
)
func TestInsertingSpanSizeBlocks(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
// Mock HeimdallClient.FetchWithRetry to return span data from span.json
res, heimdallSpan := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").
Return(res, nil).
Times(2) // both FinalizeAndAssemble and chain.InsertChain call HeimdallClient.FetchWithRetry. @todo Investigate this in depth
h, heimdallSpan := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
// Build 1st block's header
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor.Period)
// to := int64(block.Header().Time)
statedb, err := chain.State()
// Insert sprintSize # of blocks so that span is fetched at the start of a new sprint
for i := uint64(1); i <= spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
validators, err := _bor.GetCurrentValidators(sprintSize, spanSize) // check validator set at the first block of new span
if err != nil {
t.Fatalf("%s", err)
}
_key, _ := hex.DecodeString(privKey)
insertNewBlock(t, _bor, chain, header, statedb, _key)
assert.True(t, h.AssertNumberOfCalls(t, "FetchWithRetry", 2))
validators, err := _bor.GetCurrentValidators(1, 256) // new span starts at 256
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, len(validators), 3)
assert.Equal(t, 3, len(validators))
for i, validator := range validators {
assert.Equal(t, validator.Address.Bytes(), heimdallSpan.SelectedProducers[i].Address.Bytes())
assert.Equal(t, validator.VotingPower, heimdallSpan.SelectedProducers[i].VotingPower)
}
}
func TestIsValidatorAction(t *testing.T) {
func TestFetchStateSyncEvents(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
proposeStateData, _ := hex.DecodeString("ede01f170000000000000000000000000000000000000000000000000000000000000000")
proposeSpanData, _ := hex.DecodeString("4b0e4d17")
var tx *types.Transaction
tx = types.NewTransaction(
0,
common.HexToAddress(chain.Config().Bor.StateReceiverContract),
big.NewInt(0), 0, big.NewInt(0),
proposeStateData,
)
assert.True(t, _bor.IsValidatorAction(chain, addr, tx))
// A. Insert blocks for 0th sprint
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
// Insert sprintSize # of blocks so that span is fetched at the start of a new sprint
for i := uint64(1); i < sprintSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
tx = types.NewTransaction(
0,
common.HexToAddress(chain.Config().Bor.ValidatorContract),
big.NewInt(0), 0, big.NewInt(0),
proposeSpanData,
)
assert.True(t, _bor.IsValidatorAction(chain, addr, tx))
res, heimdallSpan := loadSpanFromFile(t)
// B. Before inserting 1st block of the next sprint, mock heimdall deps
// B.1 Mock /bor/span/1
res, _ := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
h.On("FetchWithRetry", spanPath, "").Return(res, nil)
// B.2 Mock State Sync events
fromID := uint64(1)
// at # sprintSize, events are fetched for [fromID, (block-sprint).Time)
to := int64(chain.GetHeaderByNumber(0).Time)
eventCount := 50
sample := getSampleEventRecord(t)
sample.Time = time.Unix(to-int64(eventCount+1), 0) // last event.Time will be just < to
eventRecords := generateFakeStateSyncEvents(sample, eventCount)
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
_bor.SetHeimdallClient(h)
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
}
func TestFetchStateSyncEvents_2(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
// Mock /bor/span/1
res, _ := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", spanPath, "").Return(res, nil)
// Mock State Sync events
// at # sprintSize, events are fetched for [fromID, (block-sprint).Time)
fromID := uint64(1)
to := int64(chain.GetHeaderByNumber(0).Time)
sample := getSampleEventRecord(t)
// First query will be from [id=1, (block-sprint).Time]
// Insert 5 events in this time range
eventRecords := []*bor.EventRecordWithTime{
buildStateEvent(sample, 1, 3), // id = 1, time = 1
buildStateEvent(sample, 2, 1), // id = 2, time = 3
buildStateEvent(sample, 3, 2), // id = 3, time = 2
// event with id 5 is missing
buildStateEvent(sample, 4, 5), // id = 4, time = 5
buildStateEvent(sample, 6, 4), // id = 6, time = 4
}
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
_bor.SetHeimdallClient(h)
// Insert blocks for 0th sprint
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
for i := uint64(1); i <= sprintSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
lastStateID, _ := _bor.GenesisContractsClient.LastStateId(sprintSize)
// state 6 was not written
assert.Equal(t, uint64(4), lastStateID.Uint64())
//
fromID = uint64(5)
to = int64(chain.GetHeaderByNumber(sprintSize).Time)
eventRecords = []*bor.EventRecordWithTime{
buildStateEvent(sample, 5, 7),
buildStateEvent(sample, 6, 4),
}
h.On("FetchStateSyncEvents", fromID, to).Return(eventRecords, nil)
for i := sprintSize + 1; i <= spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
assert.True(t, h.AssertCalled(t, "FetchStateSyncEvents", fromID, to))
lastStateID, _ = _bor.GenesisContractsClient.LastStateId(spanSize)
assert.Equal(t, uint64(6), lastStateID.Uint64())
}
func TestOutOfTurnSigning(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
h, _ := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
// Build 1st block's header
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor.Period)
statedb, err := chain.State()
if err != nil {
t.Fatalf("%s", err)
for i := uint64(1); i < spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
_key, _ := hex.DecodeString(privKey)
insertNewBlock(t, _bor, chain, header, statedb, _key)
// insert spanSize-th block
// This account is one the out-of-turn validators for 1st (0-indexed) span
signer := "c8deb0bea5c41afe8e37b4d1bd84e31adff11b09c8c96ff4b605003cce067cd9"
signerKey, _ := hex.DecodeString(signer)
key, _ = crypto.HexToECDSA(signer)
addr = crypto.PubkeyToAddress(key.PublicKey)
expectedSuccessionNumber := 2
block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor)
_, err := chain.InsertChain([]*types.Block{block})
assert.Equal(t,
*err.(*bor.BlockTooSoonError),
bor.BlockTooSoonError{Number: spanSize, Succession: expectedSuccessionNumber})
expectedDifficulty := uint64(3 - expectedSuccessionNumber) // len(validators) - succession
header := block.Header()
header.Time += (bor.CalcProducerDelay(header.Number.Uint64(), expectedSuccessionNumber, init.genesis.Config.Bor) -
bor.CalcProducerDelay(header.Number.Uint64(), 0, init.genesis.Config.Bor))
sign(t, header, signerKey)
block = types.NewBlockWithHeader(header)
_, err = chain.InsertChain([]*types.Block{block})
assert.Equal(t,
*err.(*bor.WrongDifficultyError),
bor.WrongDifficultyError{Number: spanSize, Expected: expectedDifficulty, Actual: 3, Signer: addr.Bytes()})
var headers []*types.Header
for i := int64(2); i <= 255; i++ {
header := buildMinimalNextHeader(t, block, init.genesis.Config.Bor.Period)
headers = append(headers, header)
block = types.NewBlockWithHeader(header)
header.Difficulty = new(big.Int).SetUint64(expectedDifficulty)
sign(t, header, signerKey)
block = types.NewBlockWithHeader(header)
_, err = chain.InsertChain([]*types.Block{block})
assert.Nil(t, err)
}
func TestSignerNotFound(t *testing.T) {
init := buildEthereumInstance(t, rawdb.NewMemoryDatabase())
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
h, _ := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
// random signer account that is not a part of the validator set
signer := "3714d99058cd64541433d59c6b391555b2fd9b54629c2b717a6c9c00d1127b6b"
signerKey, _ := hex.DecodeString(signer)
key, _ = crypto.HexToECDSA(signer)
addr = crypto.PubkeyToAddress(key.PublicKey)
block = buildNextBlock(t, _bor, chain, block, signerKey, init.genesis.Config.Bor)
_, err := chain.InsertChain([]*types.Block{block})
assert.Equal(t,
*err.(*bor.UnauthorizedSignerError),
bor.UnauthorizedSignerError{Number: 0, Signer: addr.Bytes()})
}
func getMockedHeimdallClient(t *testing.T) (*mocks.IHeimdallClient, *bor.HeimdallSpan) {
res, heimdallSpan := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor/span/1", "").Return(res, nil)
h.On(
"FetchStateSyncEvents",
mock.AnythingOfType("uint64"),
mock.AnythingOfType("int64")).Return([]*bor.EventRecordWithTime{getSampleEventRecord(t)}, nil)
return h, heimdallSpan
}
func generateFakeStateSyncEvents(sample *bor.EventRecordWithTime, count int) []*bor.EventRecordWithTime {
events := make([]*bor.EventRecordWithTime, count)
event := *sample
event.ID = 1
events[0] = &bor.EventRecordWithTime{}
*events[0] = event
for i := 1; i < count; i++ {
event.ID = uint64(i)
event.Time = event.Time.Add(1 * time.Second)
events[i] = &bor.EventRecordWithTime{}
*events[i] = event
}
t.Logf("inserting %v headers", len(headers))
if _, err := chain.InsertHeaderChain(headers, 0); err != nil {
return events
}
func buildStateEvent(sample *bor.EventRecordWithTime, id uint64, timeStamp int64) *bor.EventRecordWithTime {
event := *sample
event.ID = id
event.Time = time.Unix(timeStamp, 0)
return &event
}
func getSampleEventRecord(t *testing.T) *bor.EventRecordWithTime {
res := stateSyncEventsPayload(t)
var _eventRecords []*bor.EventRecordWithTime
if err := json.Unmarshal(res.Result, &_eventRecords); err != nil {
t.Fatalf("%s", err)
}
for _, validator := range heimdallSpan.SelectedProducers {
_addr := validator.Address
tx = types.NewTransaction(
0,
common.HexToAddress(chain.Config().Bor.StateReceiverContract),
big.NewInt(0), 0, big.NewInt(0),
proposeStateData,
)
assert.True(t, _bor.IsValidatorAction(chain, _addr, tx))
tx = types.NewTransaction(
0,
common.HexToAddress(chain.Config().Bor.ValidatorContract),
big.NewInt(0), 0, big.NewInt(0),
proposeSpanData,
)
assert.True(t, _bor.IsValidatorAction(chain, _addr, tx))
}
_eventRecords[0].Time = time.Unix(1, 0)
return _eventRecords[0]
}

File diff suppressed because one or more lines are too long

View file

@ -5,24 +5,38 @@ import (
"encoding/json"
"io/ioutil"
"math/big"
"sort"
"testing"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
"github.com/maticnetwork/bor/core"
"github.com/maticnetwork/bor/core/state"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/crypto/secp256k1"
"github.com/maticnetwork/bor/eth"
"github.com/maticnetwork/bor/ethdb"
"github.com/maticnetwork/bor/node"
"github.com/maticnetwork/bor/params"
)
var (
// The genesis for tests was generated with following parameters
extraSeal = 65 // Fixed number of extra-data suffix bytes reserved for signer seal
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
// Only this account is a validator for the 0th span
privKey = "b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291"
key, _ = crypto.HexToECDSA(privKey)
addr = crypto.PubkeyToAddress(key.PublicKey) // 0x71562b71999873DB5b286dF957af199Ec94617F7
// This account is one the validators for 1st span (0-indexed)
privKey2 = "9b28f36fbd67381120752d6172ecdcf10e06ab2d9a1367aac00cdcd6ac7855d3"
key2, _ = crypto.HexToECDSA(privKey2)
addr2 = crypto.PubkeyToAddress(key2.PublicKey) // 0x9fB29AAc15b9A4B7F17c3385939b007540f4d791
validatorHeaderBytesLength = common.AddressLength + 20 // address + power
sprintSize uint64 = 4
spanSize uint64 = 8
)
type initializeData struct {
@ -81,37 +95,77 @@ func buildEthereumInstance(t *testing.T, db ethdb.Database) *initializeData {
}
}
func insertNewBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, header *types.Header, statedb *state.StateDB, privKey []byte) {
_, err := _bor.FinalizeAndAssemble(chain, header, statedb, nil, nil, nil)
if err != nil {
t.Fatalf("%s", err)
}
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), privKey)
if err != nil {
t.Fatalf("%s", err)
}
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
block := types.NewBlockWithHeader(header)
func insertNewBlock(t *testing.T, chain *core.BlockChain, block *types.Block) {
if _, err := chain.InsertChain([]*types.Block{block}); err != nil {
t.Fatalf("%s", err)
}
}
func buildMinimalNextHeader(t *testing.T, block *types.Block, period uint64) *types.Header {
func buildNextBlock(t *testing.T, _bor *bor.Bor, chain *core.BlockChain, block *types.Block, signer []byte, borConfig *params.BorConfig) *types.Block {
header := block.Header()
header.Number.Add(header.Number, big.NewInt(1))
number := header.Number.Uint64()
if signer == nil {
signer = getSignerKey(header.Number.Uint64())
}
header.ParentHash = block.Hash()
header.Time += (period + 1)
header.Extra = make([]byte, 97) // vanity (32) + extraSeal (65)
_key, _ := hex.DecodeString(privKey)
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), _key)
header.Time += bor.CalcProducerDelay(header.Number.Uint64(), 0, borConfig)
header.Extra = make([]byte, 32+65) // vanity + extraSeal
currentValidators := []*bor.Validator{bor.NewValidator(addr, 10)}
isSpanEnd := (number+1)%spanSize == 0
isSpanStart := number%spanSize == 0
isSprintEnd := (header.Number.Uint64()+1)%sprintSize == 0
if isSpanEnd {
_, heimdallSpan := loadSpanFromFile(t)
// this is to stash the validator bytes in the header
currentValidators = heimdallSpan.ValidatorSet.Validators
} else if isSpanStart {
header.Difficulty = new(big.Int).SetInt64(3)
}
if isSprintEnd {
sort.Sort(bor.ValidatorsByAddress(currentValidators))
validatorBytes := make([]byte, len(currentValidators)*validatorHeaderBytesLength)
header.Extra = make([]byte, 32+len(validatorBytes)+65) // vanity + validatorBytes + extraSeal
for i, val := range currentValidators {
copy(validatorBytes[i*validatorHeaderBytesLength:], val.HeaderBytes())
}
copy(header.Extra[32:], validatorBytes)
}
state, err := chain.State()
if err != nil {
t.Fatalf("%s", err)
}
_, err = _bor.FinalizeAndAssemble(chain, header, state, nil, nil, nil)
if err != nil {
t.Fatalf("%s", err)
}
sign(t, header, signer)
return types.NewBlockWithHeader(header)
}
func sign(t *testing.T, header *types.Header, signer []byte) {
sig, err := secp256k1.Sign(crypto.Keccak256(bor.BorRLP(header)), signer)
if err != nil {
t.Fatalf("%s", err)
}
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
return header
}
func stateSyncEventsPayload(t *testing.T) *bor.ResponseWithHeight {
stateData, err := ioutil.ReadFile("states.json")
if err != nil {
t.Fatalf("%s", err)
}
res := &bor.ResponseWithHeight{}
if err := json.Unmarshal(stateData, res); err != nil {
t.Fatalf("%s", err)
}
return res
}
func loadSpanFromFile(t *testing.T) (*bor.ResponseWithHeight, *bor.HeimdallSpan) {
@ -130,3 +184,14 @@ func loadSpanFromFile(t *testing.T) (*bor.ResponseWithHeight, *bor.HeimdallSpan)
}
return res, heimdallSpan
}
func getSignerKey(number uint64) []byte {
signerKey := privKey
isSpanStart := number%spanSize == 0
if isSpanStart {
// validator set in the new span has changed
signerKey = privKey2
}
_key, _ := hex.DecodeString(signerKey)
return _key
}

View file

@ -0,0 +1,125 @@
package bortest
import (
"math/rand"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
)
const (
numVals = 100
)
func TestGetSignerSuccessionNumber_ProposerIsSigner(t *testing.T) {
validators := buildRandomValidatorSet(numVals)
validatorSet := bor.NewValidatorSet(validators)
snap := bor.Snapshot{
ValidatorSet: validatorSet,
}
// proposer is signer
signer := validatorSet.Proposer.Address
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, 0, successionNumber)
}
func TestGetSignerSuccessionNumber_SignerIndexIsLarger(t *testing.T) {
validators := buildRandomValidatorSet(numVals)
// sort validators by address, which is what bor.NewValidatorSet also does
sort.Sort(bor.ValidatorsByAddress(validators))
proposerIndex := 32
signerIndex := 56
// give highest ProposerPriority to a particular val, so that they become the proposer
validators[proposerIndex].VotingPower = 200
snap := bor.Snapshot{
ValidatorSet: bor.NewValidatorSet(validators),
}
// choose a signer at an index greater than proposer index
signer := snap.ValidatorSet.Validators[signerIndex].Address
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, signerIndex-proposerIndex, successionNumber)
}
func TestGetSignerSuccessionNumber_SignerIndexIsSmaller(t *testing.T) {
validators := buildRandomValidatorSet(numVals)
proposerIndex := 98
signerIndex := 11
// give highest ProposerPriority to a particular val, so that they become the proposer
validators[proposerIndex].VotingPower = 200
snap := bor.Snapshot{
ValidatorSet: bor.NewValidatorSet(validators),
}
// choose a signer at an index greater than proposer index
signer := snap.ValidatorSet.Validators[signerIndex].Address
successionNumber, err := snap.GetSignerSuccessionNumber(signer)
if err != nil {
t.Fatalf("%s", err)
}
assert.Equal(t, signerIndex+numVals-proposerIndex, successionNumber)
}
func TestGetSignerSuccessionNumber_ProposerNotFound(t *testing.T) {
validators := buildRandomValidatorSet(numVals)
snap := bor.Snapshot{
ValidatorSet: bor.NewValidatorSet(validators),
}
dummyProposerAddress := randomAddress()
snap.ValidatorSet.Proposer = &bor.Validator{Address: dummyProposerAddress}
// choose any signer
signer := snap.ValidatorSet.Validators[3].Address
_, err := snap.GetSignerSuccessionNumber(signer)
assert.NotNil(t, err)
e, ok := err.(*bor.UnauthorizedProposerError)
assert.True(t, ok)
assert.Equal(t, dummyProposerAddress.Bytes(), e.Proposer)
}
func TestGetSignerSuccessionNumber_SignerNotFound(t *testing.T) {
validators := buildRandomValidatorSet(numVals)
snap := bor.Snapshot{
ValidatorSet: bor.NewValidatorSet(validators),
}
dummySignerAddress := randomAddress()
_, err := snap.GetSignerSuccessionNumber(dummySignerAddress)
assert.NotNil(t, err)
e, ok := err.(*bor.UnauthorizedSignerError)
assert.True(t, ok)
assert.Equal(t, dummySignerAddress.Bytes(), e.Signer)
}
func buildRandomValidatorSet(numVals int) []*bor.Validator {
rand.Seed(time.Now().Unix())
validators := make([]*bor.Validator, numVals)
for i := 0; i < numVals; i++ {
validators[i] = &bor.Validator{
Address: randomAddress(),
// cannot process validators with voting power 0, hence +1
VotingPower: int64(rand.Intn(99) + 1),
}
}
// sort validators by address, which is what bor.NewValidatorSet also does
sort.Sort(bor.ValidatorsByAddress(validators))
return validators
}
func randomAddress() common.Address {
bytes := make([]byte, 32)
rand.Read(bytes)
return common.BytesToAddress(bytes)
}

View file

@ -6,85 +6,57 @@
"end_block": 6655,
"validator_set": {
"validators": [{
"ID": 3,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x046434e10a34ade13c4fea917346a9fd1473eac2138a0b4e2a36426871918be63188fde4edbf598457592c9a49fe3b0036dd5497079495d132e5045bf499c4bdb1",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,
"accum": -40000
}, {
"ID": 4,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x04d9d09f2afc9da3cccc164e8112eb6911a63f5ede10169768f800df83cf99c73f944411e9d4fac3543b11c5f84a82e56b36cfcd34f1d065855c1e2b27af8b5247",
"signer": "0x461295d3d9249215e758e939a150ab180950720b",
"last_updated": 0,
"accum": 10000
}, {
"ID": 5,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 30,
"pubKey": "0x04a36f6ed1f93acb0a38f4cacbe2467c72458ac41ce3b12b34d758205b2bc5d930a4e059462da7a0976c32fce766e1f7e8d73933ae72ac2af231fe161187743932",
"signer": "0x836fe3e3dd0a5f77d9d5b0f67e48048aaafcd5a0",
"signer": "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791",
"last_updated": 0,
"accum": 10000
}, {
"ID": 1,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 20,
"pubKey": "0x04a312814042a6655c8e5ecf0c52cba0b6a6f3291c87cc42260a3c0222410c0d0d59b9139d1c56542e5df0ce2fce3a86ce13e93bd9bde0dc8ff664f8dd5294dead",
"signer": "0x925a91f8003aaeabea6037103123b93c50b86ca3",
"signer": "0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a",
"last_updated": 0,
"accum": 10000
}, {
"ID": 2,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"power": 10,
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
"signer": "0x1c4f0f054a0d6a1415382dc0fd83c6535188b220",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,
"accum": 10000
}],
"proposer": {
"ID": 3,
"startEpoch": 0,
"endEpoch": 0,
"power": 10000,
"pubKey": "0x046434e10a34ade13c4fea917346a9fd1473eac2138a0b4e2a36426871918be63188fde4edbf598457592c9a49fe3b0036dd5497079495d132e5045bf499c4bdb1",
"signer": "0x1c4f0f054a0d6a1415382dc0fd83c6535188b220",
"last_updated": 0,
"accum": -40000
}
"accum": 5000
}]
},
"selected_producers": [{
"ID": 5,
"startEpoch": 0,
"endEpoch": 0,
"power": 1,
"power": 30,
"pubKey": "0x04a36f6ed1f93acb0a38f4cacbe2467c72458ac41ce3b12b34d758205b2bc5d930a4e059462da7a0976c32fce766e1f7e8d73933ae72ac2af231fe161187743932",
"signer": "0x836fe3e3dd0a5f77d9d5b0f67e48048aaafcd5a0",
"signer": "0x9fB29AAc15b9A4B7F17c3385939b007540f4d791",
"last_updated": 0,
"accum": 10000
}, {
"ID": 1,
"startEpoch": 0,
"endEpoch": 0,
"power": 1,
"power": 20,
"pubKey": "0x04a312814042a6655c8e5ecf0c52cba0b6a6f3291c87cc42260a3c0222410c0d0d59b9139d1c56542e5df0ce2fce3a86ce13e93bd9bde0dc8ff664f8dd5294dead",
"signer": "0x925a91f8003aaeabea6037103123b93c50b86ca3",
"signer": "0x96C42C56fdb78294F96B0cFa33c92bed7D75F96a",
"last_updated": 0,
"accum": 10000
}, {
"ID": 2,
"startEpoch": 0,
"endEpoch": 0,
"power": 2,
"power": 10,
"pubKey": "0x0469536ae98030a7e83ec5ef3baffed2d05a32e31d978e58486f6bdb0fbbf240293838325116090190c0639db03f9cbd8b9aecfd269d016f46e3a2287fbf9ad232",
"signer": "0xc787af4624cb3e80ee23ae7faac0f2acea2be34c",
"last_updated": 0,

View file

@ -0,0 +1,23 @@
{
"height": "0",
"result": [
{
"id": 1,
"contract": "0xb55969a6d60413a63291a1de572269875df541e3",
"data": "0x00000000000000000000000048aa8d4af32551892fcf08ad63be7dd206d46f6500000000000000000000000048aa8d4af32551892fcf08ad63be7dd206d46f65000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014",
"tx_hash": "0x7b113e09d98b6d4be1dedfbc0746e34876de767f2cb8b58ff00160a160811dd6",
"log_index": 0,
"bor_chain_id": "15001",
"record_time": "2020-05-15T13:36:38.580995Z"
},
{
"id": 2,
"contract": "0xb55969a6d60413a63291a1de572269875df541e3",
"data": "0x00000000000000000000000048aa8d4af32551892fcf08ad63be7dd206d46f6500000000000000000000000048aa8d4af32551892fcf08ad63be7dd206d46f65000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000015",
"tx_hash": "0xb72358aff8e4d61f4de97a37a40ddda986c081e0de8036e0a78c4b61b067cba9",
"log_index": 0,
"bor_chain_id": "15001",
"record_time": "2020-05-15T13:42:37.319058Z"
}
]
}

View file

@ -1,6 +1,9 @@
package bor
import (
"fmt"
"time"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/common/hexutil"
)
@ -14,3 +17,33 @@ type EventRecord struct {
LogIndex uint64 `json:"log_index" yaml:"log_index"`
ChainID string `json:"bor_chain_id" yaml:"bor_chain_id"`
}
type EventRecordWithTime struct {
EventRecord
Time time.Time `json:"record_time" yaml:"record_time"`
}
// String returns the string representatin of span
func (e *EventRecordWithTime) String() string {
return fmt.Sprintf(
"id %v, contract %v, data: %v, txHash: %v, logIndex: %v, chainId: %v, time %s",
e.ID,
e.Contract.String(),
e.Data.String(),
e.TxHash.Hex(),
e.LogIndex,
e.ChainID,
e.Time.Format(time.RFC3339),
)
}
func (e *EventRecordWithTime) BuildEventRecord() *EventRecord {
return &EventRecord{
ID: e.ID,
Contract: e.Contract,
Data: e.Data,
TxHash: e.TxHash,
LogIndex: e.LogIndex,
ChainID: e.ChainID,
}
}

143
consensus/bor/errors.go Normal file
View file

@ -0,0 +1,143 @@
package bor
import (
"fmt"
"time"
)
// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded
type TotalVotingPowerExceededError struct {
Sum int64
Validators []*Validator
}
func (e *TotalVotingPowerExceededError) Error() string {
return fmt.Sprintf(
"Total voting power should be guarded to not exceed %v; got: %v; for validator set: %v",
MaxTotalVotingPower,
e.Sum,
e.Validators,
)
}
type InvalidStartEndBlockError struct {
Start uint64
End uint64
CurrentHeader uint64
}
func (e *InvalidStartEndBlockError) Error() string {
return fmt.Sprintf(
"Invalid parameters start: %d and end block: %d params",
e.Start,
e.End,
)
}
type MaxCheckpointLengthExceededError struct {
Start uint64
End uint64
}
func (e *MaxCheckpointLengthExceededError) Error() string {
return fmt.Sprintf(
"Start: %d and end block: %d exceed max allowed checkpoint length: %d",
e.Start,
e.End,
MaxCheckpointLength,
)
}
// MismatchingValidatorsError is returned if a last block in sprint contains a
// list of validators different from the one that local node calculated
type MismatchingValidatorsError struct {
Number uint64
ValidatorSetSnap []byte
ValidatorSetHeader []byte
}
func (e *MismatchingValidatorsError) Error() string {
return fmt.Sprintf(
"Mismatching validators at block %d\nValidatorBytes from snapshot: 0x%x\nValidatorBytes in Header: 0x%x\n",
e.Number,
e.ValidatorSetSnap,
e.ValidatorSetHeader,
)
}
type BlockTooSoonError struct {
Number uint64
Succession int
}
func (e *BlockTooSoonError) Error() string {
return fmt.Sprintf(
"Block %d was created too soon. Signer turn-ness number is %d\n",
e.Number,
e.Succession,
)
}
// UnauthorizedProposerError is returned if a header is [being] signed by an unauthorized entity.
type UnauthorizedProposerError struct {
Number uint64
Proposer []byte
}
func (e *UnauthorizedProposerError) Error() string {
return fmt.Sprintf(
"Proposer 0x%x is not a part of the producer set at block %d",
e.Proposer,
e.Number,
)
}
// UnauthorizedSignerError is returned if a header is [being] signed by an unauthorized entity.
type UnauthorizedSignerError struct {
Number uint64
Signer []byte
}
func (e *UnauthorizedSignerError) Error() string {
return fmt.Sprintf(
"Signer 0x%x is not a part of the producer set at block %d",
e.Signer,
e.Number,
)
}
// WrongDifficultyError is returned if the difficulty of a block doesn't match the
// turn of the signer.
type WrongDifficultyError struct {
Number uint64
Expected uint64
Actual uint64
Signer []byte
}
func (e *WrongDifficultyError) Error() string {
return fmt.Sprintf(
"Wrong difficulty at block %d, expected: %d, actual %d. Signer was %x\n",
e.Number,
e.Expected,
e.Actual,
e.Signer,
)
}
type InvalidStateReceivedError struct {
Number uint64
LastStateID uint64
To *time.Time
Event *EventRecordWithTime
}
func (e *InvalidStateReceivedError) Error() string {
return fmt.Sprintf(
"Received invalid event %s at block %d. Requested events until %s. Last state id was %d",
e.Event,
e.Number,
e.To.Format(time.RFC3339),
e.LastStateID,
)
}

File diff suppressed because one or more lines are too long

48
consensus/bor/merkle.go Normal file
View file

@ -0,0 +1,48 @@
package bor
func appendBytes32(data ...[]byte) []byte {
var result []byte
for _, v := range data {
paddedV, err := convertTo32(v)
if err == nil {
result = append(result, paddedV[:]...)
}
}
return result
}
func nextPowerOfTwo(n uint64) uint64 {
if n == 0 {
return 1
}
// http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
n--
n |= n >> 1
n |= n >> 2
n |= n >> 4
n |= n >> 8
n |= n >> 16
n |= n >> 32
n++
return n
}
func convertTo32(input []byte) (output [32]byte, err error) {
l := len(input)
if l > 32 || l == 0 {
return
}
copy(output[32-l:], input[:])
return
}
func convert(input []([32]byte)) [][]byte {
var output [][]byte
for _, in := range input {
newInput := make([]byte, len(in[:]))
copy(newInput, in[:])
output = append(output, newInput)
}
return output
}

View file

@ -6,12 +6,16 @@ import (
"io/ioutil"
"net/http"
"net/url"
"path"
"sort"
"time"
"github.com/maticnetwork/bor/log"
)
var (
stateFetchLimit = 50
)
// ResponseWithHeight defines a response object type that wraps an original
// response with a height.
type ResponseWithHeight struct {
@ -20,8 +24,9 @@ type ResponseWithHeight struct {
}
type IHeimdallClient interface {
Fetch(paths ...string) (*ResponseWithHeight, error)
FetchWithRetry(paths ...string) (*ResponseWithHeight, error)
Fetch(path string, query string) (*ResponseWithHeight, error)
FetchWithRetry(path string, query string) (*ResponseWithHeight, error)
FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error)
}
type HeimdallClient struct {
@ -39,33 +44,57 @@ func NewHeimdallClient(urlString string) (*HeimdallClient, error) {
return h, nil
}
func (h *HeimdallClient) Fetch(paths ...string) (*ResponseWithHeight, error) {
func (h *HeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*EventRecordWithTime, error) {
eventRecords := make([]*EventRecordWithTime, 0)
for {
queryParams := fmt.Sprintf("from-id=%d&to-time=%d&limit=%d", fromID, to, stateFetchLimit)
log.Info("Fetching state sync events", "queryParams", queryParams)
response, err := h.FetchWithRetry("clerk/event-record/list", queryParams)
if err != nil {
return nil, err
}
var _eventRecords []*EventRecordWithTime
if response.Result == nil { // status 204
break
}
if err := json.Unmarshal(response.Result, &_eventRecords); err != nil {
return nil, err
}
eventRecords = append(eventRecords, _eventRecords...)
if len(_eventRecords) < stateFetchLimit {
break
}
fromID += uint64(stateFetchLimit)
}
sort.SliceStable(eventRecords, func(i, j int) bool {
return eventRecords[i].ID < eventRecords[j].ID
})
return eventRecords, nil
}
// Fetch fetches response from heimdall
func (h *HeimdallClient) Fetch(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
u, err := url.Parse(h.urlString)
if err != nil {
return nil, err
}
for _, e := range paths {
if e != "" {
u.Path = path.Join(u.Path, e)
}
}
u.Path = rawPath
u.RawQuery = rawQuery
return h.internalFetch(u)
}
// FetchWithRetry returns data from heimdall with retry
func (h *HeimdallClient) FetchWithRetry(paths ...string) (*ResponseWithHeight, error) {
func (h *HeimdallClient) FetchWithRetry(rawPath string, rawQuery string) (*ResponseWithHeight, error) {
u, err := url.Parse(h.urlString)
if err != nil {
return nil, err
}
for _, e := range paths {
if e != "" {
u.Path = path.Join(u.Path, e)
}
}
u.Path = rawPath
u.RawQuery = rawQuery
for {
res, err := h.internalFetch(u)
@ -86,18 +115,22 @@ func (h *HeimdallClient) internalFetch(u *url.URL) (*ResponseWithHeight, error)
defer res.Body.Close()
// check status code
if res.StatusCode != 200 {
if res.StatusCode != 200 && res.StatusCode != 204 {
return nil, fmt.Errorf("Error while fetching data from Heimdall")
}
// unmarshall data from buffer
var response ResponseWithHeight
if res.StatusCode == 204 {
return &response, nil
}
// get response
body, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
// unmarshall data from buffer
var response ResponseWithHeight
if err := json.Unmarshal(body, &response); err != nil {
return nil, err
}

View file

@ -26,7 +26,6 @@ import (
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/ethdb"
"github.com/maticnetwork/bor/internal/ethapi"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/params"
)
@ -87,7 +86,9 @@ func loadSnapshot(config *params.BorConfig, sigcache *lru.ARCCache, db ethdb.Dat
snap.ethAPI = ethAPI
// update total voting power
snap.ValidatorSet.updateTotalVotingPower()
if err := snap.ValidatorSet.updateTotalVotingPower(); err != nil {
return nil, err
}
return snap, nil
}
@ -153,31 +154,11 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
// check if signer is in validator set
if !snap.ValidatorSet.HasAddress(signer.Bytes()) {
return nil, errUnauthorizedSigner
return nil, &UnauthorizedSignerError{number, signer.Bytes()}
}
//
// Check validator
//
validators := snap.ValidatorSet.Validators
// proposer will be the last signer if block is not epoch block
proposer := snap.ValidatorSet.GetProposer().Address
proposerIndex, _ := snap.ValidatorSet.GetByAddress(proposer)
signerIndex, _ := snap.ValidatorSet.GetByAddress(signer)
limit := len(validators)/2 + 1
// temp index
tempIndex := signerIndex
if proposerIndex != tempIndex && limit > 0 {
if tempIndex < proposerIndex {
tempIndex = tempIndex + len(validators)
}
if tempIndex-proposerIndex > limit {
log.Info("Invalid signer: error while applying headers", "proposerIndex", validators[proposerIndex].Address.Hex(), "signerIndex", validators[signerIndex].Address.Hex())
return nil, errRecentlySigned
}
if _, err = snap.GetSignerSuccessionNumber(signer); err != nil {
return nil, err
}
// add recents
@ -185,6 +166,9 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
// change validator set and change proposer
if number > 0 && (number+1)%s.config.Sprint == 0 {
if err := validateHeaderExtraField(header.Extra); err != nil {
return nil, err
}
validatorBytes := header.Extra[extraVanity : len(header.Extra)-extraSeal]
// get validators from headers and use that for new validator set
@ -200,6 +184,28 @@ func (s *Snapshot) apply(headers []*types.Header) (*Snapshot, error) {
return snap, nil
}
// GetSignerSuccessionNumber returns the relative position of signer in terms of the in-turn proposer
func (s *Snapshot) GetSignerSuccessionNumber(signer common.Address) (int, error) {
validators := s.ValidatorSet.Validators
proposer := s.ValidatorSet.GetProposer().Address
proposerIndex, _ := s.ValidatorSet.GetByAddress(proposer)
if proposerIndex == -1 {
return -1, &UnauthorizedProposerError{s.Number, proposer.Bytes()}
}
signerIndex, _ := s.ValidatorSet.GetByAddress(signer)
if signerIndex == -1 {
return -1, &UnauthorizedSignerError{s.Number, signer.Bytes()}
}
tempIndex := signerIndex
if proposerIndex != tempIndex {
if tempIndex < proposerIndex {
tempIndex = tempIndex + len(validators)
}
}
return tempIndex - proposerIndex, nil
}
// signers retrieves the list of authorized signers in ascending order.
func (s *Snapshot) signers() []common.Address {
sigs := make([]common.Address, 0, len(s.ValidatorSet.Validators))
@ -209,8 +215,8 @@ func (s *Snapshot) signers() []common.Address {
return sigs
}
// inturn returns if a signer at a given block height is in-turn or not.
func (s *Snapshot) inturn(number uint64, signer common.Address, epoch uint64) uint64 {
// Difficulty returns the difficulty for a particular signer at the current snapshot number
func (s *Snapshot) Difficulty(signer common.Address) uint64 {
// if signer is empty
if bytes.Compare(signer.Bytes(), common.Address{}.Bytes()) == 0 {
return 1

View file

@ -2,7 +2,7 @@ package bor
import (
"bytes"
"encoding/json"
// "encoding/json"
"errors"
"fmt"
"math/big"
@ -13,8 +13,6 @@ import (
)
// Validator represets Volatile state for each Validator
// NOTE: The ProposerPriority is not included in Validator.Hash();
// make sure to update that method if changes are made here
type Validator struct {
ID uint64 `json:"ID"`
Address common.Address `json:"signer"`
@ -31,18 +29,23 @@ func NewValidator(address common.Address, votingPower int64) *Validator {
}
}
// Creates a new copy of the validator so we can mutate ProposerPriority.
// Copy creates a new copy of the validator so we can mutate ProposerPriority.
// Panics if the validator is nil.
func (v *Validator) Copy() *Validator {
vCopy := *v
return &vCopy
}
// Returns the one with higher ProposerPriority.
func (v *Validator) CompareProposerPriority(other *Validator) *Validator {
// Cmp returns the one validator with a higher ProposerPriority.
// If ProposerPriority is same, it returns the validator with lexicographically smaller address
func (v *Validator) Cmp(other *Validator) *Validator {
// if both of v and other are nil, nil will be returned and that could possibly lead to nil pointer dereference bubbling up the stack
if v == nil {
return other
}
if other == nil {
return v
}
if v.ProposerPriority > other.ProposerPriority {
return v
} else if v.ProposerPriority < other.ProposerPriority {
@ -55,7 +58,6 @@ func (v *Validator) CompareProposerPriority(other *Validator) *Validator {
return other
} else {
panic("Cannot compare identical validators")
return nil
}
}
}
@ -80,18 +82,6 @@ func ValidatorListString(vals []*Validator) string {
return strings.Join(chunks, ",")
}
// Bytes computes the unique encoding of a validator with a given voting power.
// These are the bytes that gets hashed in consensus. It excludes address
// as its redundant with the pubkey. This also excludes ProposerPriority
// which changes every round.
func (v *Validator) Bytes() []byte {
b, err := json.Marshal(v)
if err != nil {
return b
}
return nil
}
// HeaderBytes return header bytes
func (v *Validator) HeaderBytes() []byte {
result := make([]byte, 40)

View file

@ -11,6 +11,7 @@ import (
"strings"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/log"
)
// MaxTotalVotingPower - the maximum allowed total voting power.
@ -182,7 +183,7 @@ func computeMaxMinPriorityDiff(vals *ValidatorSet) int64 {
func (vals *ValidatorSet) getValWithMostPriority() *Validator {
var res *Validator
for _, val := range vals.Validators {
res = res.CompareProposerPriority(val)
res = res.Cmp(val)
}
return res
}
@ -256,28 +257,29 @@ func (vals *ValidatorSet) Size() int {
}
// Force recalculation of the set's total voting power.
func (vals *ValidatorSet) updateTotalVotingPower() {
func (vals *ValidatorSet) updateTotalVotingPower() error {
sum := int64(0)
for _, val := range vals.Validators {
// mind overflow
sum = safeAddClip(sum, val.VotingPower)
if sum > MaxTotalVotingPower {
panic(fmt.Sprintf(
"Total voting power should be guarded to not exceed %v; got: %v",
MaxTotalVotingPower,
sum))
return &TotalVotingPowerExceededError{sum, vals.Validators}
}
}
vals.totalVotingPower = sum
return nil
}
// TotalVotingPower returns the sum of the voting powers of all validators.
// It recomputes the total voting power if required.
func (vals *ValidatorSet) TotalVotingPower() int64 {
if vals.totalVotingPower == 0 {
vals.updateTotalVotingPower()
log.Info("invoking updateTotalVotingPower before returning it")
if err := vals.updateTotalVotingPower(); err != nil {
// Can/should we do better?
panic(err)
}
}
return vals.totalVotingPower
}
@ -298,7 +300,7 @@ func (vals *ValidatorSet) findProposer() *Validator {
var proposer *Validator
for _, val := range vals.Validators {
if proposer == nil || !bytes.Equal(val.Address.Bytes(), proposer.Address.Bytes()) {
proposer = proposer.CompareProposerPriority(val)
proposer = proposer.Cmp(val)
}
}
return proposer
@ -562,7 +564,9 @@ func (vals *ValidatorSet) updateWithChangeSet(changes []*Validator, allowDeletes
vals.applyUpdates(updates)
vals.applyRemovals(deletes)
vals.updateTotalVotingPower()
if err := vals.updateTotalVotingPower(); err != nil {
return err
}
// Scale and center.
vals.RescalePriorities(PriorityWindowSizeFactor * vals.TotalVotingPower())

View file

@ -26,6 +26,7 @@ import (
"sync"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/accounts"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/common/hexutil"
@ -39,7 +40,6 @@ import (
"github.com/maticnetwork/bor/params"
"github.com/maticnetwork/bor/rlp"
"github.com/maticnetwork/bor/rpc"
lru "github.com/hashicorp/golang-lru"
"golang.org/x/crypto/sha3"
)

View file

@ -22,12 +22,12 @@ import (
"sort"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/ethdb"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/params"
lru "github.com/hashicorp/golang-lru"
)
// Vote represents a single vote that an authorized signer made to modify the

View file

@ -31,6 +31,9 @@ var (
// to the current node.
ErrFutureBlock = errors.New("block in the future")
// ErrBlockTooSoon is returned when the period / producerDelay values in the genesis were not respected
ErrBlockTooSoon = errors.New("block was produced sooner than expected")
// ErrInvalidNumber is returned if a block's number doesn't equal it's parent's
// plus one.
ErrInvalidNumber = errors.New("invalid block number")

View file

@ -34,13 +34,13 @@ import (
"unsafe"
mmap "github.com/edsrzf/mmap-go"
"github.com/hashicorp/golang-lru/simplelru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus"
"github.com/maticnetwork/bor/core/types"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/metrics"
"github.com/maticnetwork/bor/rpc"
"github.com/hashicorp/golang-lru/simplelru"
)
var ErrInvalidDumpMagic = errors.New("invalid dump magic")

View file

@ -26,6 +26,7 @@ import (
"sync/atomic"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus"
"github.com/maticnetwork/bor/core/rawdb"
@ -33,7 +34,6 @@ import (
"github.com/maticnetwork/bor/ethdb"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/params"
lru "github.com/hashicorp/golang-lru"
)
const (

View file

@ -26,10 +26,10 @@ import (
"sync"
"sync/atomic"
"github.com/golang/snappy"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/metrics"
"github.com/golang/snappy"
)
var (

View file

@ -19,10 +19,10 @@ package state
import (
"fmt"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/ethdb"
"github.com/maticnetwork/bor/trie"
lru "github.com/hashicorp/golang-lru"
)
const (

View file

@ -117,12 +117,6 @@ const (
TxStatusIncluded
)
// bor acts as a way to be able to type cast consensus.Engine;
// since importing "github.com/maticnetwork/bor/consensus/bor" results in a cyclic dependency
type bor interface {
IsValidatorAction(chain consensus.ChainReader, from common.Address, tx *types.Transaction) bool
}
// blockChain provides the state of blockchain and current gas limit to do
// some pre checks in tx pool and event subscribers.
type blockChain interface {
@ -537,7 +531,6 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local &&
!pool.chain.Engine().(bor).IsValidatorAction(pool.chain.(consensus.ChainReader), from, tx) &&
pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}

View file

@ -1075,7 +1075,7 @@ after@0.8.2:
resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f"
integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=
agent-base@4, agent-base@^4.1.0:
agent-base@4, agent-base@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
@ -3650,11 +3650,11 @@ https-browserify@^1.0.0:
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==
version "2.2.4"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
dependencies:
agent-base "^4.1.0"
agent-base "^4.3.0"
debug "^3.1.0"
humps@^2.0.1:

View file

@ -248,3 +248,17 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
}
}
func (b *EthAPIBackend) GetRootHash(ctx context.Context, starBlockNr uint64, endBlockNr uint64) (string, error) {
var api *bor.API
for _, _api := range b.eth.Engine().APIs(b.eth.BlockChain()) {
if _api.Namespace == "bor" {
api = _api.Service.(*bor.API)
}
}
root, err := api.GetRootHash(starBlockNr, endBlockNr)
if err != nil {
return "", err
}
return root, nil
}

View file

@ -522,6 +522,15 @@ func (ec *Client) SendTransaction(ctx context.Context, tx *types.Transaction) er
return ec.c.CallContext(ctx, nil, "eth_sendRawTransaction", common.ToHex(data))
}
// GetRootHash returns the merkle root of the block headers
func (ec *Client) GetRootHash(ctx context.Context, startBlockNumber uint64, endBlockNumber uint64) (string, error) {
var rootHash string
if err := ec.c.CallContext(ctx, &rootHash, "eth_getRootHash", startBlockNumber, endBlockNumber); err != nil {
return "", err
}
return rootHash, nil
}
func toCallArg(msg ethereum.CallMsg) interface{} {
arg := map[string]interface{}{
"from": msg.From,

1
go.mod
View file

@ -60,6 +60,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208
github.com/xsleonard/go-merkle v1.1.0
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e

2
go.sum
View file

@ -176,6 +176,8 @@ github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef h1:wHSqTBrZ
github.com/tyler-smith/go-bip39 v1.0.1-0.20181017060643-dbb3b84ba2ef/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs=
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208 h1:1cngl9mPEoITZG8s8cVcUy5CeIBYhEESkOB7m6Gmkrk=
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
github.com/xsleonard/go-merkle v1.1.0 h1:fHe1fuhJjGH22ZzVTAH0jqHLhTGhOq3wQjJN+8P0jQg=
github.com/xsleonard/go-merkle v1.1.0/go.mod h1:cW4z+UZ/4f2n9IJgIiyDCdYguchoDyDAPmpuOWGxdGg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=

View file

@ -21,12 +21,12 @@ import (
"net"
"net/http"
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
"github.com/maticnetwork/bor/internal/ethapi"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/p2p"
"github.com/maticnetwork/bor/rpc"
"github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
)
// Service encapsulates a GraphQL service.

View file

@ -24,10 +24,10 @@ import (
"os"
"runtime"
"github.com/fjl/memsize/memsizeui"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/metrics"
"github.com/maticnetwork/bor/metrics/exp"
"github.com/fjl/memsize/memsizeui"
colorable "github.com/mattn/go-colorable"
"github.com/mattn/go-isatty"
"gopkg.in/urfave/cli.v1"

View file

@ -716,6 +716,14 @@ func (s *PublicBlockChainAPI) GetStorageAt(ctx context.Context, address common.A
return res[:], state.Error()
}
func (s *PublicBlockChainAPI) GetRootHash(ctx context.Context, starBlockNr uint64, endBlockNr uint64) (string, error) {
root, err := s.b.GetRootHash(ctx, starBlockNr, endBlockNr)
if err != nil {
return "", err
}
return root, nil
}
// CallArgs represents the arguments for a call.
type CallArgs struct {
From *common.Address `json:"from"`

View file

@ -62,6 +62,7 @@ type Backend interface {
SubscribeStateEvent(ch chan<- core.NewStateChangeEvent) event.Subscription
SubscribeChainHeadEvent(ch chan<- core.ChainHeadEvent) event.Subscription
SubscribeChainSideEvent(ch chan<- core.ChainSideEvent) event.Subscription
GetRootHash(ctx context.Context, starBlockNr uint64, endBlockNr uint64) (string, error)
// Transaction pool API
SendTx(ctx context.Context, signedTx *types.Transaction) error

View file

@ -149,6 +149,11 @@ web3._extend({
call: 'bor_getCurrentValidators',
params: 0
}),
new web3._extend.Method({
name: 'getRootHash',
call: 'bor_getRootHash',
params: 2,
}),
]
});
`

View file

@ -223,3 +223,7 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma
go session.Multiplex(bloomRetrievalBatch, bloomRetrievalWait, b.eth.bloomRequests)
}
}
func (b *LesApiBackend) GetRootHash(ctx context.Context, starBlockNr uint64, endBlockNr uint64) (string, error) {
return "", errors.New("Not implemented")
}

View file

@ -26,6 +26,7 @@ import (
"sync/atomic"
"time"
lru "github.com/hashicorp/golang-lru"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus"
"github.com/maticnetwork/bor/core"
@ -37,7 +38,6 @@ import (
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/params"
"github.com/maticnetwork/bor/rlp"
lru "github.com/hashicorp/golang-lru"
)
var (

View file

@ -5,9 +5,9 @@ import (
uurl "net/url"
"time"
"github.com/influxdata/influxdb/client"
"github.com/maticnetwork/bor/log"
"github.com/maticnetwork/bor/metrics"
"github.com/influxdata/influxdb/client"
)
type reporter struct {

View file

@ -12,19 +12,13 @@ type IHeimdallClient struct {
mock.Mock
}
// Fetch provides a mock function with given fields: paths
func (_m *IHeimdallClient) Fetch(paths ...string) (*bor.ResponseWithHeight, error) {
_va := make([]interface{}, len(paths))
for _i := range paths {
_va[_i] = paths[_i]
}
var _ca []interface{}
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
// Fetch provides a mock function with given fields: path, query
func (_m *IHeimdallClient) Fetch(path string, query string) (*bor.ResponseWithHeight, error) {
ret := _m.Called(path, query)
var r0 *bor.ResponseWithHeight
if rf, ok := ret.Get(0).(func(...string) *bor.ResponseWithHeight); ok {
r0 = rf(paths...)
if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok {
r0 = rf(path, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bor.ResponseWithHeight)
@ -32,8 +26,8 @@ func (_m *IHeimdallClient) Fetch(paths ...string) (*bor.ResponseWithHeight, erro
}
var r1 error
if rf, ok := ret.Get(1).(func(...string) error); ok {
r1 = rf(paths...)
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(path, query)
} else {
r1 = ret.Error(1)
}
@ -41,19 +35,36 @@ func (_m *IHeimdallClient) Fetch(paths ...string) (*bor.ResponseWithHeight, erro
return r0, r1
}
// FetchWithRetry provides a mock function with given fields: paths
func (_m *IHeimdallClient) FetchWithRetry(paths ...string) (*bor.ResponseWithHeight, error) {
_va := make([]interface{}, len(paths))
for _i := range paths {
_va[_i] = paths[_i]
// FetchStateSyncEvents provides a mock function with given fields: fromID, to
func (_m *IHeimdallClient) FetchStateSyncEvents(fromID uint64, to int64) ([]*bor.EventRecordWithTime, error) {
ret := _m.Called(fromID, to)
var r0 []*bor.EventRecordWithTime
if rf, ok := ret.Get(0).(func(uint64, int64) []*bor.EventRecordWithTime); ok {
r0 = rf(fromID, to)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*bor.EventRecordWithTime)
}
}
var _ca []interface{}
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
var r1 error
if rf, ok := ret.Get(1).(func(uint64, int64) error); ok {
r1 = rf(fromID, to)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// FetchWithRetry provides a mock function with given fields: path, query
func (_m *IHeimdallClient) FetchWithRetry(path string, query string) (*bor.ResponseWithHeight, error) {
ret := _m.Called(path, query)
var r0 *bor.ResponseWithHeight
if rf, ok := ret.Get(0).(func(...string) *bor.ResponseWithHeight); ok {
r0 = rf(paths...)
if rf, ok := ret.Get(0).(func(string, string) *bor.ResponseWithHeight); ok {
r0 = rf(path, query)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*bor.ResponseWithHeight)
@ -61,8 +72,8 @@ func (_m *IHeimdallClient) FetchWithRetry(paths ...string) (*bor.ResponseWithHei
}
var r1 error
if rf, ok := ret.Get(1).(func(...string) error); ok {
r1 = rf(paths...)
if rf, ok := ret.Get(1).(func(string, string) error); ok {
r1 = rf(path, query)
} else {
r1 = ret.Error(1)
}

View file

@ -25,8 +25,8 @@ import (
"sync"
"time"
"github.com/maticnetwork/bor/log"
"github.com/jackpal/go-nat-pmp"
"github.com/maticnetwork/bor/log"
)
// An implementation of nat.Interface can map local ports to ports

View file

@ -35,11 +35,11 @@ import (
"sync"
"time"
"github.com/golang/snappy"
"github.com/maticnetwork/bor/common/bitutil"
"github.com/maticnetwork/bor/crypto"
"github.com/maticnetwork/bor/crypto/ecies"
"github.com/maticnetwork/bor/rlp"
"github.com/golang/snappy"
"golang.org/x/crypto/sha3"
)

View file

@ -29,12 +29,12 @@ import (
"strings"
"sync"
"github.com/julienschmidt/httprouter"
"github.com/maticnetwork/bor/event"
"github.com/maticnetwork/bor/p2p"
"github.com/maticnetwork/bor/p2p/enode"
"github.com/maticnetwork/bor/p2p/simulations/adapters"
"github.com/maticnetwork/bor/rpc"
"github.com/julienschmidt/httprouter"
"golang.org/x/net/websocket"
)

View file

@ -319,6 +319,7 @@ type BorConfig struct {
Period uint64 `json:"period"` // Number of seconds between blocks to enforce
ProducerDelay uint64 `json:"producerDelay"` // Number of seconds delay between two producer interval
Sprint uint64 `json:"sprint"` // Epoch length to proposer
BackupMultiplier uint64 `json:"backupMultiplier"` // Backup multiplier to determine the wiggle time
ValidatorContract string `json:"validatorContract"` // Validator set contract
StateReceiverContract string `json:"stateReceiverContract"` // State receiver contract
}