소스 검색

Export get diff accounts in block api (#431)

* support get diff accounts

Signed-off-by: Keefe-Liu <bianze.kernel@gmail.com>

* add testcase for diff accounts

Signed-off-by: Keefe-Liu <bianze.kernel@gmail.com>
KeefeL 4 년 전
부모
커밋
b2f1d25f83
9개의 변경된 파일495개의 추가작업 그리고 24개의 파일을 삭제
  1. 36 0
      core/blockchain.go
  2. 158 16
      core/blockchain_diff_test.go
  3. 11 0
      core/types/block.go
  4. 4 0
      eth/api_backend.go
  5. 14 0
      ethclient/ethclient.go
  6. 161 8
      ethclient/ethclient_test.go
  7. 106 0
      internal/ethapi/api.go
  8. 1 0
      internal/ethapi/backend.go
  9. 4 0
      les/api_backend.go

+ 36 - 0
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 && 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) {

+ 158 - 16
core/blockchain_diff_test.go

@@ -45,8 +45,79 @@ var (
 	testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
 	// testAddr is the Ethereum address of the tester account.
 	testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
+	// testBlocks is the test parameters array for specific blocks.
+	testBlocks = []testBlockParam{
+		{
+			// This txs params also used to default block.
+			blockNr: 11,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+			},
+		},
+		{
+			blockNr: 12,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x02},
+					value:    big.NewInt(2),
+					gasPrice: big.NewInt(2),
+					data:     nil,
+				},
+			},
+		},
+		{
+			blockNr: 13,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x02},
+					value:    big.NewInt(2),
+					gasPrice: big.NewInt(2),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x03},
+					value:    big.NewInt(3),
+					gasPrice: big.NewInt(3),
+					data:     nil,
+				},
+			},
+		},
+		{
+			blockNr: 14,
+			txs:     []testTransactionParam{},
+		},
+	}
 )
 
+type testTransactionParam struct {
+	to       common.Address
+	value    *big.Int
+	gasPrice *big.Int
+	data     []byte
+}
+
+type testBlockParam struct {
+	blockNr int
+	txs     []testTransactionParam
+}
+
 // testBackend is a mock implementation of the live Ethereum message handler. Its
 // purpose is to allow testing the request/reply workflows and wire serialization
 // in the `eth` protocol without actually doing any data processing.
@@ -78,13 +149,35 @@ func newTestBackendWithGenerator(blocks int, lightProcess bool) *testBackend {
 		// lets unset (nil). Set it here to the correct value.
 		block.SetCoinbase(testAddr)
 
-		// We want to simulate an empty middle block, having the same state as the
-		// first one. The last is needs a state change again to force a reorg.
-		tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), common.Address{0x01}, big.NewInt(1), params.TxGas, big.NewInt(1), nil), signer, testKey)
-		if err != nil {
-			panic(err)
+		for idx, testBlock := range testBlocks {
+			// Specific block setting, the index in this generator has 1 diff from specified blockNr.
+			if i+1 == testBlock.blockNr {
+				for _, testTransaction := range testBlock.txs {
+					tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testTransaction.to,
+						testTransaction.value, params.TxGas, testTransaction.gasPrice, testTransaction.data), signer, testKey)
+					if err != nil {
+						panic(err)
+					}
+					block.AddTxWithChain(chain, tx)
+				}
+				break
+			}
+
+			// Default block setting.
+			if idx == len(testBlocks)-1 {
+				// We want to simulate an empty middle block, having the same state as the
+				// first one. The last is needs a state change again to force a reorg.
+				for _, testTransaction := range testBlocks[0].txs {
+					tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testTransaction.to,
+						testTransaction.value, params.TxGas, testTransaction.gasPrice, testTransaction.data), signer, testKey)
+					if err != nil {
+						panic(err)
+					}
+					block.AddTxWithChain(chain, tx)
+				}
+			}
 		}
-		block.AddTxWithChain(chain, tx)
+
 	}
 	bs, _ := GenerateChain(params.TestChainConfig, chain.Genesis(), ethash.NewFaker(), db, blocks, generator)
 	if _, err := chain.InsertChain(bs); err != nil {
@@ -139,12 +232,17 @@ func TestProcessDiffLayer(t *testing.T) {
 		}
 		blockHash := block.Hash()
 		rawDiff := fullBackend.chain.GetDiffLayerRLP(blockHash)
-		diff, err := rawDataToDiffLayer(rawDiff)
-		if err != nil {
-			t.Errorf("failed to decode rawdata %v", err)
+		if len(rawDiff) != 0 {
+			diff, err := rawDataToDiffLayer(rawDiff)
+			if err != nil {
+				t.Errorf("failed to decode rawdata %v", err)
+			}
+			if diff == nil {
+				continue
+			}
+			lightBackend.Chain().HandleDiffLayer(diff, "testpid", true)
 		}
-		lightBackend.Chain().HandleDiffLayer(diff, "testpid", true)
-		_, err = lightBackend.chain.insertChain([]*types.Block{block}, true)
+		_, err := lightBackend.chain.insertChain([]*types.Block{block}, true)
 		if err != nil {
 			t.Errorf("failed to insert block %v", err)
 		}
@@ -186,7 +284,8 @@ func TestFreezeDiffLayer(t *testing.T) {
 	blockNum := 1024
 	fullBackend := newTestBackend(blockNum, true)
 	defer fullBackend.close()
-	if fullBackend.chain.diffQueue.Size() != blockNum {
+	// Minus one empty block.
+	if fullBackend.chain.diffQueue.Size() != blockNum-1 {
 		t.Errorf("size of diff queue is wrong, expected: %d, get: %d", blockNum, fullBackend.chain.diffQueue.Size())
 	}
 	time.Sleep(diffLayerFreezerRecheckInterval + 1*time.Second)
@@ -215,10 +314,11 @@ func TestPruneDiffLayer(t *testing.T) {
 	for num := uint64(1); num < uint64(blockNum); num++ {
 		header := fullBackend.chain.GetHeaderByNumber(num)
 		rawDiff := fullBackend.chain.GetDiffLayerRLP(header.Hash())
-		diff, _ := rawDataToDiffLayer(rawDiff)
-		fullBackend.Chain().HandleDiffLayer(diff, "testpid1", true)
-		fullBackend.Chain().HandleDiffLayer(diff, "testpid2", true)
-
+		if len(rawDiff) != 0 {
+			diff, _ := rawDataToDiffLayer(rawDiff)
+			fullBackend.Chain().HandleDiffLayer(diff, "testpid1", true)
+			fullBackend.Chain().HandleDiffLayer(diff, "testpid2", true)
+		}
 	}
 	fullBackend.chain.pruneDiffLayer()
 	if len(fullBackend.chain.diffNumToBlockHashes) != maxDiffForkDist {
@@ -261,3 +361,45 @@ func TestPruneDiffLayer(t *testing.T) {
 	}
 
 }
+
+func TestGetDiffAccounts(t *testing.T) {
+	t.Parallel()
+
+	blockNum := 128
+	fullBackend := newTestBackend(blockNum, false)
+	defer fullBackend.close()
+
+	for _, testBlock := range testBlocks {
+		block := fullBackend.chain.GetBlockByNumber(uint64(testBlock.blockNr))
+		if block == nil {
+			t.Fatal("block should not be nil")
+		}
+		blockHash := block.Hash()
+		accounts, err := fullBackend.chain.GetDiffAccounts(blockHash)
+		if err != nil {
+			t.Errorf("get diff accounts eror for block number (%d): %v", testBlock.blockNr, err)
+		}
+
+		for idx, account := range accounts {
+			if testAddr == account {
+				break
+			}
+
+			if idx == len(accounts)-1 {
+				t.Errorf("the diff accounts does't include addr: %v", testAddr)
+			}
+		}
+
+		for _, transaction := range testBlock.txs {
+			for idx, account := range accounts {
+				if transaction.to == account {
+					break
+				}
+
+				if idx == len(accounts)-1 {
+					t.Errorf("the diff accounts does't include addr: %v", transaction.to)
+				}
+			}
+		}
+	}
+}

+ 11 - 0
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
+}

+ 4 - 0
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()
 }

+ 14 - 0
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
+}
+
+// GetDiffAccountsWithScope returns detailed changes of some interested accounts in a specific block number.
+func (ec *Client) GetDiffAccountsWithScope(ctx context.Context, number *big.Int, accounts []common.Address) (*types.DiffAccountsInBlock, error) {
+	var result types.DiffAccountsInBlock
+	err := ec.c.CallContext(ctx, &result, "eth_getDiffAccountsWithScope", toBlockNumArg(number), accounts)
+	return &result, err
+}
+
 type rpcTransaction struct {
 	tx *types.Transaction
 	txExtraInfo

+ 161 - 8
ethclient/ethclient_test.go

@@ -31,9 +31,11 @@ import (
 	"github.com/ethereum/go-ethereum/core"
 	"github.com/ethereum/go-ethereum/core/rawdb"
 	"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/eth"
 	"github.com/ethereum/go-ethereum/eth/ethconfig"
+	"github.com/ethereum/go-ethereum/ethdb/memorydb"
 	"github.com/ethereum/go-ethereum/node"
 	"github.com/ethereum/go-ethereum/params"
 	"github.com/ethereum/go-ethereum/rpc"
@@ -181,11 +183,82 @@ func TestToFilterArg(t *testing.T) {
 }
 
 var (
-	testKey, _  = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
-	testAddr    = crypto.PubkeyToAddress(testKey.PublicKey)
-	testBalance = big.NewInt(2e10)
+	testKey, _   = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
+	testAddr     = crypto.PubkeyToAddress(testKey.PublicKey)
+	testBalance  = big.NewInt(2e10)
+	testBlockNum = 128
+	testBlocks   = []testBlockParam{
+		{
+			// This txs params also used to default block.
+			blockNr: 10,
+			txs:     []testTransactionParam{},
+		},
+		{
+			blockNr: 11,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+			},
+		},
+		{
+			blockNr: 12,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x02},
+					value:    big.NewInt(2),
+					gasPrice: big.NewInt(2),
+					data:     nil,
+				},
+			},
+		},
+		{
+			blockNr: 13,
+			txs: []testTransactionParam{
+				{
+					to:       common.Address{0x01},
+					value:    big.NewInt(1),
+					gasPrice: big.NewInt(1),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x02},
+					value:    big.NewInt(2),
+					gasPrice: big.NewInt(2),
+					data:     nil,
+				},
+				{
+					to:       common.Address{0x03},
+					value:    big.NewInt(3),
+					gasPrice: big.NewInt(3),
+					data:     nil,
+				},
+			},
+		},
+	}
 )
 
+type testTransactionParam struct {
+	to       common.Address
+	value    *big.Int
+	gasPrice *big.Int
+	data     []byte
+}
+
+type testBlockParam struct {
+	blockNr int
+	txs     []testTransactionParam
+}
+
 func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
 	// Generate test chain.
 	genesis, blocks := generateTestChain()
@@ -197,6 +270,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
 	// Create Ethereum Service
 	config := &ethconfig.Config{Genesis: genesis}
 	config.Ethash.PowMode = ethash.ModeFake
+	config.SnapshotCache = 256
 	ethservice, err := eth.New(n, config)
 	if err != nil {
 		t.Fatalf("can't create new ethereum service: %v", err)
@@ -212,7 +286,10 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
 }
 
 func generateTestChain() (*core.Genesis, []*types.Block) {
+	signer := types.HomesteadSigner{}
+	// Create a database pre-initialize with a genesis block
 	db := rawdb.NewMemoryDatabase()
+	db.SetDiffStore(memorydb.New())
 	config := params.AllEthashProtocolChanges
 	genesis := &core.Genesis{
 		Config:    config,
@@ -220,13 +297,45 @@ func generateTestChain() (*core.Genesis, []*types.Block) {
 		ExtraData: []byte("test genesis"),
 		Timestamp: 9000,
 	}
-	generate := func(i int, g *core.BlockGen) {
-		g.OffsetTime(5)
-		g.SetExtra([]byte("test"))
+	genesis.MustCommit(db)
+	chain, _ := core.NewBlockChain(db, nil, params.TestChainConfig, ethash.NewFaker(), vm.Config{}, nil, nil, core.EnablePersistDiff(860000))
+	generate := func(i int, block *core.BlockGen) {
+		block.OffsetTime(5)
+		block.SetExtra([]byte("test"))
+		//block.SetCoinbase(testAddr)
+
+		for idx, testBlock := range testBlocks {
+			// Specific block setting, the index in this generator has 1 diff from specified blockNr.
+			if i+1 == testBlock.blockNr {
+				for _, testTransaction := range testBlock.txs {
+					tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testTransaction.to,
+						testTransaction.value, params.TxGas, testTransaction.gasPrice, testTransaction.data), signer, testKey)
+					if err != nil {
+						panic(err)
+					}
+					block.AddTxWithChain(chain, tx)
+				}
+				break
+			}
+
+			// Default block setting.
+			if idx == len(testBlocks)-1 {
+				// We want to simulate an empty middle block, having the same state as the
+				// first one. The last is needs a state change again to force a reorg.
+				for _, testTransaction := range testBlocks[0].txs {
+					tx, err := types.SignTx(types.NewTransaction(block.TxNonce(testAddr), testTransaction.to,
+						testTransaction.value, params.TxGas, testTransaction.gasPrice, testTransaction.data), signer, testKey)
+					if err != nil {
+						panic(err)
+					}
+					block.AddTxWithChain(chain, tx)
+				}
+			}
+		}
 	}
 	gblock := genesis.ToBlock(db)
 	engine := ethash.NewFaker()
-	blocks, _ := core.GenerateChain(config, gblock, engine, db, 1, generate)
+	blocks, _ := core.GenerateChain(config, gblock, engine, db, testBlockNum, generate)
 	blocks = append([]*types.Block{gblock}, blocks...)
 	return genesis, blocks
 }
@@ -261,6 +370,9 @@ func TestEthClient(t *testing.T) {
 		"TestCallContract": {
 			func(t *testing.T) { testCallContract(t, client) },
 		},
+		"TestDiffAccounts": {
+			func(t *testing.T) { testDiffAccounts(t, client) },
+		},
 		// DO not have TestAtFunctions now, because we do not have pending block now
 	}
 
@@ -393,7 +505,7 @@ func testGetBlock(t *testing.T, client *rpc.Client) {
 	if err != nil {
 		t.Fatalf("unexpected error: %v", err)
 	}
-	if blockNumber != 1 {
+	if blockNumber != uint64(testBlockNum) {
 		t.Fatalf("BlockNumber returned wrong number: %d", blockNumber)
 	}
 	// Get current block by number
@@ -507,3 +619,44 @@ func sendTransaction(ec *Client) error {
 	// Send transaction
 	return ec.SendTransaction(context.Background(), signedTx)
 }
+
+func testDiffAccounts(t *testing.T, client *rpc.Client) {
+	ec := NewClient(client)
+	ctx, cancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)
+	defer cancel()
+
+	for _, testBlock := range testBlocks {
+		if testBlock.blockNr == 10 {
+			continue
+		}
+		diffAccounts, err := ec.GetDiffAccounts(ctx, big.NewInt(int64(testBlock.blockNr)))
+		if err != nil {
+			t.Fatalf("unexpected error: %v", err)
+		}
+
+		accounts := make([]common.Address, 0)
+		for _, tx := range testBlock.txs {
+			// tx.to should be in the accounts list.
+			for idx, account := range diffAccounts {
+				if tx.to == account {
+					break
+				}
+
+				if idx == len(diffAccounts)-1 {
+					t.Fatalf("address(%v) expected in the diff account list, but not", tx.to)
+				}
+			}
+
+			accounts = append(accounts, tx.to)
+		}
+
+		diffDetail, err := ec.GetDiffAccountsWithScope(ctx, big.NewInt(int64(testBlock.blockNr)), accounts)
+		if err != nil {
+			t.Fatalf("get diff accounts in block error: %v", err)
+		}
+		// No contract deposit tx, so expect empty transactions.
+		if len(diffDetail.Transactions) != 0 {
+			t.Fatalf("expect ignore all transactions, but some transaction has recorded")
+		}
+	}
+}

+ 106 - 0
internal/ethapi/api.go

@@ -35,6 +35,7 @@ import (
 	"github.com/ethereum/go-ethereum/common/gopool"
 	"github.com/ethereum/go-ethereum/common/hexutil"
 	"github.com/ethereum/go-ethereum/common/math"
+	"github.com/ethereum/go-ethereum/consensus"
 	"github.com/ethereum/go-ethereum/consensus/clique"
 	"github.com/ethereum/go-ethereum/consensus/ethash"
 	"github.com/ethereum/go-ethereum/core"
@@ -1086,6 +1087,111 @@ 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())
+}
+
+// GetDiffAccountsWithScope returns detailed changes of some interested accounts in a specific block number.
+func (s *PublicBlockChainAPI) GetDiffAccountsWithScope(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])
+				if diffTx.Accounts[account].Cmp(big.NewInt(0)) == 0 {
+					delete(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

+ 1 - 0
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

+ 4 - 0
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
 }