From 7b7e34ae00a0a1a7e6797d0a117f525359c812ce Mon Sep 17 00:00:00 2001 From: Jianrong Date: Sun, 29 Aug 2021 14:20:01 +1000 Subject: [PATCH] Fix XDC forking incident with tests --- accounts/abi/bind/backends/simulated.go | 33 +- consensus/XDPoS/XDPoS.go | 39 ++ core/blockchain.go | 33 +- eth/api_backend.go | 4 +- internal/ethapi/backend.go | 4 +- les/api_backend.go | 4 +- params/config.go | 8 +- tests/end_to_end/block_signer_test.go | 483 ++++++++++++++++++++++++ 8 files changed, 579 insertions(+), 29 deletions(-) create mode 100644 tests/end_to_end/block_signer_test.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 0424f73117..9d8889c3e7 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -28,6 +28,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/consensus/XDPoS" "github.com/ethereum/go-ethereum/consensus/ethash" "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/bloombits" @@ -63,6 +64,28 @@ type SimulatedBackend struct { config *params.ChainConfig } +// XDC simulated backend for testing purpose. +func NewXDCSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend { + database := ethdb.NewMemDatabase() + genesis := core.Genesis{ + GasLimit: 10000000, // need this big, support initial smart contract + Config: params.TestXDPoSMockChainConfig, + Alloc: alloc, + ExtraData: append(make([]byte, 32), make([]byte, 65)...), + } + genesis.MustCommit(database) + blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, XDPoS.NewFaker(database), vm.Config{}, nil) + + backend := &SimulatedBackend{ + database: database, + blockchain: blockchain, + config: genesis.Config, + events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false), + } + backend.rollback() + return backend +} + // NewSimulatedBackend creates a new binding backend using a simulated blockchain // for testing purposes. func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend { @@ -102,7 +125,7 @@ func (b *SimulatedBackend) Rollback() { } func (b *SimulatedBackend) rollback() { - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {}) + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), XDPoS.GetFaker(), b.database, 1, func(int, *core.BlockGen) {}) statedb, _ := b.blockchain.State() b.pendingBlock = blocks[0] @@ -319,7 +342,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce)) } - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), XDPoS.GetFaker(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTxWithChain(b.blockchain, tx) } @@ -404,7 +427,7 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { b.mu.Lock() defer b.mu.Unlock() - blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) { + blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), XDPoS.GetFaker(), b.database, 1, func(number int, block *core.BlockGen) { for _, tx := range b.pendingBlock.Transactions() { block.AddTx(tx) } @@ -418,6 +441,10 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error { return nil } +func (b *SimulatedBackend) GetBlockChain() *core.BlockChain { + return b.blockchain +} + // callmsg implements core.Message to allow passing it as a transaction simulator. type callmsg struct { ethereum.CallMsg diff --git a/consensus/XDPoS/XDPoS.go b/consensus/XDPoS/XDPoS.go index 78ee4b87a2..e370c86d62 100644 --- a/consensus/XDPoS/XDPoS.go +++ b/consensus/XDPoS/XDPoS.go @@ -264,6 +264,37 @@ func New(config *params.XDPoSConfig, db ethdb.Database) *XDPoS { } } +var engine *XDPoS + +// NewFullFaker creates an ethash consensus engine with a full fake scheme that +// accepts all blocks as valid, without checking any consensus rules whatsoever. +func NewFaker(db ethdb.Database) *XDPoS { + // Set any missing consensus parameters to their defaults + conf := params.TestXDPoSMockChainConfig.XDPoS + + // Allocate the snapshot caches and create the engine + BlockSigners, _ := lru.New(blockSignersCacheLimit) + recents, _ := lru.NewARC(inmemorySnapshots) + signatures, _ := lru.NewARC(inmemorySnapshots) + validatorSignatures, _ := lru.NewARC(inmemorySnapshots) + verifiedHeaders, _ := lru.NewARC(inmemorySnapshots) + engine = &XDPoS{ + config: conf, + db: db, + BlockSigners: BlockSigners, + recents: recents, + signatures: signatures, + verifiedHeaders: verifiedHeaders, + validatorSignatures: validatorSignatures, + proposals: make(map[common.Address]bool), + } + return engine +} + +func GetFaker() *XDPoS { + return engine +} + // Author implements consensus.Engine, returning the Ethereum address recovered // from the signature in the header's extra-data section. func (c *XDPoS) Author(header *types.Header) (common.Address, error) { @@ -313,6 +344,10 @@ func (c *XDPoS) verifyHeaderWithCache(chain consensus.ChainReader, header *types // looking those up from the database. This is useful for concurrently verifying // a batch of new headers. func (c *XDPoS) verifyHeader(chain consensus.ChainReader, header *types.Header, parents []*types.Header, fullVerify bool) error { + // If we're running a engine faking, accept any block as valid + if c.config.SkipValidation { + return nil + } if common.IsTestnet { fullVerify = false } @@ -1027,6 +1062,10 @@ func (c *XDPoS) CalcDifficulty(chain consensus.ChainReader, time uint64, parent } func (c *XDPoS) calcDifficulty(chain consensus.ChainReader, parent *types.Header, signer common.Address) *big.Int { + // If we're running a engine faking, skip calculation + if c.config.SkipValidation { + return big.NewInt(1) + } len, preIndex, curIndex, _, err := c.YourTurn(chain, parent, signer) if err != nil { return big.NewInt(int64(len + curIndex - preIndex)) diff --git a/core/blockchain.go b/core/blockchain.go index 858fed3c90..e3b3c1b1e8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -151,7 +151,7 @@ type BlockChain struct { badBlocks *lru.Cache // Bad block cache shouldPreserve func(*types.Block) bool // Function used to determine whether should preserve the given block. IPCEndpoint string - Client *ethclient.Client // Global ipc client instance. + Client bind.ContractBackend // Global ipc client instance. } // NewBlockChain returns a fully initialised block chain using information @@ -1067,6 +1067,13 @@ func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types. // Set new head. if status == CanonStatTy { bc.insert(block) + // prepare set of masternodes for the next epoch + if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap) { + err := bc.UpdateM1() + if err != nil { + log.Crit("Error when update masternodes set. Stopping node", "err", err) + } + } } // save cache BlockSigners if bc.chainConfig.XDPoS != nil && bc.chainConfig.IsTIPSigning(block.Number()) { @@ -1283,13 +1290,6 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, [] if (chain[it.index].NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 { CheckpointCh <- 1 } - // prepare set of masternodes for the next epoch - if (chain[it.index].NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap) { - err := bc.UpdateM1() - if err != nil { - log.Crit("Error when update masternodes set. Stopping node", "err", err) - } - } } } // Any blocks remaining here? The only ones we care about are the future ones @@ -1503,14 +1503,6 @@ func (bc *BlockChain) insertBlock(block *types.Block) ([]interface{}, []*types.L if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == 0 { CheckpointCh <- 1 } - // prepare set of masternodes for the next epoch - if (block.NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap) { - err := bc.UpdateM1() - if err != nil { - log.Error("Error when update masternodes set. Stopping node", "err", err) - os.Exit(1) - } - } } // Append a single chain head event if we've progressed the chain if status == CanonStatTy && bc.CurrentBlock().Hash() == block.Hash() { @@ -1738,6 +1730,13 @@ func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error { // Write lookup entries for hash based transaction/receipt searches rawdb.WriteTxLookupEntries(bc.db, newChain[i]) addedTxs = append(addedTxs, newChain[i].Transactions()...) + // prepare set of masternodes for the next epoch + if (newChain[i].NumberU64() % bc.chainConfig.XDPoS.Epoch) == (bc.chainConfig.XDPoS.Epoch - bc.chainConfig.XDPoS.Gap) { + err := bc.UpdateM1() + if err != nil { + log.Crit("Error when update masternodes set. Stopping node", "err", err) + } + } } // When transactions get deleted from the database, the receipts that were // created in the fork must also be deleted @@ -1989,7 +1988,7 @@ func (bc *BlockChain) SubscribeLogsEvent(ch chan<- []*types.Log) event.Subscript } // Get current IPC Client. -func (bc *BlockChain) GetClient() (*ethclient.Client, error) { +func (bc *BlockChain) GetClient() (bind.ContractBackend, error) { if bc.Client == nil { // Inject ipc client global instance. client, err := ethclient.Dial(bc.IPCEndpoint) diff --git a/eth/api_backend.go b/eth/api_backend.go index bde68c1c84..43410b4eb5 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -24,6 +24,7 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" @@ -34,7 +35,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" @@ -233,7 +233,7 @@ func (b *EthAPIBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma } } -func (b *EthAPIBackend) GetIPCClient() (*ethclient.Client, error) { +func (b *EthAPIBackend) GetIPCClient() (bind.ContractBackend, error) { client, err := b.eth.blockchain.GetClient() if err != nil { return nil, err diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 5792e5f200..a1049c04be 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -22,6 +22,7 @@ import ( "math/big" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core" @@ -29,7 +30,6 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/params" @@ -72,7 +72,7 @@ type Backend interface { ChainConfig() *params.ChainConfig CurrentBlock() *types.Block - GetIPCClient() (*ethclient.Client, error) + GetIPCClient() (bind.ContractBackend, error) GetEngine() consensus.Engine GetRewardByHash(hash common.Hash) map[string]interface{} } diff --git a/les/api_backend.go b/les/api_backend.go index e8aa40c49f..a67e661b58 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -24,6 +24,7 @@ import ( "path/filepath" "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/consensus" @@ -35,7 +36,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/eth/downloader" "github.com/ethereum/go-ethereum/eth/gasprice" - "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/light" @@ -210,7 +210,7 @@ func (b *LesApiBackend) ServiceFilter(ctx context.Context, session *bloombits.Ma } } -func (b *LesApiBackend) GetIPCClient() (*ethclient.Client, error) { +func (b *LesApiBackend) GetIPCClient() (bind.ContractBackend, error) { return nil, nil } diff --git a/params/config.go b/params/config.go index a4c23d3a93..8418201c5d 100644 --- a/params/config.go +++ b/params/config.go @@ -159,9 +159,10 @@ var ( AllXDPoSProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &XDPoSConfig{Period: 0, Epoch: 30000}} AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}, nil} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil} - TestRules = TestChainConfig.Rules(new(big.Int)) - TestXDPoSChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &XDPoSConfig{Period: 2, Epoch: 900, Reward: 250, RewardCheckpoint: 900, Gap: 890, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068")}} + TestXDPoSMockChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &XDPoSConfig{Epoch: 900, Gap: 890, SkipValidation: true}} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, nil} + TestRules = TestChainConfig.Rules(new(big.Int)) + TestXDPoSChainConfig = &ChainConfig{big.NewInt(1337), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, nil, nil, nil, &XDPoSConfig{Period: 2, Epoch: 900, Reward: 250, RewardCheckpoint: 900, Gap: 890, FoudationWalletAddr: common.HexToAddress("0x0000000000000000000000000000000000000068")}} ) // TrustedCheckpoint represents a set of post-processed trie roots (CHT and @@ -234,6 +235,7 @@ type XDPoSConfig struct { RewardCheckpoint uint64 `json:"rewardCheckpoint"` // Checkpoint block for calculate rewards. Gap uint64 `json:"gap"` // Gap time preparing for the next epoch FoudationWalletAddr common.Address `json:"foudationWalletAddr"` // Foundation Address Wallet + SkipValidation bool //Skip Block Validation for testing purpose } // String implements the stringer interface, returning the consensus engine details. diff --git a/tests/end_to_end/block_signer_test.go b/tests/end_to_end/block_signer_test.go new file mode 100644 index 0000000000..0c75568b1c --- /dev/null +++ b/tests/end_to_end/block_signer_test.go @@ -0,0 +1,483 @@ +package end_to_end + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "math/big" + "strings" + "testing" + "time" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/XDPoS" + contractValidator "github.com/ethereum/go-ethereum/contracts/validator/contract" + "github.com/ethereum/go-ethereum/core" + . "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" +) + +type masterNodes map[string]big.Int +type signersList map[string]bool + +var ( + acc1Key, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") + acc2Key, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") + acc3Key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + voterKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee04aefe388d1e14474d32c45c72ce7b7a") + acc1Addr = crypto.PubkeyToAddress(acc1Key.PublicKey) //xdc703c4b2bD70c169f5717101CaeE543299Fc946C7 + acc2Addr = crypto.PubkeyToAddress(acc2Key.PublicKey) //xdc0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e + acc3Addr = crypto.PubkeyToAddress(acc3Key.PublicKey) //xdc71562b71999873DB5b286dF957af199Ec94617F7 + voterAddr = crypto.PubkeyToAddress(voterKey.PublicKey) //xdc5F74529C0338546f82389402a01c31fB52c6f434 + chainID = int64(1337) +) + +func debugMessage(backend *backends.SimulatedBackend, signers signersList, t *testing.T) { + ms := GetCandidateFromCurrentSmartContract(backend, t) + fmt.Println("=== current smart contract") + for nodeAddr, cap := range ms { + if !strings.Contains(nodeAddr, "000000000000000000000000000000000000") { //remove defaults + fmt.Println(nodeAddr, cap) + } + } + fmt.Println("=== this block signer list") + for signer := range signers { + if !strings.Contains(signer, "000000000000000000000000000000000000") { //remove defaults + fmt.Println(signer) + } + } +} + +func getCommonBackend(t *testing.T) *backends.SimulatedBackend { + + // initial helper backend + contractBackendForSC := backends.NewXDCSimulatedBackend(core.GenesisAlloc{ + voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, + }) + + transactOpts := bind.NewKeyedTransactor(voterKey) + + var candidates []common.Address + var caps []*big.Int + defalutCap := new(big.Int) + defalutCap.SetString("1000000000", 10) + + for i := 1; i <= 16; i++ { + addr := fmt.Sprintf("%02d", i) + candidates = append(candidates, common.StringToAddress(addr)) + caps = append(caps, defalutCap) + } + + acc1Cap, acc2Cap, acc3Cap, voterCap := new(big.Int), new(big.Int), new(big.Int), new(big.Int) + + acc1Cap.SetString("10000001", 10) + acc2Cap.SetString("10000002", 10) + acc3Cap.SetString("10000003", 10) + voterCap.SetString("1000000000", 10) + + caps = append(caps, voterCap, acc1Cap, acc2Cap, acc3Cap) + candidates = append(candidates, voterAddr, acc1Addr, acc2Addr, acc3Addr) + // create validator smart contract + validatorSCAddr, _, _, err := contractValidator.DeployXDCValidator( + transactOpts, + contractBackendForSC, + candidates, + caps, + voterAddr, // first owner, not used + big.NewInt(50000), + big.NewInt(1), + big.NewInt(99), + big.NewInt(100), + big.NewInt(100), + ) + if err != nil { + t.Fatalf("can't deploy root registry: %v", err) + } + + contractBackendForSC.Commit() // Write into database(state) + + // Prepare Code and Storage + d := time.Now().Add(1000 * time.Millisecond) + ctx, cancel := context.WithDeadline(context.Background(), d) + defer cancel() + + code, _ := contractBackendForSC.CodeAt(ctx, validatorSCAddr, nil) + storage := make(map[common.Hash]common.Hash) + f := func(key, val common.Hash) bool { + decode := []byte{} + trim := bytes.TrimLeft(val.Bytes(), "\x00") + rlp.DecodeBytes(trim, &decode) + storage[key] = common.BytesToHash(decode) + log.Info("DecodeBytes", "value", val.String(), "decode", storage[key].String()) + return true + } + contractBackendForSC.ForEachStorageAt(ctx, validatorSCAddr, nil, f) + + // create test backend with smart contract in it + contractBackend2 := backends.NewXDCSimulatedBackend(core.GenesisAlloc{ + acc1Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + acc2Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + acc3Addr: {Balance: new(big.Int).SetUint64(10000000000)}, + voterAddr: {Balance: new(big.Int).SetUint64(10000000000)}, + common.HexToAddress(common.MasternodeVotingSMC): {Balance: new(big.Int).SetUint64(1), Code: code, Storage: storage}, // Binding the MasternodeVotingSMC with newly created 'code' for SC execution + }) + + return contractBackend2 + +} + +/* +func transferTx(t *testing.T) *types.Transaction { + data := []byte{} + gasPrice := big.NewInt(int64(1)) + gasLimit := uint64(21000) + amount := big.NewInt(int64(999)) + nonce := uint64(0) + to := common.HexToAddress("35658f7b2a9E7701e65E7a654659eb1C481d1dC5") + tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) + signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), acc4Key) + if err != nil { + t.Fatal(err) + } + return signedTX +} +func proposeTX(t *testing.T) *types.Transaction { + data := common.Hex2Bytes("012679510000000000000000000000000d3ab14bbad3d99f4203bd7a11acb94882050e7e") + //data := []byte{} + fmt.Println("data", string(data[:])) + gasPrice := big.NewInt(int64(0)) + gasLimit := uint64(22680) + amountInt := new(big.Int) + amount, ok := amountInt.SetString("11000000000000000000000000", 10) + if !ok { + t.Fatal("big int init failed") + } + nonce := uint64(0) + to := common.HexToAddress("xdc35658f7b2a9e7701e65e7a654659eb1c481d1dc5") + tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) + signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), acc4Key) + if err != nil { + t.Fatal(err) + } + return signedTX +} +*/ + +func voteTX(gasLimit uint64, nonce uint64, addr string) (*types.Transaction, error) { + vote := "6dd7d8ea" // VoteMethod = "0x6dd7d8ea" + action := fmt.Sprintf("%s%s%s", vote, "000000000000000000000000", addr[3:]) + data := common.Hex2Bytes(action) + gasPrice := big.NewInt(int64(0)) + amountInt := new(big.Int) + amount, ok := amountInt.SetString("60000", 10) + if !ok { + return nil, fmt.Errorf("big int init failed") + } + to := common.HexToAddress(common.MasternodeVotingSMC) + tx := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, data) + + signedTX, err := types.SignTx(tx, types.NewEIP155Signer(big.NewInt(chainID)), voterKey) + if err != nil { + return nil, err + } + + return signedTX, nil +} +func UpdateSigner(bc *BlockChain) error { + err := bc.UpdateM1() + return err +} + +func GetSnapshotSigner(bc *BlockChain, header *types.Header) (signersList, error) { + engine := bc.Engine().(*XDPoS.XDPoS) + snap, err := engine.GetSnapshot(bc, header) + if err != nil { + return nil, err + + } + ms := make(signersList) + + for addr := range snap.Signers { + ms[addr.Hex()] = true + } + return ms, nil + +} + +func GetCandidateFromCurrentSmartContract(backend bind.ContractBackend, t *testing.T) masterNodes { + addr := common.HexToAddress(common.MasternodeVotingSMC) + validator, err := contractValidator.NewXDCValidator(addr, backend) + if err != nil { + t.Fatal(err) + } + + opts := new(bind.CallOpts) + candidates, err := validator.GetCandidates(opts) + if err != nil { + t.Fatal(err) + } + + ms := make(masterNodes) + for _, candidate := range candidates { + v, err := validator.GetCandidateCap(opts, candidate) + if err != nil { + t.Fatal(err) + } + ms[candidate.String()] = *v + } + return ms +} + +func PrepareXDCTestBlockChain(t *testing.T) (*BlockChain, *backends.SimulatedBackend, *types.Block) { + // Preparation + var err error + backend := getCommonBackend(t) + blockchain := backend.GetBlockChain() + blockchain.Client = backend + + currentBlock := blockchain.Genesis() + + // Insert initial 9 blocks + for i := 1; i <= 9; i++ { + blockCoinBase := fmt.Sprintf("0x111000000000000000000000000000000%03d", i) + merkleRoot := "35999dded35e8db12de7e6c1471eb9670c162eec616ecebbaf4fddd4676fb930" + block, err := insertBlock(blockchain, i, blockCoinBase, currentBlock, merkleRoot) + if err != nil { + t.Fatal(err) + } + currentBlock = block + } + // Update Signer as there is no previous signer assigned + err = UpdateSigner(blockchain) + if err != nil { + t.Fatal(err) + } + + return blockchain, backend, currentBlock +} + +//Should call updateM1 at gap block, and update the snapshot if there are SM transactions involved +func TestCallUpdateM1WithSmartContractTranscation(t *testing.T) { + + blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t) + // Insert first Block 10 A + t.Logf("Inserting block with propose at 10 A...") + blockCoinbaseA := "0xaaa0000000000000000000000000000000000010" + tx, err := voteTX(37117, 0, acc1Addr.String()) + if err != nil { + t.Fatal(err) + } + + //Get from block validator error message + merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" + blockA, err := insertBlockTxs(blockchain, 10, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot) + if err != nil { + t.Fatal(err) + } + + signers, err := GetSnapshotSigner(blockchain, blockA.Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc1Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("account 1 should sit in the signer list") + } +} + +// Should have previous smart contract transcation, then have 2 blocks at Gap Step. +// Both Block should have update signer whenever they be added into main chain. + +func Test1(t *testing.T) { +} + +// Should call updateM1 and update snapshot when a forked block(at gap block number) is inserted back into main chain (Edge case) +func TestCallUpdateM1WhenForkedBlockBackToMainChain(t *testing.T) { + + blockchain, backend, currentBlock := PrepareXDCTestBlockChain(t) + + // Check initial signer + signers, err := GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc3Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("voterAddr should sit in the signer list") + } + + // Insert first Block 10 A + t.Logf("Inserting block with propose at 10 A...") + blockCoinbaseA := "0xaaa0000000000000000000000000000000000010" + tx, err := voteTX(37117, 0, acc1Addr.String()) + if err != nil { + t.Fatal(err) + } + + merkleRoot := "46234e9cd7e85a267f7f0435b15256a794a2f6d65cc98cdbd21dcd10a01d9772" + blockA, err := insertBlockTxs(blockchain, 10, blockCoinbaseA, currentBlock, []*types.Transaction{tx}, merkleRoot) + if err != nil { + t.Fatal(err) + } + + signers, err = GetSnapshotSigner(blockchain, blockA.Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc1Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("account 1 should sit in the signer list") + } + + // Insert forked Block 10 B + t.Logf("Inserting block with propose at 10 B...") + + blockCoinBase10B := "0xbbb0000000000000000000000000000000000010" + tx, err = voteTX(37117, 0, acc2Addr.String()) + if err != nil { + t.Fatal(err) + } + + merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" + block10B, err := insertBlockTxs(blockchain, 10, blockCoinBase10B, currentBlock, []*types.Transaction{tx}, merkleRoot) + if err != nil { + t.Fatal(err) + } + signers, err = GetSnapshotSigner(blockchain, block10B.Header()) + if err != nil { + t.Fatal(err) + } + // Should not run the `updateM1` for forked chain, hence account3 still exit + if signers[acc3Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("account 3 should sit in the signer list as previos block result") + } + + //Insert block 11 parent is 11 B + t.Logf("Inserting block with propose at 11 B...") + + blockCoinBase11B := "0xbbb0000000000000000000000000000000000011" + merkleRoot = "068dfa09d7b4093441c0cc4d9807a71bc586f6101c072d939b214c21cd136eb3" + block11B, err := insertBlock(blockchain, 11, blockCoinBase11B, block10B, merkleRoot) + + if err != nil { + t.Fatal(err) + } + + signers, err = GetSnapshotSigner(blockchain, block10B.Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc2Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("account 2 should sit in the signer list") + } + + signers, err = GetSnapshotSigner(blockchain, block11B.Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc2Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("account 2 should sit in the signer list") + } + + signers, err = GetSnapshotSigner(blockchain, blockchain.CurrentBlock().Header()) + if err != nil { + t.Fatal(err) + } + if signers[acc2Addr.Hex()] != true { + debugMessage(backend, signers, t) + t.Fatalf("acc2Addr should sit in the signer list") + } +} + +// insert Block without transcation attached +func insertBlock(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, root string) (*types.Block, error) { + block, err := createXDPoSTestBlock( + blockchain, + parentBlock.Hash().Hex(), + blockCoinBase, blockNum, nil, + "56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + common.HexToHash(root), + ) + if err != nil { + return nil, err + } + + err = blockchain.InsertBlock(block) + if err != nil { + return nil, err + } + return block, nil +} + +// insert Block with transcation attached +func insertBlockTxs(blockchain *BlockChain, blockNum int, blockCoinBase string, parentBlock *types.Block, txs []*types.Transaction, root string) (*types.Block, error) { + block, err := createXDPoSTestBlock( + blockchain, + parentBlock.Hash().Hex(), + blockCoinBase, blockNum, txs, + "9319777b782ba2c83a33c995481ff894ac96d9a92a1963091346a3e1e386705c", + common.HexToHash(root), + ) + if err != nil { + return nil, err + } + + err = blockchain.InsertBlock(block) + if err != nil { + return nil, err + } + return block, nil +} + +func createXDPoSTestBlock(bc *BlockChain, parentHash, coinbase string, number int, txs []*types.Transaction, receiptHash string, root common.Hash) (*types.Block, error) { + extraSubstring := "d7830100018358444388676f312e31342e31856c696e75780000000000000000b185dc0d0e917d18e5dbf0746be6597d3331dd27ea0554e6db433feb2e81730b20b2807d33a1527bf43cd3bc057aa7f641609c2551ebe2fd575f4db704fbf38101" // Grabbed from existing mainnet block, it does not have any meaning except for the length validation + //ReceiptHash = "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + //Root := "0xc99c095e53ff1afe3b86750affd13c7550a2d24d51fb8e41b3c3ef2ea8274bcc" + extraByte, _ := hex.DecodeString(extraSubstring) + header := types.Header{ + ParentHash: common.HexToHash(parentHash), + UncleHash: types.EmptyUncleHash, + TxHash: types.EmptyRootHash, + ReceiptHash: types.EmptyRootHash, + Root: root, + Coinbase: common.HexToAddress(coinbase), + Difficulty: big.NewInt(int64(1)), + Number: big.NewInt(int64(number)), + GasLimit: 1200000000, + Time: big.NewInt(int64(number * 10)), + Extra: extraByte, + } + + var block *types.Block + if len(txs) == 0 { + block = types.NewBlockWithHeader(&header) + } else { + + // Prepare Receipt + statedb, err := bc.StateAt(bc.GetBlockByNumber(uint64(number - 1)).Root()) //Get parent root + if err != nil { + return nil, fmt.Errorf("%v when get state", err) + } + gp := new(GasPool).AddGas(header.GasLimit) + usedGas := uint64(0) + statedb.Prepare(txs[0].Hash(), header.Hash(), 0) + receipt, _, err := ApplyTransaction(bc.Config(), bc, &header.Coinbase, gp, statedb, &header, txs[0], &usedGas, vm.Config{}) + if err != nil { + return nil, fmt.Errorf("%v when creating block", err) + } + header.GasUsed = txs[0].Gas() + block = types.NewBlock(&header, txs, nil, []*types.Receipt{receipt}) + } + + return block, nil +}