diff --git a/.gitignore b/.gitignore index 11eb4dd62c..f4bd18c255 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ nodelogs/ */config.toml */private.key .vscode/ -.idea* \ No newline at end of file +testlogs/ +.idea* +*.cov \ No newline at end of file diff --git a/consensus/progpow/consensus.go b/consensus/progpow/consensus.go index 204095ca67..975b712546 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 298cb569e3..e4a27f1548 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 @@ -250,6 +253,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 @@ -264,14 +268,8 @@ 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() @@ -279,11 +277,17 @@ func makeHeader(chain consensus.ChainReader, parent *types.Block, state *state.S header.SetEVMRoot(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 cbe487ea4a..28e59dbc97 100644 --- a/core/core.go +++ b/core/core.go @@ -85,12 +85,18 @@ type IndexerConfig struct { IndexAddressUtxos bool } +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, indexerConfig *IndexerConfig, 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, indexerConfig *IndexerConfig, genesis *Genesis, logger *log.Logger) (*Core, error) { slice, err := NewSlice(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, indexerConfig, 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, @@ -103,6 +109,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, indexerConfig *IndexerConfig, genesis *Genesis, logger *log.Logger) (*Core, error) { + slice, err := NewFakeSlice(db, config, txConfig, txLookupLimit, isLocalBlock, chainConfig, slicesRunning, domClientUrl, subClientUrls, engine, cacheConfig, indexerConfig, 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 @@ -115,8 +138,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/genesis.go b/core/genesis.go index b3ca72c621..37dc948d60 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -271,6 +271,7 @@ func (g *Genesis) ToBlock(db ethdb.Database) *types.Block { head.SetGasUsed(0) head.SetCoinbase(common.Zero) head.SetBaseFee(new(big.Int).SetUint64(params.InitialBaseFee)) + head.SetCoinbase(g.Coinbase) if g.GasLimit == 0 { head.SetGasLimit(params.GenesisGasLimit) } diff --git a/core/slice.go b/core/slice.go index ea9336ef95..a0ae94261f 100644 --- a/core/slice.go +++ b/core/slice.go @@ -86,6 +86,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, indexerConfig *IndexerConfig, 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, indexerConfig, 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, indexerConfig *IndexerConfig, vmConfig vm.Config, genesis *Genesis, nodeCtx int, logger *log.Logger) (*Slice, error) { sl := &Slice{ config: chainConfig, engine: engine, @@ -124,13 +141,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 } @@ -144,6 +154,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, indexerConfig *IndexerConfig, 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, indexerConfig, 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 @@ -307,15 +335,8 @@ func (sl *Slice) Append(header *types.Header, domPendingHeader *types.Header, do var time8, time9 common.PrettyDuration var bestPh types.PendingHeader - var exist bool if nodeCtx == common.ZONE_CTX { - bestPh, exist = sl.readPhCache(sl.bestPhKey) - if !exist { - sl.WriteBestPhKey(sl.config.GenesisHash) - sl.writePhCache(block.Hash(), pendingHeaderWithTermini) - bestPh = types.EmptyPendingHeader() - sl.logger.WithField("key", sl.bestPhKey).Warn("BestPh Key does not exist") - } + bestPh, _ = sl.readPhCache(sl.bestPhKey) time8 = common.PrettyDuration(time.Since(start)) diff --git a/core/types/block.go b/core/types/block.go index e3a27a7eb7..3aa663ad3a 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -113,6 +113,64 @@ 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}, + "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/log/logger.go b/log/logger.go index f6ade4b71b..204094416c 100644 --- a/log/logger.go +++ b/log/logger.go @@ -24,6 +24,7 @@ const ( defaultLogMaxBackups = 3 // maximum number of old log files to keep defaultLogMaxAge = 28 // maximum number of days to retain old log files defaultLogCompress = true // whether to compress the rotated log files using gzip + DebugLevel = logrus.DebugLevel ) var ( diff --git a/quai/backend.go b/quai/backend.go index c646326457..78023aa2d3 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, @@ -212,9 +240,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, indexerConfig, 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, indexerConfig, config.Genesis, logger) if err != nil { return nil, err } @@ -225,17 +254,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() { @@ -253,6 +271,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.go b/quaiclient/ethclient/ethclient.go index d501b064f6..54eb2c95d7 100644 --- a/quaiclient/ethclient/ethclient.go +++ b/quaiclient/ethclient/ethclient.go @@ -100,7 +100,7 @@ type rpcBlock struct { Transactions []rpcTransaction `json:"transactions"` UncleHashes []common.Hash `json:"uncles"` ExtTransactions []rpcTransaction `json:"extTransactions"` - SubManifest types.BlockManifest `json:"manifest"` + SubManifest types.BlockManifest `json:"subManifest"` } func (ec *Client) getBlock(ctx context.Context, method string, args ...interface{}) (*types.Block, error) { diff --git a/quaiclient/ethclient/ethclient_test.go b/quaiclient/ethclient/ethclient_test.go index 8837b814c4..387df02ab2 100644 --- a/quaiclient/ethclient/ethclient_test.go +++ b/quaiclient/ethclient/ethclient_test.go @@ -20,183 +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/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) + nodeLocation = common.Location{0, 1} + testKey, _ = crypto.HexToECDSA("e6122ba6f706fff23b50654d2a6f47345d135463f32cd6fd3b278fbc0d475394") + testAddr = crypto.PubkeyToAddress(testKey.PublicKey, nodeLocation) + testBalance = big.NewInt(2e15) ) -func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { +func newTestBackend(t *testing.T, ctx context.Context) (*node.Node, []*types.Block) { + log.Global.SetLevel(log.DebugLevel) // Generate test chain. - genesis, blocks := generateTestChain() - // Create node - n, err := node.New(&node.Config{}) + db := rawdb.NewMemoryDatabase() + genesis, blocks := generateTestChain(db, log.Global) + // Create p2p node + + p2p := &p2p.P2PNode{} + //Create node + n, err := node.New(&node.Config{}, log.Global) 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(), log.Global, db) if err != nil { t.Fatalf("can't create new quai service: %v", err) } @@ -204,36 +93,45 @@ 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), + Coinbase: testAddr, } 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() @@ -274,39 +172,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) } }) } @@ -325,7 +218,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), }, @@ -368,11 +261,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") } } @@ -383,7 +276,7 @@ func testChainID(t *testing.T, client *rpc.Client) { if err != nil { t.Fatalf("unexpected error: %v", err) } - if id == nil || id.Cmp(params.AllProgpowProtocolChanges.ChainID) != 0 { + if id == nil || id.Cmp(big.NewInt(1)) != 0 { t.Fatalf("ChainID returned wrong number: %+v", id) } } @@ -403,8 +296,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()) @@ -473,7 +366,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, @@ -566,8 +459,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 da6fe0bc6a..1275d6bce5 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) + } + }) + } +}