From b2b45eec0d6dad2cf8a5a27c97cb5e5841374bf6 Mon Sep 17 00:00:00 2001 From: Guilherme Ferreira Date: Fri, 16 Feb 2024 14:44:00 -0300 Subject: [PATCH] fix quaiclient tests build errors and create a test chain --- consensus/progpow/consensus.go | 3 +- consensus/progpow/progpow.go | 6 +- core/chain_makers.go | 22 ++- core/core.go | 25 ++- core/slice.go | 42 +++- core/types/block.go | 59 ++++++ quai/backend.go | 68 +++++-- quaiclient/ethclient/ethclient_test.go | 262 +++++++------------------ quaiclient/quaiclient.go | 52 ++++- quaiclient/quaiclient_test.go | 138 +++++++++++++ 10 files changed, 448 insertions(+), 229 deletions(-) create mode 100644 quaiclient/quaiclient_test.go diff --git a/consensus/progpow/consensus.go b/consensus/progpow/consensus.go index 08b4b964de..67df492cc2 100644 --- a/consensus/progpow/consensus.go +++ b/consensus/progpow/consensus.go @@ -428,7 +428,8 @@ func (progpow *Progpow) verifySeal(header *types.Header) (common.Hash, error) { if progpow.fakeFail == header.NumberU64(nodeCtx) { return common.Hash{}, errInvalidPoW } - return common.Hash{}, nil + //if hash is empty here, it fails because of div / 0 on poem.go: IntrinsicLogS() + return common.HexToHash("0xf5d8c9fb1a61e47c6dd4b5d0a1a0d6c0f7bce9cfae0e2a9d8a9c8d6d6f8f4f7"), nil } // If we're running a shared PoW, delegate verification to it if progpow.shared != nil { diff --git a/consensus/progpow/progpow.go b/consensus/progpow/progpow.go index ddff0a8e09..b8a268ed1c 100644 --- a/consensus/progpow/progpow.go +++ b/consensus/progpow/progpow.go @@ -219,11 +219,13 @@ func NewTester(notify []string, noverify bool) *Progpow { // NewFaker creates a progpow consensus engine with a fake PoW scheme that accepts // all blocks' seal as valid, though they still have to conform to the Quai // consensus rules. -func NewFaker() *Progpow { +func NewFaker(logger *log.Logger, nodeLocation common.Location) *Progpow { return &Progpow{ config: Config{ - PowMode: ModeFake, + PowMode: ModeFake, + NodeLocation: nodeLocation, }, + logger: logger, } } diff --git a/core/chain_makers.go b/core/chain_makers.go index 922cdd7922..af21af904c 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -23,11 +23,13 @@ import ( "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/consensus" "github.com/dominant-strategies/go-quai/consensus/misc" + "github.com/dominant-strategies/go-quai/core/rawdb" "github.com/dominant-strategies/go-quai/core/state" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/core/vm" "github.com/dominant-strategies/go-quai/ethdb" "github.com/dominant-strategies/go-quai/params" + "github.com/dominant-strategies/go-quai/trie" ) // BlockGen creates blocks for testing. @@ -215,6 +217,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse chainreader := &fakeChainReader{config: config} genblock := func(i int, parent *types.Block, statedb *state.StateDB) (*types.Block, types.Receipts) { b := &BlockGen{i: i, chain: blocks, parent: parent, statedb: statedb, config: config, engine: engine} + b.subManifest = types.BlockManifest{parent.Hash()} b.header = makeHeader(chainreader, parent, statedb, b.engine) // Execute any user modifications to the block @@ -243,6 +246,7 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse panic(err) } block, receipt := genblock(i, parent, statedb) + rawdb.WriteBlock(db, block, config.Location.Context()) blocks[i] = block receipts[i] = receipt parent = block @@ -257,25 +261,25 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S } else { time = parent.Time() + 10 // block time is fixed at 10 seconds } - nodeCtx := chain.Config().Location.Context() - - // Temporary header values just to calc difficulty - diffheader := types.EmptyHeader() - diffheader.SetDifficulty(parent.Difficulty(nodeCtx)) - diffheader.SetNumber(parent.Number(nodeCtx), nodeCtx) - diffheader.SetTime(time - 10) - diffheader.SetUncleHash(parent.UncleHash()) + nodeLoc := chain.Config().Location + nodeCtx := nodeLoc.Context() // Make new header header := types.EmptyHeader() header.SetRoot(state.IntermediateRoot(true)) header.SetParentHash(parent.Hash(), nodeCtx) header.SetCoinbase(parent.Coinbase()) - header.SetDifficulty(engine.CalcDifficulty(chain, diffheader)) + header.SetDifficulty(engine.CalcDifficulty(chain, parent.Header())) header.SetGasLimit(parent.GasLimit()) header.SetNumber(new(big.Int).Add(parent.Number(nodeCtx), common.Big1), nodeCtx) header.SetTime(time) header.SetBaseFee(misc.CalcBaseFee(chain.Config(), parent.Header())) + + header.SetLocation(nodeLoc) + + manifest := types.BlockManifest{parent.Hash()} + header.SetManifestHash(types.DeriveSha(manifest, trie.NewStackTrie(nil)), nodeCtx) + return header } diff --git a/core/core.go b/core/core.go index 4eebe259d9..06b56928b1 100644 --- a/core/core.go +++ b/core/core.go @@ -81,12 +81,18 @@ type Core struct { logger *log.Logger } +type NewCoreFunction func(db ethdb.Database, config *Config, isLocalBlock func(block *types.Header) bool, txConfig *TxPoolConfig, txLookupLimit *uint64, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, logger *log.Logger) (*Core, error) + func NewCore(db ethdb.Database, config *Config, isLocalBlock func(block *types.Header) bool, txConfig *TxPoolConfig, txLookupLimit *uint64, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, logger *log.Logger) (*Core, error) { slice, err := NewSlice(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, vmConfig, genesis, logger) if err != nil { return nil, err } + return newCommonCore(slice, engine, logger) +} + +func newCommonCore(slice *Slice, engine consensus.Engine, logger *log.Logger) (*Core, error) { c := &Core{ sl: slice, engine: engine, @@ -99,6 +105,23 @@ func NewCore(db ethdb.Database, config *Config, isLocalBlock func(block *types.H // Initialize the sync target to current header parent entropy c.syncTarget = c.CurrentHeader() + c.AppendQueueProcessCache() + + return c, nil +} + +// Used on unit testing +func NewFakeCore(db ethdb.Database, config *Config, isLocalBlock func(block *types.Header) bool, txConfig *TxPoolConfig, txLookupLimit *uint64, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, logger *log.Logger) (*Core, error) { + slice, err := NewFakeSlice(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, vmConfig, genesis, logger) + + if err != nil { + return nil, err + } + + return newCommonCore(slice, engine, logger) +} + +func (c *Core) AppendQueueProcessCache() { appendQueue, _ := lru.New(c_maxAppendQueue) c.appendQueue = appendQueue @@ -111,8 +134,6 @@ func NewCore(db ethdb.Database, config *Config, isLocalBlock func(block *types.H go c.updateAppendQueue() go c.startStatsTimer() go c.checkSyncTarget() - - return c, nil } // InsertChain attempts to append a list of blocks to the slice, optionally diff --git a/core/slice.go b/core/slice.go index f2ce24bdaa..1fe02975da 100644 --- a/core/slice.go +++ b/core/slice.go @@ -88,6 +88,23 @@ type Slice struct { func NewSlice(db ethdb.Database, config *Config, txConfig *TxPoolConfig, txLookupLimit *uint64, isLocalBlock func(block *types.Header) bool, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, logger *log.Logger) (*Slice, error) { nodeCtx := chainConfig.Location.Context() + sl, err := newSliceCommon(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, vmConfig, genesis, nodeCtx, logger) + + if err != nil { + return nil, err + } + + // only set domClient if the chain is not Prime. + if nodeCtx != common.PRIME_CTX { + go func() { + sl.domClient = makeDomClient(domClientUrl, logger) + }() + } + + return sl, nil +} + +func newSliceCommon(db ethdb.Database, config *Config, txConfig *TxPoolConfig, txLookupLimit *uint64, isLocalBlock func(block *types.Header) bool, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, nodeCtx int, logger *log.Logger) (*Slice, error) { sl := &Slice{ config: chainConfig, engine: engine, @@ -126,13 +143,6 @@ func NewSlice(db ethdb.Database, config *Config, txConfig *TxPoolConfig, txLooku }() } - // only set domClient if the chain is not Prime. - if nodeCtx != common.PRIME_CTX { - go func() { - sl.domClient = makeDomClient(domClientUrl, sl.logger) - }() - } - if err := sl.init(genesis); err != nil { return nil, err } @@ -146,6 +156,24 @@ func NewSlice(db ethdb.Database, config *Config, txConfig *TxPoolConfig, txLooku return sl, nil } +func NewFakeSlice(db ethdb.Database, config *Config, txConfig *TxPoolConfig, txLookupLimit *uint64, isLocalBlock func(block *types.Header) bool, chainConfig *params.ChainConfig, slicesRunning []common.Location, domClientUrl string, subClientUrls []string, engine consensus.Engine, cacheConfig *CacheConfig, vmConfig vm.Config, genesis *Genesis, logger *log.Logger) (*Slice, error) { + nodeCtx := chainConfig.Location.Context() + sl, err := newSliceCommon(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, vmConfig, genesis, nodeCtx, logger) + + if err != nil { + return nil, err + } + + // only set domClient if the chain is not Prime. + if nodeCtx != common.PRIME_CTX { + go func() { + sl.domClient = quaiclient.NewClient(&quaiclient.TestRpcClient{}) + }() + } + + return sl, nil +} + // Append takes a proposed header and constructs a local block and attempts to hierarchically append it to the block graph. // If this is called from a dominant context a domTerminus must be provided else a common.Hash{} should be used and domOrigin should be set to true. // Return of this function is the Etxs generated in the Zone Block, subReorg bool that tells dom if should be mined on, setHead bool that determines if we should set the block as the current head and the error diff --git a/core/types/block.go b/core/types/block.go index 1152695a94..2a10abf6ff 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -111,6 +111,65 @@ type Header struct { PowDigest atomic.Value } +func (h1 *Header) Compare(h2 *Header) error{ + if h1 == nil || h2 == nil { + if h1 == h2{ + return nil + } + return fmt.Errorf("Headers are not equal expected %v, got %v", h1, h2) + } + + fields := map[string][]interface{}{ + "parentHash": {h1.parentHash, h2.parentHash}, + "uncleHash": {h1.uncleHash, h2.uncleHash}, + "coinbase": {h1.coinbase, h2.coinbase}, + "root": {h1.root, h2.root}, + "txHash": {h1.txHash, h2.txHash}, + "etxHash": {h1.etxHash, h2.etxHash}, + "etxRollupHash": {h1.etxRollupHash, h2.etxRollupHash}, + "manifestHash": {h1.manifestHash, h2.manifestHash}, + "receiptHash": {h1.receiptHash, h2.receiptHash}, + "difficulty": {h1.difficulty, h2.difficulty}, + "number": {h1.number, h2.number}, + "gasLimit": {h1.gasLimit, h2.gasLimit}, + "gasUsed": {h1.gasUsed, h2.gasUsed}, + "baseFee": {h1.baseFee, h2.baseFee}, + "location": {h1.location, h2.location}, + "time": {h1.time, h2.time}, + "extra": {h1.extra, h2.extra}, + "mixHash": {h1.mixHash, h2.mixHash}, + "nonce": {h1.nonce, h2.nonce}, + "hash": {h1.hash, h2.hash}, + "sealHash": {h1.sealHash, h2.sealHash}, + "PowHash": {h1.PowHash, h2.PowHash}, + "PowDigest": {h1.PowDigest, h2.PowDigest}, + } + + for fieldName, values := range fields { + if !reflect.DeepEqual(values[0], values[1]) { + return fmt.Errorf("Field %s is not equal expected %v, got %v", fieldName, values[0], values[1]) + } + } + + if len(h1.parentEntropy) != len(h2.parentEntropy) { + return fmt.Errorf("Field parentEntropy is not equal expected %v, got %v", h1.parentEntropy, h2.parentEntropy) + } + for i := range h1.parentEntropy { + if h1.parentEntropy[i].Cmp(h2.parentEntropy[i]) != 0 { + return fmt.Errorf("Field parentEntropy at index %d is not equal expected %v, got %v", i, h1.parentEntropy[i], h2.parentEntropy[i]) + } + } + if len(h1.parentDeltaS) != len(h2.parentDeltaS) { + return fmt.Errorf("Field parentEntropy is not equal expected %v, got %v", h1.parentEntropy, h2.parentEntropy) + } + for i := range h1.parentDeltaS { + if h1.parentEntropy[i].Cmp(h2.parentDeltaS[i]) != 0 { + return fmt.Errorf("Field parentDeltaS at index %d is not equal expected %v, got %v", i, h1.parentDeltaS[i], h2.parentDeltaS[i]) + } + } + return nil +} + // field type overrides for gencodec type headerMarshaling struct { Difficulty *hexutil.Big diff --git a/quai/backend.go b/quai/backend.go index 74509d629f..1f02a57204 100644 --- a/quai/backend.go +++ b/quai/backend.go @@ -81,6 +81,33 @@ type Quai struct { // New creates a new Quai object (including the // initialisation of the common Quai object) func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx int, logger *log.Logger) (*Quai, error) { + chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) + + if err != nil { + return nil, err + } + + quai, err := newCommon(stack, p2p, config, nodeCtx, logger, chainDb, core.NewCore) + + // Set the p2p Networking API + quai.p2p = p2p + // Subscribe to the Blocks subscription + quai.p2p.Subscribe(config.NodeLocation, &types.Block{}) + quai.p2p.Subscribe(config.NodeLocation, common.Hash{}) + quai.p2p.Subscribe(config.NodeLocation, &types.Transaction{}) + + quai.handler = newHandler(quai.p2p, quai.core, config.NodeLocation) + // Start the handler + quai.handler.Start() + + if err != nil { + return nil, err + } + + return quai, nil +} + +func newCommon(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx int, logger *log.Logger, chainDb ethdb.Database, coreFunction core.NewCoreFunction) (*Quai, error) { // Ensure configuration values are compatible and sane if config.Miner.GasPrice == nil || config.Miner.GasPrice.Cmp(common.Big0) <= 0 { logger.WithFields(log.Fields{ @@ -104,10 +131,6 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx }).Info("Allocated trie memory caches") // Assemble the Quai object - chainDb, err := stack.OpenDatabaseWithFreezer("chaindata", config.DatabaseCache, config.DatabaseHandles, config.DatabaseFreezer, "eth/db/chaindata/", false) - if err != nil { - return nil, err - } chainConfig, _, genesisErr := core.SetupGenesisBlockWithOverride(chainDb, config.Genesis, config.NodeLocation, logger) if genesisErr != nil { return nil, genesisErr @@ -186,6 +209,11 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx rawdb.WriteDatabaseVersion(chainDb, core.BlockChainVersion) } } + + if config.TxPool.Journal != "" { + config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) + } + var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, @@ -202,12 +230,10 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx } ) - if config.TxPool.Journal != "" { - config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal) - } + var err error logger.WithField("url", quai.config.DomUrl).Info("Dom client") - quai.core, err = core.NewCore(chainDb, &config.Miner, quai.isLocalBlock, &config.TxPool, &config.TxLookupLimit, chainConfig, quai.config.SlicesRunning, quai.config.DomUrl, quai.config.SubUrls, quai.engine, cacheConfig, vmConfig, config.Genesis, logger) + quai.core, err = coreFunction(chainDb, &config.Miner, quai.isLocalBlock, &config.TxPool, &config.TxLookupLimit, chainConfig, quai.config.SlicesRunning, quai.config.DomUrl, quai.config.SubUrls, quai.engine, cacheConfig, vmConfig, config.Genesis, logger) if err != nil { return nil, err } @@ -218,17 +244,6 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx quai.bloomIndexer.Start(quai.Core().Slice().HeaderChain()) } - // Set the p2p Networking API - quai.p2p = p2p - // Subscribe to the Blocks subscription - quai.p2p.Subscribe(config.NodeLocation, &types.Block{}) - quai.p2p.Subscribe(config.NodeLocation, common.Hash{}) - quai.p2p.Subscribe(config.NodeLocation, &types.Transaction{}) - - quai.handler = newHandler(quai.p2p, quai.core, config.NodeLocation) - // Start the handler - quai.handler.Start() - quai.APIBackend = &QuaiAPIBackend{stack.Config().ExtRPCEnabled(), quai, nil} // Gasprice oracle is only initiated in zone chains if nodeCtx == common.ZONE_CTX && quai.core.ProcessingState() { @@ -260,6 +275,21 @@ func New(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx return quai, nil } +func NewFake(stack *node.Node, p2p NetworkingAPI, config *quaiconfig.Config, nodeCtx int, logger *log.Logger, chainDb ethdb.Database) (*Quai, error) { + + quai, err := newCommon(stack, p2p, config, nodeCtx, logger, chainDb, core.NewFakeCore) + + quai.handler = newHandler(quai.p2p, quai.core, config.NodeLocation) + // Start the handler + quai.handler.Start() + + if err != nil { + return nil, err + } + + return quai, nil +} + // APIs return the collection of RPC services the go-quai package offers. // NOTE, some of these services probably need to be moved to somewhere else. func (s *Quai) APIs() []rpc.API { diff --git a/quaiclient/ethclient/ethclient_test.go b/quaiclient/ethclient/ethclient_test.go index 94bf7383f4..6496164c4b 100644 --- a/quaiclient/ethclient/ethclient_test.go +++ b/quaiclient/ethclient/ethclient_test.go @@ -20,185 +20,72 @@ import ( "bytes" "context" "errors" - "fmt" "math/big" - "reflect" "testing" "time" - quai "github.com/dominant-strategies/go-quai" + goQuai "github.com/dominant-strategies/go-quai" "github.com/dominant-strategies/go-quai/common" "github.com/dominant-strategies/go-quai/consensus/progpow" "github.com/dominant-strategies/go-quai/core" "github.com/dominant-strategies/go-quai/core/rawdb" "github.com/dominant-strategies/go-quai/core/types" "github.com/dominant-strategies/go-quai/crypto" - "github.com/dominant-strategies/go-quai/eth" - "github.com/dominant-strategies/go-quai/eth/ethconfig" + "github.com/dominant-strategies/go-quai/ethdb" + "github.com/dominant-strategies/go-quai/log" "github.com/dominant-strategies/go-quai/node" + p2p "github.com/dominant-strategies/go-quai/p2p/node" "github.com/dominant-strategies/go-quai/params" + "github.com/dominant-strategies/go-quai/quai" + "github.com/dominant-strategies/go-quai/quai/quaiconfig" + "github.com/dominant-strategies/go-quai/quaiclient" "github.com/dominant-strategies/go-quai/rpc" ) // Verify that Client implements the quai interfaces. var ( - _ = quai.ChainReader(&Client{}) - _ = quai.TransactionReader(&Client{}) - _ = quai.ChainStateReader(&Client{}) - _ = quai.ChainSyncReader(&Client{}) - _ = quai.ContractCaller(&Client{}) - _ = quai.GasEstimator(&Client{}) - _ = quai.GasPricer(&Client{}) - _ = quai.LogFilterer(&Client{}) - _ = quai.PendingStateReader(&Client{}) - // _ = quai.PendingStateEventer(&Client{}) - _ = quai.PendingContractCaller(&Client{}) + _ = goQuai.ChainReader(&Client{}) + _ = goQuai.TransactionReader(&Client{}) + _ = goQuai.ChainStateReader(&Client{}) + _ = goQuai.ChainSyncReader(&Client{}) + _ = goQuai.ContractCaller(&Client{}) + _ = goQuai.GasEstimator(&Client{}) + _ = goQuai.GasPricer(&Client{}) + //_ = goQuai.LogFilterer(&Client{}) + _ = goQuai.PendingStateReader(&Client{}) + // _ = goQuai.PendingStateEventer(&Client{}) + _ = goQuai.PendingContractCaller(&Client{}) ) -func TestToFilterArg(t *testing.T) { - blockHashErr := fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") - addresses := []common.Address{ - common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462"), - } - blockHash := common.HexToHash( - "0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb", - ) - - for _, testCase := range []struct { - name string - input quai.FilterQuery - output interface{} - err error - }{ - { - "without BlockHash", - quai.FilterQuery{ - Addresses: addresses, - FromBlock: big.NewInt(1), - ToBlock: big.NewInt(2), - Topics: [][]common.Hash{}, - }, - map[string]interface{}{ - "address": addresses, - "fromBlock": "0x1", - "toBlock": "0x2", - "topics": [][]common.Hash{}, - }, - nil, - }, - { - "with nil fromBlock and nil toBlock", - quai.FilterQuery{ - Addresses: addresses, - Topics: [][]common.Hash{}, - }, - map[string]interface{}{ - "address": addresses, - "fromBlock": "0x0", - "toBlock": "latest", - "topics": [][]common.Hash{}, - }, - nil, - }, - { - "with negative fromBlock and negative toBlock", - quai.FilterQuery{ - Addresses: addresses, - FromBlock: big.NewInt(-1), - ToBlock: big.NewInt(-1), - Topics: [][]common.Hash{}, - }, - map[string]interface{}{ - "address": addresses, - "fromBlock": "pending", - "toBlock": "pending", - "topics": [][]common.Hash{}, - }, - nil, - }, - { - "with blockhash", - quai.FilterQuery{ - Addresses: addresses, - BlockHash: &blockHash, - Topics: [][]common.Hash{}, - }, - map[string]interface{}{ - "address": addresses, - "blockHash": blockHash, - "topics": [][]common.Hash{}, - }, - nil, - }, - { - "with blockhash and from block", - quai.FilterQuery{ - Addresses: addresses, - BlockHash: &blockHash, - FromBlock: big.NewInt(1), - Topics: [][]common.Hash{}, - }, - nil, - blockHashErr, - }, - { - "with blockhash and to block", - quai.FilterQuery{ - Addresses: addresses, - BlockHash: &blockHash, - ToBlock: big.NewInt(1), - Topics: [][]common.Hash{}, - }, - nil, - blockHashErr, - }, - { - "with blockhash and both from / to block", - quai.FilterQuery{ - Addresses: addresses, - BlockHash: &blockHash, - FromBlock: big.NewInt(1), - ToBlock: big.NewInt(2), - Topics: [][]common.Hash{}, - }, - nil, - blockHashErr, - }, - } { - t.Run(testCase.name, func(t *testing.T) { - output, err := toFilterArg(testCase.input) - if (testCase.err == nil) != (err == nil) { - t.Fatalf("expected error %v but got %v", testCase.err, err) - } - if testCase.err != nil { - if testCase.err.Error() != err.Error() { - t.Fatalf("expected error %v but got %v", testCase.err, err) - } - } else if !reflect.DeepEqual(testCase.output, output) { - t.Fatalf("expected filter arg %v but got %v", testCase.output, output) - } - }) - } -} - var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAddr = crypto.PubkeyToAddress(testKey.PublicKey) - testBalance = big.NewInt(2e15) + testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey, common.Location{0, 0}) + testBalance = big.NewInt(2e15) + nodeLocation = common.Location{0, 0} ) -func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { +func newTestBackend(t *testing.T, ctx context.Context) (*node.Node, []*types.Block) { + logger := log.NewLogger("testlogs/ethclient_test.log", "debug") // Generate test chain. - genesis, blocks := generateTestChain() - // Create node - n, err := node.New(&node.Config{}) + db := rawdb.NewMemoryDatabase() + genesis, blocks := generateTestChain(db, logger) + // Create p2p node + + p2p := &p2p.P2PNode{} + //Create node + n, err := node.New(&node.Config{}, logger) if err != nil { t.Fatalf("can't create new node: %v", err) } // Create quai Service - config := ðconfig.Config{Genesis: genesis} + config := &quaiconfig.Config{Genesis: genesis} + config.Zone = 0 + config.Miner.ExtraData = []byte("test miner") config.Progpow.PowMode = progpow.ModeFake - ethservice, err := eth.New(n, config) + // Set location to ZONE_CTX + config.NodeLocation = nodeLocation + + ethservice, err := quai.NewFake(n, p2p, config, nodeLocation.Context(), logger, db) if err != nil { t.Fatalf("can't create new quai service: %v", err) } @@ -206,36 +93,44 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { if err := n.Start(); err != nil { t.Fatalf("can't start test node: %v", err) } - if _, err := ethservice.BlockChain().InsertChain(blocks[1:]); err != nil { + + if _, err := ethservice.Core().InsertChain(blocks[1:]); err != nil { t.Fatalf("can't import test blocks: %v", err) } return n, blocks } -func generateTestChain() (*core.Genesis, []*types.Block) { - db := rawdb.NewMemoryDatabase() - config := params.AllProgpowProtocolChanges +// Generate a zone chain with genesis + 1 block +func generateTestChain(db ethdb.Database, logger *log.Logger) (*core.Genesis, []*types.Block) { + config := params.TestChainConfig + config.Location = nodeLocation + genesis := &core.Genesis{ - Config: config, - Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance}}, - ExtraData: []byte("test genesis"), - Timestamp: 9000, - BaseFee: big.NewInt(params.InitialBaseFee), + Config: config, + Nonce: 0, + ExtraData: []byte("test genesis"), + GasLimit: 5000000, + Difficulty: big.NewInt(300000000), } generate := func(i int, g *core.BlockGen) { g.OffsetTime(5) g.SetExtra([]byte("test")) } gblock := genesis.ToBlock(db) - engine := progpow.NewFaker() + + config.GenesisHash = gblock.Hash() + + engine := progpow.NewFaker(logger, nodeLocation) blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate) blocks = append([]*types.Block{gblock}, blocks...) return genesis, blocks } func TestEthClient(t *testing.T) { - backend, chain := newTestBackend(t) + ctx, cancel := context.WithCancel(context.Background()) + backend, chain := newTestBackend(t, ctx) client, _ := backend.Attach() + defer cancel() defer backend.Close() defer client.Close() @@ -276,39 +171,34 @@ func TestEthClient(t *testing.T) { func testHeader(t *testing.T, chain []*types.Block, client *rpc.Client) { tests := map[string]struct { - block *big.Int + block string want *types.Header wantErr error }{ "genesis": { - block: big.NewInt(0), + block: "0x0", want: chain[0].Header(), }, "first_block": { - block: big.NewInt(1), + block: "0x1", want: chain[1].Header(), }, "future_block": { - block: big.NewInt(1000000000), - want: nil, - wantErr: quai.NotFound, + block: "0xffffff", + want: nil, }, } for name, tt := range tests { t.Run(name, func(t *testing.T) { - ec := NewClient(client) + ec := quaiclient.NewClient(&quaiclient.TestRpcClient{Chain: chain}) ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() - got, err := ec.HeaderByNumber(ctx, tt.block) - if !errors.Is(err, tt.wantErr) { - t.Fatalf("HeaderByNumber(%v) error = %q, want %q", tt.block, err, tt.wantErr) - } - if got != nil && got.Number() != nil && got.Number().Sign() == 0 { - got.Number() = big.NewInt(0) // hack to make DeepEqual work - } - if !reflect.DeepEqual(got, tt.want) { - t.Fatalf("HeaderByNumber(%v)\n = %v\nwant %v", tt.block, got, tt.want) + got := ec.HeaderByNumber(ctx, tt.block) + + err := got.Compare(tt.want) + if err != nil { + t.Fatalf("deepEqual failed %v", err) } }) } @@ -327,7 +217,7 @@ func testBalanceAt(t *testing.T, client *rpc.Client) { want: testBalance, }, "non_existent_account": { - account: common.Address{1}, + account: common.Address{}, block: big.NewInt(1), want: big.NewInt(0), }, @@ -370,11 +260,11 @@ func testTransactionInBlockInterrupted(t *testing.T, client *rpc.Client) { if tx != nil { t.Fatal("transaction should be nil") } - if err == nil || err == quai.NotFound { + if err == nil || err == goQuai.NotFound { t.Fatal("error should not be nil/notfound") } // Test tx in block not found - if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != quai.NotFound { + if _, err := ec.TransactionInBlock(context.Background(), block.Hash(), 1); err != goQuai.NotFound { t.Fatal("error should be quai.NotFound") } } @@ -405,8 +295,8 @@ func testGetBlock(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if block.NumberU64() != blockNumber { - t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64()) + if block.NumberU64(nodeLocation.Context()) != blockNumber { + t.Fatalf("BlockByNumber returned wrong block: want %d got %d", blockNumber, block.NumberU64(nodeLocation.Context())) } // Get current block by hash blockH, err := ec.BlockByHash(context.Background(), block.Hash()) @@ -475,7 +365,7 @@ func testCallContract(t *testing.T, client *rpc.Client) { ec := NewClient(client) // EstimateGas - msg := quai.CallMsg{ + msg := goQuai.CallMsg{ From: testAddr, To: &common.Address{}, Gas: 21000, @@ -568,8 +458,8 @@ func sendTransaction(ec *Client) error { return err } // Create transaction - tx := types.NewTransaction(0, common.Address{1}, big.NewInt(1), 22000, big.NewInt(params.InitialBaseFee), nil) - signer := types.LatestSignerForChainID(chainID) + tx := types.NewTx(&types.ExternalTx{}) + signer := types.LatestSignerForChainID(chainID, nodeLocation) signature, err := crypto.Sign(signer.Hash(tx).Bytes(), testKey) if err != nil { return err diff --git a/quaiclient/quaiclient.go b/quaiclient/quaiclient.go index b3ba11de96..145ea9c0b3 100644 --- a/quaiclient/quaiclient.go +++ b/quaiclient/quaiclient.go @@ -34,10 +34,56 @@ import ( var exponentialBackoffCeilingSecs int64 = 60 // 1 minute +type IClient interface { + CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error + Close() + QuaiSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) +} + +// Used on unit tests +type TestRpcClient struct { + Chain []*types.Block +} + +func (trc *TestRpcClient) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error { + if method == "quai_updateDom" { + return nil + } + + if method == "quai_getHeaderByNumber" { + blockNumber, err := hexutil.DecodeUint64(args[0].(string)) + if err != nil { + return err + } + if blockNumber >= uint64(len(trc.Chain)) { + return nil; + } + test := trc.Chain[blockNumber].Header().RPCMarshalHeader() + jsonTest, err := json.Marshal(test) + if err != nil { + return err + } + *result.(*json.RawMessage) = jsonTest + return nil + } + + if method == "quai_sendPendingEtxsToDom" { + return nil + } + return fmt.Errorf("method %s is not implemented", method) +} + +func (trc *TestRpcClient) Close() { +} + +func (trc *TestRpcClient) QuaiSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*rpc.ClientSubscription, error) { + return &rpc.ClientSubscription{}, nil +} + // Client defines typed wrappers for the Quai RPC API. type Client struct { - c *rpc.Client -} + c IClient + } // Dial connects a client to the given URL. func Dial(rawurl string, logger *log.Logger) (*Client, error) { @@ -78,7 +124,7 @@ func DialContext(ctx context.Context, rawurl string, logger *log.Logger) (*Clien } // NewClient creates a client that uses the given RPC client. -func NewClient(c *rpc.Client) *Client { +func NewClient(c IClient) *Client { return &Client{c} } diff --git a/quaiclient/quaiclient_test.go b/quaiclient/quaiclient_test.go new file mode 100644 index 0000000000..0619a4b76f --- /dev/null +++ b/quaiclient/quaiclient_test.go @@ -0,0 +1,138 @@ +package quaiclient + +import ( + "fmt" + "math/big" + "reflect" + "testing" + + quai "github.com/dominant-strategies/go-quai" + "github.com/dominant-strategies/go-quai/common" +) + +func TestToFilterArg(t *testing.T) { + blockHashErr := fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") + addresses := []common.Address{ + common.HexToAddress("0xD36722ADeC3EdCB29c8e7b5a47f352D701393462", common.Location{0, 0}), + } + blockHash := common.HexToHash( + "0xeb94bb7d78b73657a9d7a99792413f50c0a45c51fc62bdcb08a53f18e9a2b4eb", + ) + + for _, testCase := range []struct { + name string + input quai.FilterQuery + output interface{} + err error + }{ + { + "without BlockHash", + quai.FilterQuery{ + Addresses: addresses, + FromBlock: big.NewInt(1), + ToBlock: big.NewInt(2), + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "0x1", + "toBlock": "0x2", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with nil fromBlock and nil toBlock", + quai.FilterQuery{ + Addresses: addresses, + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "0x0", + "toBlock": "latest", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with negative fromBlock and negative toBlock", + quai.FilterQuery{ + Addresses: addresses, + FromBlock: big.NewInt(-1), + ToBlock: big.NewInt(-1), + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "fromBlock": "pending", + "toBlock": "pending", + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with blockhash", + quai.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + Topics: [][]common.Hash{}, + }, + map[string]interface{}{ + "address": addresses, + "blockHash": blockHash, + "topics": [][]common.Hash{}, + }, + nil, + }, + { + "with blockhash and from block", + quai.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + FromBlock: big.NewInt(1), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + { + "with blockhash and to block", + quai.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + ToBlock: big.NewInt(1), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + { + "with blockhash and both from / to block", + quai.FilterQuery{ + Addresses: addresses, + BlockHash: &blockHash, + FromBlock: big.NewInt(1), + ToBlock: big.NewInt(2), + Topics: [][]common.Hash{}, + }, + nil, + blockHashErr, + }, + } { + + t.Run(testCase.name, func(t *testing.T) { + output, err := toFilterArg(testCase.input) + if (testCase.err == nil) != (err == nil) { + t.Fatalf("expected error %v but got %v", testCase.err, err) + } + if testCase.err != nil { + if testCase.err.Error() != err.Error() { + t.Fatalf("expected error %v but got %v", testCase.err, err) + } + } else if !reflect.DeepEqual(testCase.output, output) { + t.Fatalf("expected filter arg %v but got %v", testCase.output, output) + } + }) + } +}