diff --git a/core/blockchain.go b/core/blockchain.go index 3cfa21654f..e24676404b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -974,6 +974,42 @@ func (bc *BlockChain) GetDiffLayerRLP(blockHash common.Hash) rlp.RawValue { return rawData } +func (bc *BlockChain) GetDiffAccounts(blockHash common.Hash) ([]common.Address, error) { + var ( + accounts []common.Address + diffLayer *types.DiffLayer + ) + + header := bc.GetHeaderByHash(blockHash) + if header == nil { + return nil, fmt.Errorf("no block found") + } + + if cached, ok := bc.diffLayerCache.Get(blockHash); ok { + diffLayer = cached.(*types.DiffLayer) + } else if diffStore := bc.db.DiffStore(); diffStore != nil { + diffLayer = rawdb.ReadDiffLayer(diffStore, blockHash) + } + + if diffLayer == nil { + if header.TxHash != types.EmptyRootHash { + return nil, fmt.Errorf("no diff layer found") + } + + return nil, nil + } + + for _, diffAccounts := range diffLayer.Accounts { + accounts = append(accounts, diffAccounts.Account) + } + + if header.TxHash != types.EmptyRootHash && (accounts == nil || len(accounts) == 0) { + return nil, fmt.Errorf("no diff account in block, maybe bad diff layer") + } + + return accounts, nil +} + // HasBlock checks if a block is fully present in the database or not. func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool { if bc.blockCache.Contains(hash) { diff --git a/core/types/block.go b/core/types/block.go index a577e60516..bee5d80cdd 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -453,3 +453,14 @@ type DiffStorage struct { Keys []string Vals [][]byte } + +type DiffAccountsInTx struct { + TxHash common.Hash + Accounts map[common.Address]*big.Int +} + +type DiffAccountsInBlock struct { + Number uint64 + BlockHash common.Hash + Transactions []DiffAccountsInTx +} diff --git a/eth/api_backend.go b/eth/api_backend.go index 7ac1f82a86..5c864a236b 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -279,6 +279,10 @@ func (b *EthAPIBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestPrice(ctx) } +func (b *EthAPIBackend) Chain() *core.BlockChain { + return b.eth.BlockChain() +} + func (b *EthAPIBackend) ChainDb() ethdb.Database { return b.eth.ChainDb() } diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 81fbe5b407..84b8bcc288 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -186,6 +186,20 @@ func (ec *Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.H return head, err } +// GetDiffAccounts returns changed accounts in a specific block number. +func (ec *Client) GetDiffAccounts(ctx context.Context, number *big.Int) ([]common.Address, error) { + accounts := make([]common.Address, 0) + err := ec.c.CallContext(ctx, &accounts, "eth_getDiffAccounts", toBlockNumArg(number)) + return accounts, err +} + +// GetDiffAccountsInBlock returns detailed changes of some interested accounts in a specific block number. +func (ec *Client) GetDiffAccountsInBlock(ctx context.Context, number *big.Int, accounts []common.Address) (*types.DiffAccountsInBlock, error) { + var result types.DiffAccountsInBlock + err := ec.c.CallContext(ctx, &result, "eth_getDiffAccountsInBlock", toBlockNumArg(number), accounts) + return &result, err +} + type rpcTransaction struct { tx *types.Transaction txExtraInfo diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 15e7a8c8f1..744a04d46d 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -25,6 +25,8 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/consensus" + "github.com/davecgh/go-spew/spew" "github.com/ethereum/go-ethereum/accounts" @@ -1086,6 +1088,108 @@ func (s *PublicBlockChainAPI) EstimateGas(ctx context.Context, args CallArgs, bl return DoEstimateGas(ctx, s.b, args, bNrOrHash, s.b.RPCGasCap()) } +// GetDiffAccounts returns changed accounts in a specific block number. +func (s *PublicBlockChainAPI) GetDiffAccounts(ctx context.Context, blockNr rpc.BlockNumber) ([]common.Address, error) { + if s.b.Chain() == nil { + return nil, fmt.Errorf("blockchain not support get diff accounts") + } + + header, err := s.b.HeaderByNumber(ctx, blockNr) + if err != nil { + return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr, err) + } + + return s.b.Chain().GetDiffAccounts(header.Hash()) +} + +// GetDiffAccountsInBlock returns detailed changes of some interested accounts in a specific block number. +func (s *PublicBlockChainAPI) GetDiffAccountsInBlock(ctx context.Context, blockNr rpc.BlockNumber, accounts []common.Address) (*types.DiffAccountsInBlock, error) { + if s.b.Chain() == nil { + return nil, fmt.Errorf("blockchain not support get diff accounts") + } + + block, err := s.b.BlockByNumber(ctx, blockNr) + if err != nil { + return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr, err) + } + parent, err := s.b.BlockByHash(ctx, block.ParentHash()) + if err != nil { + return nil, fmt.Errorf("block not found for block number (%d): %v", blockNr-1, err) + } + statedb, err := s.b.Chain().StateAt(parent.Root()) + if err != nil { + return nil, fmt.Errorf("state not found for block number (%d): %v", blockNr-1, err) + } + + result := &types.DiffAccountsInBlock{ + Number: uint64(blockNr), + BlockHash: block.Hash(), + Transactions: make([]types.DiffAccountsInTx, 0), + } + + accountSet := make(map[common.Address]struct{}, len(accounts)) + for _, account := range accounts { + accountSet[account] = struct{}{} + } + + // Recompute transactions. + signer := types.MakeSigner(s.b.ChainConfig(), block.Number()) + for _, tx := range block.Transactions() { + // Skip data empty tx and to is one of the interested accounts tx. + skip := false + if len(tx.Data()) == 0 { + skip = true + } else if to := tx.To(); to != nil { + if _, exists := accountSet[*to]; exists { + skip = true + } + } + + diffTx := types.DiffAccountsInTx{ + TxHash: tx.Hash(), + Accounts: make(map[common.Address]*big.Int, len(accounts)), + } + + if !skip { + // Record account balance + for _, account := range accounts { + diffTx.Accounts[account] = statedb.GetBalance(account) + } + } + + // Apply transaction + msg, _ := tx.AsMessage(signer) + txContext := core.NewEVMTxContext(msg) + context := core.NewEVMBlockContext(block.Header(), s.b.Chain(), nil) + vmenv := vm.NewEVM(context, txContext, statedb, s.b.ChainConfig(), vm.Config{}) + + if posa, ok := s.b.Engine().(consensus.PoSA); ok { + if isSystem, _ := posa.IsSystemTransaction(tx, block.Header()); isSystem { + balance := statedb.GetBalance(consensus.SystemAddress) + if balance.Cmp(common.Big0) > 0 { + statedb.SetBalance(consensus.SystemAddress, big.NewInt(0)) + statedb.AddBalance(block.Header().Coinbase, balance) + } + } + } + + if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + return nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) + + if !skip { + // Compute account balance diff. + for _, account := range accounts { + diffTx.Accounts[account] = new(big.Int).Sub(statedb.GetBalance(account), diffTx.Accounts[account]) + } + result.Transactions = append(result.Transactions, diffTx) + } + } + + return result, nil +} + // ExecutionResult groups all structured logs emitted by the EVM // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index 07e76583f3..ca5a55d5ed 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -42,6 +42,7 @@ type Backend interface { // General Ethereum API Downloader() *downloader.Downloader SuggestPrice(ctx context.Context) (*big.Int, error) + Chain() *core.BlockChain ChainDb() ethdb.Database AccountManager() *accounts.Manager ExtRPCEnabled() bool diff --git a/les/api_backend.go b/les/api_backend.go index 60c64a8bdf..c8eca2d905 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -255,6 +255,10 @@ func (b *LesApiBackend) SuggestPrice(ctx context.Context) (*big.Int, error) { return b.gpo.SuggestPrice(ctx) } +func (b *LesApiBackend) Chain() *core.BlockChain { + return nil +} + func (b *LesApiBackend) ChainDb() ethdb.Database { return b.eth.chainDb }