Merge pull request #64 from maticnetwork/mew-deposits

new: New fetch State sync logic
This commit is contained in:
Jaynti Kanani 2020-05-19 18:23:08 +05:30 committed by GitHub
commit 01c9c8b4e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 382 additions and 268 deletions

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
@ -66,6 +62,7 @@ var (
validatorHeaderBytesLength = common.AddressLength + 20 // address + power
systemAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE")
stateFetchLimit = 50
)
// Various error messages to mark blocks invalid. These should be private to
@ -223,10 +220,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
@ -255,17 +253,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
@ -654,9 +653,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
@ -664,6 +661,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)
@ -679,8 +677,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
@ -693,7 +691,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
}
}
@ -827,38 +825,6 @@ func (c *Bor) Close() error {
return nil
}
// Checks if "force" proposeSpan has been set
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
}
msgData := (hexutil.Bytes)(data)
toAddress := common.HexToAddress(c.config.ValidatorContract)
gas := (hexutil.Uint64)(uint64(math.MaxUint64 / 2))
result, err := c.ethAPI.Call(context.Background(), 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
@ -963,37 +929,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
}
@ -1022,7 +965,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
@ -1130,83 +1073,85 @@ 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()
lastSync, err := c.genesisContractsClient.LastStateSyncTime(number - 1)
if err != nil {
return err
}
// state ids
if len(stateIds) > 0 {
log.Debug("Found new proposed states", "numberOfStates", len(stateIds))
from := lastSync.Add(time.Second * 1) // querying the interval [from, to)
to := time.Unix(int64(chain.Chain.GetHeaderByNumber(number-c.config.Sprint).Time), 0)
if !from.Before(to) {
return nil
}
log.Info(
"Fetching state updates from Heimdall",
"from", from.Format(time.RFC3339),
"to", to.Format(time.RFC3339))
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))
page := 1
eventRecords := make([]*EventRecordWithTime, 0)
for {
queryParams := fmt.Sprintf("from-time=%d&to-time=%d&page=%d&limit=%d", from.Unix(), to.Unix(), page, stateFetchLimit)
log.Info("Fetching state sync events", "queryParams", queryParams)
response, err := c.HeimdallClient.FetchWithRetry("clerk/event-record/list", queryParams)
if err != nil {
return err
}
// get event record
var eventRecord EventRecord
if err := json.Unmarshal(response.Result, &eventRecord); err != nil {
var _eventRecords []*EventRecordWithTime
if response.Result == nil { // status 204
break
}
if err := json.Unmarshal(response.Result, &_eventRecords); err != nil {
return err
}
eventRecords = append(eventRecords, _eventRecords...)
if len(_eventRecords) < stateFetchLimit {
break
}
page++
}
// check if chain id matches with event record
if 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,
)
sort.SliceStable(eventRecords, func(i, j int) bool {
return eventRecords[i].ID < eventRecords[j].ID
})
chainID := c.chainConfig.ChainID.String()
for _, eventRecord := range eventRecords {
// validateEventRecord checks whether an event lies in the specified time range
// since the events are sorted by time and if it turns out that event i lies outside the time range,
// it would mean all subsequent events lie outside of the time range. Hence we don't probe any further and break the loop
if err := validateEventRecord(eventRecord, number, from, to, chainID); err != nil {
log.Error(
fmt.Sprintf(
"Received event %s does not lie in the time range, from %s, to %s",
eventRecord, from.Format(time.RFC3339), to.Format(time.RFC3339)))
break
}
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
}
}
return nil
}
func validateEventRecord(eventRecord *EventRecordWithTime, number uint64, from, to time.Time, chainID string) error {
// event should lie in the range [from, to)
if eventRecord.ChainID != chainID || eventRecord.Time.Before(from) || !eventRecord.Time.Before(to) {
return &InvalidStateReceivedError{number, &from, &to, eventRecord}
}
return nil
}
@ -1219,26 +1164,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")

View file

@ -2,45 +2,51 @@ package bortest
import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"testing"
"time"
"github.com/maticnetwork/bor/common"
"github.com/maticnetwork/bor/consensus/bor"
"github.com/maticnetwork/bor/core/rawdb"
"github.com/maticnetwork/bor/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/maticnetwork/bor/core/types"
"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)
h, heimdallSpan := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
to := int64(block.Header().Time)
// Insert sprintSize # of blocks so that span is fetched at the start of a new sprint
for i := uint64(1); i <= sprintSize; i++ {
for i := uint64(1); i <= spanSize; i++ {
block = buildNextBlock(t, _bor, chain, block, nil, init.genesis.Config.Bor)
insertNewBlock(t, chain, block)
}
// FetchWithRetry is invoked 2 times
// 1. bor.FinalizeAndAssemble to prepare a new block when calling buildNextBlock
// 2. bor.Finalize via(bc.insertChain => bc.processor.Process)
assert.True(t, h.AssertCalled(t, "FetchWithRetry", "bor", "span", "1"))
validators, err := _bor.GetCurrentValidators(sprintSize, 256) // new span starts at 256
assert.True(t, h.AssertCalled(t, "FetchWithRetry", spanPath, ""))
assert.True(t, h.AssertCalled(t, "FetchWithRetry", clerkPath, fmt.Sprintf(clerkQueryParams, 1, to, 1)))
validators, err := _bor.GetCurrentValidators(sprintSize, spanSize) // check validator set at the first block of new span
if err != nil {
t.Fatalf("%s", err)
}
@ -52,62 +58,61 @@ func TestCommitSpan(t *testing.T) {
}
}
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))
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)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
_bor.SetHeimdallClient(h)
// A. Insert blocks for 0th sprint
db := init.ethereum.ChainDb()
block := init.genesis.ToBlock(db)
for i := uint64(1); i <= spanSize; i++ {
// 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)
}
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))
// 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", spanPath, "").Return(res, nil)
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))
// B.2 Mock State Sync events
// read heimdall api response from file
res = stateSyncEventsPayload(t)
var _eventRecords []*bor.EventRecordWithTime
if err := json.Unmarshal(res.Result, &_eventRecords); err != nil {
t.Fatalf("%s", err)
}
// use that as a sample to generate bor.stateFetchLimit events
eventRecords := generateFakeStateSyncEvents(_eventRecords[0], 50)
_res, _ := json.Marshal(eventRecords)
response := bor.ResponseWithHeight{Height: "0"}
if err := json.Unmarshal(_res, &response.Result); err != nil {
t.Fatalf("%s", err)
}
// at # sprintSize, events are fetched for the interval [from, (block-sprint).Time)
from := 1
to := int64(chain.GetHeaderByNumber(0).Time)
page := 1
query1Params := fmt.Sprintf(clerkQueryParams, from, to, page)
h.On("FetchWithRetry", clerkPath, query1Params).Return(&response, nil)
page = 2
query2Params := fmt.Sprintf(clerkQueryParams, from, to, page)
h.On("FetchWithRetry", clerkPath, query2Params).Return(&bor.ResponseWithHeight{}, 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, "FetchWithRetry", clerkPath, query1Params))
assert.True(t, h.AssertCalled(t, "FetchWithRetry", clerkPath, query2Params))
}
func TestOutOfTurnSigning(t *testing.T) {
@ -115,10 +120,7 @@ func TestOutOfTurnSigning(t *testing.T) {
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
res, _ := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
h, _ := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
@ -166,10 +168,7 @@ func TestSignerNotFound(t *testing.T) {
chain := init.ethereum.BlockChain()
engine := init.ethereum.Engine()
_bor := engine.(*bor.Bor)
res, _ := loadSpanFromFile(t)
h := &mocks.IHeimdallClient{}
h.On("FetchWithRetry", "bor", "span", "1").Return(res, nil)
h, _ := getMockedHeimdallClient(t)
_bor.SetHeimdallClient(h)
db := init.ethereum.ChainDb()
@ -187,3 +186,27 @@ func TestSignerNotFound(t *testing.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("FetchWithRetry", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(stateSyncEventsPayload(t), nil)
return h, heimdallSpan
}
func generateFakeStateSyncEvents(sample *bor.EventRecordWithTime, count int) []*bor.EventRecordWithTime {
events := make([]*bor.EventRecordWithTime, count)
event := *sample
event.ID = 0
event.Time = time.Now()
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
}
return events
}

File diff suppressed because one or more lines are too long

View file

@ -156,6 +156,18 @@ func sign(t *testing.T, header *types.Header, signer []byte) {
copy(header.Extra[len(header.Extra)-extraSeal:], sig)
}
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) {
spanData, err := ioutil.ReadFile("span.json")
if err != nil {

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

View file

@ -2,6 +2,7 @@ package bor
import (
"fmt"
"time"
)
// TotalVotingPowerExceededError is returned when the maximum allowed total voting power is exceeded
@ -123,3 +124,20 @@ func (e *WrongDifficultyError) Error() string {
e.Signer,
)
}
type InvalidStateReceivedError struct {
Number uint64
From *time.Time
To *time.Time
Event *EventRecordWithTime
}
func (e *InvalidStateReceivedError) Error() string {
return fmt.Sprintf(
"Received event with invalid timestamp at block %d. Requested events from %s to %s. Received %s\n",
e.Number,
e.From.Format(time.RFC3339),
e.To.Format(time.RFC3339),
e.Event,
)
}

File diff suppressed because one or more lines are too long

View file

@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"
"net/url"
"path"
"time"
"github.com/maticnetwork/bor/log"
@ -20,8 +19,8 @@ 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)
}
type HeimdallClient struct {
@ -39,33 +38,28 @@ func NewHeimdallClient(urlString string) (*HeimdallClient, error) {
return h, nil
}
func (h *HeimdallClient) Fetch(paths ...string) (*ResponseWithHeight, error) {
// 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)

View file

@ -116,12 +116,6 @@ type Engine interface {
Close() error
}
// Bor is a consensus engine developed by folks at Matic Network
type Bor interface {
Engine
IsValidatorAction(chain ChainReader, from common.Address, tx *types.Transaction) bool
}
// PoW is a consensus engine based on proof-of-work.
type PoW interface {
Engine

View file

@ -531,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().(consensus.Bor).IsValidatorAction(pool.chain.(consensus.ChainReader), from, tx) &&
pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}

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,13 @@ 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]
}
var _ca []interface{}
_ca = append(_ca, _va...)
ret := _m.Called(_ca...)
// 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 +49,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)
}