Skip to content

[ETCM-996] Move getAccount and getAccountProof from Blockchain into BlockchainReader #1075

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 2, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ object FastSyncItSpecUtils {
val accountExpectedCode = ByteString(i.toByteArray)
val codeHash = kec256(accountExpectedCode)
val accountExpectedStorageAddresses = (i until i + 20).toList
val account = bl.getAccount(accountAddress, blockNumber).get
val account = blockchainReader.getAccount(blockchainReader.getBestBranch(), accountAddress, blockNumber).get
val code = evmCodeStorage.get(codeHash).get
val storedData = accountExpectedStorageAddresses.map { addr =>
ByteUtils.toBigInt(bl.getAccountStorageAt(account.storageRoot, addr, ethCompatibleStorage = true))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import io.iohk.ethereum.jsonrpc.ProofService.StorageProof
import io.iohk.ethereum.jsonrpc.ProofService.StorageProofKey
import io.iohk.ethereum.ledger.InMemoryWorldStateProxy
import io.iohk.ethereum.ledger.InMemoryWorldStateProxyStorage
import io.iohk.ethereum.mpt.MptNode
import io.iohk.ethereum.network.EtcPeerManagerActor.PeerInfo
import io.iohk.ethereum.network.ForkResolver
import io.iohk.ethereum.network.PeerEventBusActor
Expand Down Expand Up @@ -179,8 +178,6 @@ class BlockchainMock(genesisHash: ByteString) extends Blockchain {
override lazy val hash: ByteString = genesisHash
}

override def getAccountProof(address: Address, blockNumber: BigInt): Option[Vector[MptNode]] = None

override def getStorageProofAt(
rootHash: NodeHash,
position: BigInt,
Expand Down
27 changes: 0 additions & 27 deletions src/main/scala/io/iohk/ethereum/domain/Blockchain.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package io.iohk.ethereum.domain

import akka.util.ByteString

import cats.instances.option._
import cats.syntax.flatMap._

import scala.annotation.tailrec

import io.iohk.ethereum.db.dataSource.DataSourceBatchUpdate
Expand All @@ -27,15 +24,6 @@ trait Blockchain {
type S <: Storage[S]
type WS <: WorldStateProxy[WS, S]

/** Get an account for an address and a block number
*
* @param address address of the account
* @param blockNumber the block that determines the state of the account
*/
def getAccount(address: Address, blockNumber: BigInt): Option[Account]

def getAccountProof(address: Address, blockNumber: BigInt): Option[Vector[MptNode]]

/** Get account storage at given position
*
* @param rootHash storage root hash
Expand Down Expand Up @@ -99,21 +87,6 @@ class BlockchainImpl(
override def getLatestCheckpointBlockNumber(): BigInt =
blockchainMetadata.bestKnownBlockAndLatestCheckpoint.get().latestCheckpointNumber

override def getAccount(address: Address, blockNumber: BigInt): Option[Account] =
getAccountMpt(blockNumber) >>= (_.get(address))

override def getAccountProof(address: Address, blockNumber: BigInt): Option[Vector[MptNode]] =
getAccountMpt(blockNumber) >>= (_.getProof(address))

private def getAccountMpt(blockNumber: BigInt): Option[MerklePatriciaTrie[Address, Account]] =
blockchainReader.getBlockHeaderByNumber(blockNumber).map { bh =>
val storage = stateStorage.getBackingStorage(blockNumber)
MerklePatriciaTrie[Address, Account](
rootHash = bh.stateRoot.toArray,
source = storage
)
}

override def getAccountStorageAt(
rootHash: ByteString,
position: BigInt,
Expand Down
35 changes: 35 additions & 0 deletions src/main/scala/io/iohk/ethereum/domain/BlockchainReader.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import io.iohk.ethereum.db.storage.StateStorage
import io.iohk.ethereum.domain.branch.BestBranch
import io.iohk.ethereum.domain.branch.Branch
import io.iohk.ethereum.domain.branch.EmptyBranch
import io.iohk.ethereum.mpt.MerklePatriciaTrie
import io.iohk.ethereum.mpt.MptNode
import io.iohk.ethereum.utils.Logger

Expand Down Expand Up @@ -141,6 +142,31 @@ class BlockchainReader(
case EmptyBranch => false
}

/** Get an account for an address and a block number
*
* @param branch branch for which we want to get the account
* @param address address of the account
* @param blockNumber the block that determines the state of the account
*/
def getAccount(branch: Branch, address: Address, blockNumber: BigInt): Option[Account] = branch match {
case BestBranch(_, tipBlockNumber) =>
if (blockNumber <= tipBlockNumber)
getAccountMpt(blockNumber).flatMap(_.get(address))
else
None
case EmptyBranch => None
}

def getAccountProof(branch: Branch, address: Address, blockNumber: BigInt): Option[Vector[MptNode]] =
branch match {
case BestBranch(_, tipBlockNumber) =>
if (blockNumber <= tipBlockNumber)
getAccountMpt(blockNumber).flatMap(_.getProof(address))
else
None
case EmptyBranch => None
}

/** Allows to query for a block based on it's number
*
* @param number Block number
Expand All @@ -159,6 +185,15 @@ class BlockchainReader(
*/
private def getHashByBlockNumber(number: BigInt): Option[ByteString] =
blockNumberMappingStorage.get(number)

private def getAccountMpt(blockNumber: BigInt): Option[MerklePatriciaTrie[Address, Account]] =
getBlockHeaderByNumber(blockNumber).map { bh =>
val storage = stateStorage.getBackingStorage(blockNumber)
MerklePatriciaTrie[Address, Account](
rootHash = bh.stateRoot.toArray,
source = storage
)
}
}

object BlockchainReader {
Expand Down
6 changes: 4 additions & 2 deletions src/main/scala/io/iohk/ethereum/jsonrpc/EthProofService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,11 +168,13 @@ class EthProofService(
for {
blockNumber <- resolveBlock(block).map(_.block.number)
account <- Either.fromOption(
blockchain.getAccount(address, blockNumber),
blockchainReader.getAccount(blockchainReader.getBestBranch(), address, blockNumber),
noAccount(address, blockNumber)
)
accountProof <- Either.fromOption(
blockchain.getAccountProof(address, blockNumber).map(_.map(asRlpSerializedNode)),
blockchainReader
.getAccountProof(blockchainReader.getBestBranch(), address, blockNumber)
.map(_.map(asRlpSerializedNode)),
noAccountProof(address, blockNumber)
)
storageProof = getStorageProof(account, storageKeys)
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/io/iohk/ethereum/jsonrpc/EthUserService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ class EthUserService(
Task {
resolveBlock(blockParam)
.map { case ResolvedBlock(block, _) =>
blockchain
.getAccount(address, block.header.number)
blockchainReader
.getAccount(blockchainReader.getBestBranch(), address, block.header.number)
.getOrElse(Account.empty(blockchainConfig.accountStartNonce))
}
.map(makeResponse)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import io.iohk.ethereum.crypto
import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.domain.Account
import io.iohk.ethereum.domain.Address
import io.iohk.ethereum.domain.Blockchain
import io.iohk.ethereum.domain.BlockchainReader
import io.iohk.ethereum.jsonrpc.AkkaTaskOps._
import io.iohk.ethereum.jsonrpc.JsonRpcError._
Expand Down Expand Up @@ -77,7 +76,6 @@ object PersonalService {

class PersonalService(
keyStore: KeyStore,
blockchain: Blockchain,
blockchainReader: BlockchainReader,
txPool: ActorRef,
txPoolConfig: TxPoolConfig,
Expand Down Expand Up @@ -231,7 +229,7 @@ class PersonalService(
}

private def getCurrentAccount(address: Address): Option[Account] =
blockchain.getAccount(address, blockchainReader.getBestBlockNumber())
blockchainReader.getAccount(blockchainReader.getBestBranch(), address, blockchainReader.getBestBlockNumber())

private def getMessageToSign(message: ByteString) = {
val prefixed: Array[Byte] =
Expand Down
12 changes: 10 additions & 2 deletions src/main/scala/io/iohk/ethereum/jsonrpc/TestService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -377,7 +377,11 @@ class TestService(
} else {
val accountBatch: Seq[(ByteString, Address)] = accountHashWithAdresses.view
.dropWhile { case (hash, _) => UInt256(hash) < UInt256(request.parameters.addressHash) }
.filter { case (_, address) => blockchain.getAccount(address, blockOpt.get.header.number).isDefined }
.filter { case (_, address) =>
blockchainReader
.getAccount(blockchainReader.getBestBranch(), address, blockOpt.get.header.number)
.isDefined
}
.take(request.parameters.maxResults + 1)
.to(Seq)

Expand Down Expand Up @@ -414,7 +418,11 @@ class TestService(

(for {
block <- blockOpt.toRight(StorageRangeResponse(complete = false, Map.empty, None))
accountOpt = blockchain.getAccount(Address(request.parameters.address), block.header.number)
accountOpt = blockchainReader.getAccount(
blockchainReader.getBestBranch(),
Address(request.parameters.address),
block.header.number
)
account <- accountOpt.toRight(StorageRangeResponse(complete = false, Map.empty, None))

} yield {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,6 @@ trait PersonalServiceBuilder {

lazy val personalService = new PersonalService(
keyStore,
blockchain,
blockchainReader,
pendingTransactionsManager,
txPoolConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ object StateSyncUtils extends EphemBlockchainTestSetup {
def checkAllDataExists(
nodeData: List[MptNodeData],
blockchain: Blockchain,
blockchainReader: BlockchainReader,
evmCodeStorage: EvmCodeStorage,
blNumber: BigInt
): Boolean = {
Expand All @@ -108,7 +109,8 @@ object StateSyncUtils extends EphemBlockchainTestSetup {
true
} else {
val dataToCheck = remaining.head
val address = blockchain.getAccount(dataToCheck.accountAddress, blNumber)
val address =
blockchainReader.getAccount(blockchainReader.getBestBranch(), dataToCheck.accountAddress, blNumber)
val code = address.flatMap(a => evmCodeStorage.get(a.codeHash))

val storageCorrect = dataToCheck.accountStorage.forall { case (key, value) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class SyncStateSchedulerSpec
"SyncStateScheduler" should "sync with mptTrie with one account (1 leaf node)" in new TestSetup {
val prov = getTrieProvider
val worldHash = prov.buildWorld(Seq(MptNodeData(Address(1), None, Seq(), 20)))
val (syncStateScheduler, _, _, schedulerDb) = buildScheduler()
val (syncStateScheduler, _, _, _, schedulerDb) = buildScheduler()
val initialState = syncStateScheduler.initState(worldHash).get
val (missingNodes, newState) = syncStateScheduler.getMissingNodes(initialState, 1)
val responses = prov.getNodes(missingNodes)
Expand All @@ -57,7 +57,7 @@ class SyncStateSchedulerSpec
val worldHash = prov.buildWorld(
Seq(MptNodeData(Address(1), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20))
)
val (syncStateScheduler, _, _, schedulerDb) = buildScheduler()
val (syncStateScheduler, _, _, _, schedulerDb) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val state1 = exchangeSingleNode(initState, syncStateScheduler, prov).value
val state2 = exchangeSingleNode(state1, syncStateScheduler, prov).value
Expand All @@ -80,7 +80,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20)
)
)
val (syncStateScheduler, _, _, _) = buildScheduler()
val (syncStateScheduler, _, _, _, _) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val stateAfterExchange = exchangeAllNodes(initState, syncStateScheduler, prov)
assert(stateAfterExchange.numberOfPendingRequests == 0)
Expand Down Expand Up @@ -112,7 +112,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3, 4)), Seq((2, 2)), 20)
)
)
val (syncStateScheduler, _, _, schedulerDb) = buildScheduler()
val (syncStateScheduler, _, _, _, schedulerDb) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
assert(schedulerDb.dataSource.storage.isEmpty)
val state1 = exchangeSingleNode(initState, syncStateScheduler, prov).value
Expand Down Expand Up @@ -156,7 +156,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20)
)
)
val (syncStateScheduler, _, _, schedulerDb) = buildScheduler()
val (syncStateScheduler, _, _, _, schedulerDb) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val state1 = exchangeSingleNode(initState, syncStateScheduler, prov).value
val (allMissingNodes1, state2) = syncStateScheduler.getAllMissingNodes(state1)
Expand Down Expand Up @@ -188,7 +188,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20)
)
)
val (syncStateScheduler, _, _, _) = buildScheduler()
val (syncStateScheduler, _, _, _, _) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val (_, state1) = syncStateScheduler.getMissingNodes(initState, 1)
val result1 = syncStateScheduler.processResponse(state1, SyncResponse(ByteString(1), ByteString(2)))
Expand All @@ -205,7 +205,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20)
)
)
val (syncStateScheduler, _, _, _) = buildScheduler()
val (syncStateScheduler, _, _, _, _) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val (firstMissing, state1) = syncStateScheduler.getMissingNodes(initState, 1)
val firstMissingResponse = prov.getNodes(firstMissing)
Expand All @@ -227,7 +227,7 @@ class SyncStateSchedulerSpec
MptNodeData(Address(2), Some(ByteString(1, 2, 3)), Seq((1, 1)), 20)
)
)
val (syncStateScheduler, _, _, _) = buildScheduler()
val (syncStateScheduler, _, _, _, _) = buildScheduler()
val initState = syncStateScheduler.initState(worldHash).get
val (firstMissing, state1) = syncStateScheduler.getMissingNodes(initState, 1)
val firstMissingResponse = prov.getNodes(firstMissing)
Expand All @@ -248,9 +248,11 @@ class SyncStateSchedulerSpec
forAll(nodeDataGen) { nodeData =>
val prov = getTrieProvider
val worldHash = prov.buildWorld(nodeData)
val (scheduler, schedulerBlockchain, schedulerBlockchainWriter, allStorages) = buildScheduler()
val (scheduler, schedulerBlockchain, schedulerBlockchainWriter, schedulerBlockchainReader, allStorages) =
buildScheduler()
val header = Fixtures.Blocks.ValidBlock.header.copy(stateRoot = worldHash, number = 1)
schedulerBlockchainWriter.storeBlockHeader(header).commit()
schedulerBlockchain.saveBestKnownBlocks(1)
var state = scheduler.initState(worldHash).get
while (state.activeRequest.nonEmpty) {
val (allMissingNodes1, state2) = scheduler.getAllMissingNodes(state)
Expand All @@ -263,7 +265,15 @@ class SyncStateSchedulerSpec
assert(finalState.memBatch.isEmpty)
assert(finalState.activeRequest.isEmpty)
assert(finalState.queue.isEmpty)
assert(checkAllDataExists(nodeData, schedulerBlockchain, allStorages.storages.evmCodeStorage, 1))
assert(
checkAllDataExists(
nodeData,
schedulerBlockchain,
schedulerBlockchainReader,
allStorages.storages.evmCodeStorage,
1
)
)
}
}

Expand Down Expand Up @@ -296,6 +306,7 @@ class SyncStateSchedulerSpec
SyncStateScheduler,
BlockchainImpl,
BlockchainWriter,
BlockchainReader,
EphemDataSourceComponent with LocalPruningConfigBuilder with Storages.DefaultStorages
) = {
val freshStorage = getNewStorages
Expand All @@ -314,6 +325,7 @@ class SyncStateSchedulerSpec
),
freshBlockchain,
freshBlockchainWriter,
freshBlockchainReader,
freshStorage
)
}
Expand Down
14 changes: 10 additions & 4 deletions src/test/scala/io/iohk/ethereum/domain/BlockchainSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,9 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
val headerWithAcc = validHeader.copy(stateRoot = ByteString(mptWithAcc.getRootHash))

blockchainWriter.storeBlockHeader(headerWithAcc).commit()
blockchain.saveBestKnownBlocks(headerWithAcc.number)

val retrievedAccount = blockchain.getAccount(address, headerWithAcc.number)
val retrievedAccount = blockchainReader.getAccount(blockchainReader.getBestBranch(), address, headerWithAcc.number)
retrievedAccount shouldEqual Some(account)
}

Expand All @@ -170,17 +171,20 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
val headerWithAcc = validHeader.copy(stateRoot = ByteString(mptWithAcc.getRootHash))

blockchainWriter.storeBlockHeader(headerWithAcc).commit()
blockchain.saveBestKnownBlocks(headerWithAcc.number)

//unhappy path
val wrongAddress = Address(666)
val retrievedAccountProofWrong = blockchain.getAccountProof(wrongAddress, headerWithAcc.number)
val retrievedAccountProofWrong =
blockchainReader.getAccountProof(blockchainReader.getBestBranch(), wrongAddress, headerWithAcc.number)
//the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes that we iterated
retrievedAccountProofWrong.isDefined shouldBe true
retrievedAccountProofWrong.size shouldBe 1
mptWithAcc.get(wrongAddress) shouldBe None

//happy path
val retrievedAccountProof = blockchain.getAccountProof(address, headerWithAcc.number)
val retrievedAccountProof =
blockchainReader.getAccountProof(blockchainReader.getBestBranch(), address, headerWithAcc.number)
retrievedAccountProof.isDefined shouldBe true
retrievedAccountProof.map { proof =>
MptProofVerifier.verifyProof(mptWithAcc.getRootHash, address, proof) shouldBe ValidProof
Expand All @@ -196,9 +200,11 @@ class BlockchainSpec extends AnyFlatSpec with Matchers with ScalaCheckPropertyCh
val headerWithAcc = Fixtures.Blocks.ValidBlock.header.copy(stateRoot = ByteString(mptWithAcc.getRootHash))

blockchainWriter.storeBlockHeader(headerWithAcc).commit()
blockchain.saveBestKnownBlocks(headerWithAcc.number)

val wrongAddress = Address(666)
val retrievedAccountProofWrong = blockchain.getAccountProof(wrongAddress, headerWithAcc.number)
val retrievedAccountProofWrong =
blockchainReader.getAccountProof(blockchainReader.getBestBranch(), wrongAddress, headerWithAcc.number)
//the account doesn't exist, so we can't retrieve it, but we do receive a proof of non-existence with a full path of nodes(root node) that we iterated
(retrievedAccountProofWrong.getOrElse(Vector.empty).toList match {
case _ @HashNode(_) :: Nil => true
Expand Down
Loading