Skip to content
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

Implement eth_subscribe newHeads #16318

Merged
merged 2 commits into from
Dec 12, 2022
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
119 changes: 116 additions & 3 deletions browser/brave_wallet/ethereum_provider_impl_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,12 @@ class TestEventsListener : public brave_wallet::mojom::EventsListener {
accounts_changed_fired_ = true;
}

void MessageEvent(const std::string& subscription_id,
base::Value result) override {
message_event_fired_ = true;
last_message_ = std::move(result);
}

bool ChainChangedFired() const {
base::RunLoop().RunUntilIdle();
return chain_changed_fired_;
Expand All @@ -154,11 +160,21 @@ class TestEventsListener : public brave_wallet::mojom::EventsListener {
return accounts_changed_fired_;
}

bool MessageEventFired() const {
base::RunLoop().RunUntilIdle();
return message_event_fired_;
}

std::string GetChainId() const {
base::RunLoop().RunUntilIdle();
return chain_id_;
}

base::Value GetLastMessage() const {
base::RunLoop().RunUntilIdle();
return last_message_.Clone();
}

std::vector<std::string> GetLowercaseAccounts() const {
base::RunLoop().RunUntilIdle();
return lowercase_accounts_;
Expand All @@ -173,14 +189,18 @@ class TestEventsListener : public brave_wallet::mojom::EventsListener {
lowercase_accounts_.clear();
chain_changed_fired_ = false;
accounts_changed_fired_ = false;
message_event_fired_ = false;
EXPECT_FALSE(ChainChangedFired());
EXPECT_FALSE(AccountsChangedFired());
EXPECT_FALSE(MessageEventFired());
}

bool chain_changed_fired_ = false;
bool accounts_changed_fired_ = false;
bool message_event_fired_ = false;
std::vector<std::string> lowercase_accounts_;
std::string chain_id_;
base::Value last_message_;

private:
mojo::Receiver<brave_wallet::mojom::EventsListener> observer_receiver_{this};
Expand All @@ -189,7 +209,9 @@ class TestEventsListener : public brave_wallet::mojom::EventsListener {
class EthereumProviderImplUnitTest : public testing::Test {
public:
EthereumProviderImplUnitTest()
: shared_url_loader_factory_(
: browser_task_environment_(
base::test::TaskEnvironment::TimeSource::MOCK_TIME),
shared_url_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&url_loader_factory_)) {}

Expand Down Expand Up @@ -875,6 +897,8 @@ class EthereumProviderImplUnitTest : public testing::Test {
raw_ptr<JsonRpcService> json_rpc_service_ = nullptr;
raw_ptr<BraveWalletService> brave_wallet_service_ = nullptr;
std::unique_ptr<TestEventsListener> observer_;
network::TestURLLoaderFactory url_loader_factory_;
std::unique_ptr<EthereumProviderImpl> provider_;

private:
std::unique_ptr<ScopedTestingLocalState> local_state_;
Expand All @@ -883,8 +907,6 @@ class EthereumProviderImplUnitTest : public testing::Test {
raw_ptr<TxService> tx_service_;
raw_ptr<AssetRatioService> asset_ratio_service_;
std::unique_ptr<content::TestWebContents> web_contents_;
std::unique_ptr<EthereumProviderImpl> provider_;
network::TestURLLoaderFactory url_loader_factory_;
data_decoder::test::InProcessDataDecoder in_process_data_decoder_;
scoped_refptr<network::SharedURLLoaderFactory> shared_url_loader_factory_;
base::ScopedTempDir temp_dir_;
Expand Down Expand Up @@ -1964,6 +1986,97 @@ TEST_F(EthereumProviderImplUnitTest, AccountsChangedEvent) {
EXPECT_FALSE(observer_->AccountsChangedFired());
}

TEST_F(EthereumProviderImplUnitTest, EthSubscribe) {
CreateWallet();

// Unsupported subscription type
std::string request_payload_json =
R"({"id":1,"jsonrpc:": "2.0","method":"eth_subscribe",
"params": ["foo"]})";
absl::optional<base::Value> request_payload = base::JSONReader::Read(
request_payload_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
base::JSONParserOptions::JSON_PARSE_RFC);
auto response = CommonRequestOrSendAsync(request_payload.value());

mojom::ProviderError error_code;
std::string error_message;
GetErrorCodeMessage(std::move(response.second), &error_code, &error_message);
EXPECT_EQ(response.first, true);
EXPECT_EQ(error_code, mojom::ProviderError::kInternalError);
EXPECT_EQ(error_message,
l10n_util::GetStringUTF8(IDS_WALLET_UNSUPPORTED_SUBSCRIPTION_TYPE));

url_loader_factory_.SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
url_loader_factory_.ClearResponses();

std::string header_value;
EXPECT_TRUE(request.headers.GetHeader("X-Eth-Method", &header_value));
std::string content;
if (header_value == "eth_blockNumber" ||
header_value == "getBlockHeight")
content = R"({"id":1,"jsonrpc":"2.0","result":"0x131131"})";
else if (header_value == "eth_getBlockByNumber")
content = R"({"id":1,"jsonrpc":"2.0","result":{"difficulty":"0x1"}})";
url_loader_factory_.AddResponse(request.url.spec(), content);
}));

// Subscribing to newHeads
request_payload_json =
R"({"id":1,"jsonrpc:": "2.0","method":"eth_subscribe",
"params": ["newHeads"]})";
request_payload = base::JSONReader::Read(
request_payload_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
base::JSONParserOptions::JSON_PARSE_RFC);
response = CommonRequestOrSendAsync(request_payload.value());
EXPECT_EQ(response.first, false);
EXPECT_TRUE(response.second.is_string());
std::string first_subscription = *response.second.GetIfString();
browser_task_environment_.FastForwardBy(
base::Seconds(kBlockTrackerDefaultTimeInSeconds));
EXPECT_TRUE(observer_->MessageEventFired());
base::Value rv = observer_->GetLastMessage();
ASSERT_TRUE(rv.is_dict());
auto& dict = rv.GetDict();
std::string* difficulty = dict.FindString("difficulty");
ASSERT_TRUE(difficulty);
EXPECT_EQ(*difficulty, "0x1");
darkdh marked this conversation as resolved.
Show resolved Hide resolved

// Make a second subscription
request_payload_json =
R"({"id":1,"jsonrpc:": "2.0","method":"eth_subscribe",
"params": ["newHeads"]})";
request_payload = base::JSONReader::Read(
request_payload_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
base::JSONParserOptions::JSON_PARSE_RFC);
response = CommonRequestOrSendAsync(request_payload.value());
EXPECT_EQ(response.first, false);
EXPECT_TRUE(response.second.is_string());
std::string second_subscription = *response.second.GetIfString();

// The first unsubscribe should not stop the block tracker
request_payload_json = base::StringPrintf(R"({"id":1,"jsonrpc:": "2.0",
"method":"eth_unsubscribe",
"params": ["%s"]})",
first_subscription.c_str());
request_payload = base::JSONReader::Read(
request_payload_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
base::JSONParserOptions::JSON_PARSE_RFC);
response = CommonRequestOrSendAsync(request_payload.value());
EXPECT_TRUE(provider_->eth_block_tracker_.IsRunning());

// The second unsubscribe should stop the block tracker
request_payload_json = base::StringPrintf(R"({"id":1,"jsonrpc:": "2.0",
"method":"eth_unsubscribe",
"params": ["%s"]})",
second_subscription.c_str());
request_payload = base::JSONReader::Read(
request_payload_json, base::JSON_PARSE_CHROMIUM_EXTENSIONS |
base::JSONParserOptions::JSON_PARSE_RFC);
response = CommonRequestOrSendAsync(request_payload.value());
EXPECT_FALSE(provider_->eth_block_tracker_.IsRunning());
}

TEST_F(EthereumProviderImplUnitTest, Web3ClientVersion) {
std::string expected_version = base::StringPrintf(
"BraveWallet/v%s", version_info::GetBraveChromiumVersionNumber().c_str());
Expand Down
81 changes: 81 additions & 0 deletions components/brave_wallet/browser/ethereum_provider_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include "brave/components/brave_wallet/common/web3_provider_constants.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/grit/brave_components_strings.h"
#include "crypto/random.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"

Expand Down Expand Up @@ -88,6 +89,7 @@ EthereumProviderImpl::EthereumProviderImpl(
tx_service_(tx_service),
keyring_service_(keyring_service),
brave_wallet_service_(brave_wallet_service),
eth_block_tracker_(json_rpc_service),
prefs_(prefs),
weak_factory_(this) {
DCHECK(json_rpc_service);
Expand All @@ -105,10 +107,13 @@ EthereumProviderImpl::EthereumProviderImpl(
// Get the current so we can compare for changed events
if (delegate_)
UpdateKnownAccounts();

eth_block_tracker_.AddObserver(this);
}

EthereumProviderImpl::~EthereumProviderImpl() {
host_content_settings_map_->RemoveObserver(this);
eth_block_tracker_.RemoveObserver(this);
}

void EthereumProviderImpl::AddEthereumChain(const std::string& json_payload,
Expand Down Expand Up @@ -569,6 +574,43 @@ void EthereumProviderImpl::RecoverAddress(const std::string& message,
false);
}

void EthereumProviderImpl::EthSubscribe(const std::string& event_type,
RequestCallback callback,
base::Value id) {
if (event_type != kEthSubscribeNewHeads) {
base::Value formed_response = GetProviderErrorDictionary(
mojom::ProviderError::kInternalError,
l10n_util::GetStringUTF8(IDS_WALLET_UNSUPPORTED_SUBSCRIPTION_TYPE));
bool reject = true;
std::move(callback).Run(std::move(id), std::move(formed_response), reject,
"", false);
return;
}
std::vector<uint8_t> bytes(16);
crypto::RandBytes(&bytes.front(), bytes.size());
std::string hex_bytes = ToHex(bytes);
eth_subscriptions_.push_back(hex_bytes);
if (eth_subscriptions_.size() == 1)
eth_block_tracker_.Start(base::Seconds(kBlockTrackerDefaultTimeInSeconds));
std::move(callback).Run(std::move(id), base::Value(hex_bytes), false, "",
false);
}

void EthereumProviderImpl::EthUnsubscribe(const std::string& subscription_id,
RequestCallback callback,
base::Value id) {
auto it = std::find(eth_subscriptions_.begin(), eth_subscriptions_.end(),
subscription_id);
bool found = it != eth_subscriptions_.end();
if (found) {
if (eth_subscriptions_.size() == 1) {
eth_block_tracker_.Stop();
}
eth_subscriptions_.erase(it);
}
std::move(callback).Run(std::move(id), base::Value(found), false, "", false);
}

void EthereumProviderImpl::GetEncryptionPublicKey(const std::string& address,
RequestCallback callback,
base::Value id) {
Expand Down Expand Up @@ -1152,6 +1194,22 @@ void EthereumProviderImpl::CommonRequestOrSendAsync(base::ValueView input_value,
std::move(id), method, delegate_->GetOrigin()));
} else if (method == kWeb3ClientVersion) {
Web3ClientVersion(std::move(callback), std::move(id));
} else if (method == kEthSubscribe) {
std::string event_type;
if (!ParseEthSubscribeParams(normalized_json_request, &event_type)) {
SendErrorOnRequest(error, error_message, std::move(callback),
std::move(id));
return;
}
EthSubscribe(event_type, std::move(callback), std::move(id));
} else if (method == kEthUnsubscribe) {
std::string subscription_id;
if (!ParseEthUnsubscribeParams(normalized_json_request, &subscription_id)) {
SendErrorOnRequest(error, error_message, std::move(callback),
std::move(id));
return;
}
EthUnsubscribe(subscription_id, std::move(callback), std::move(id));
} else {
json_rpc_service_->Request(normalized_json_request, true, std::move(id),
mojom::CoinType::ETH, std::move(callback));
Expand Down Expand Up @@ -1583,4 +1641,27 @@ void EthereumProviderImpl::OnSendRawTransaction(
error != mojom::ProviderError::kSuccess, "", false);
}

// EthBlockTracker::Observer:
void EthereumProviderImpl::OnLatestBlock(uint256_t block_num) {
json_rpc_service_->GetBlockByNumber(
kEthereumBlockTagLatest,
base::BindOnce(&EthereumProviderImpl::OnGetBlockByNumber,
weak_factory_.GetWeakPtr()));
}

void EthereumProviderImpl::OnGetBlockByNumber(
base::Value result,
mojom::ProviderError error,
const std::string& error_message) {
if (events_listener_.is_bound() && error == mojom::ProviderError::kSuccess) {
base::ranges::for_each(eth_subscriptions_,
[this, &result](const std::string& subscription_id) {
events_listener_->MessageEvent(subscription_id,
result.Clone());
});
}
}

void EthereumProviderImpl::OnNewBlock(uint256_t block_num) {}

} // namespace brave_wallet
21 changes: 20 additions & 1 deletion components/brave_wallet/browser/ethereum_provider_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "base/containers/flat_map.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "brave/components/brave_wallet/browser/eth_block_tracker.h"
#include "brave/components/brave_wallet/common/brave_wallet.mojom.h"
#include "brave/components/brave_wallet/common/web3_provider_constants.h"
#include "components/content_settings/core/browser/content_settings_observer.h"
Expand All @@ -39,7 +40,8 @@ class EthereumProviderImpl final
public mojom::JsonRpcServiceObserver,
public mojom::TxServiceObserver,
public brave_wallet::mojom::KeyringServiceObserver,
public content_settings::Observer {
public content_settings::Observer,
public EthBlockTracker::Observer {
public:
using GetAllowedAccountsCallback =
base::OnceCallback<void(const std::vector<std::string>& accounts,
Expand Down Expand Up @@ -84,6 +86,13 @@ class EthereumProviderImpl final
RequestCallback callback,
base::Value id);

void EthSubscribe(const std::string& event_type,
RequestCallback callback,
base::Value id);
void EthUnsubscribe(const std::string& subscription_id,
RequestCallback callback,
base::Value id);

void GetEncryptionPublicKey(const std::string& address,
RequestCallback callback,
base::Value id);
Expand Down Expand Up @@ -160,6 +169,7 @@ class EthereumProviderImpl final
FRIEND_TEST_ALL_PREFIXES(EthereumProviderImplUnitTest, RequestEthCoinbase);
FRIEND_TEST_ALL_PREFIXES(EthereumProviderImplUnitTest,
RequestEthereumPermissionsWithAccounts);
FRIEND_TEST_ALL_PREFIXES(EthereumProviderImplUnitTest, EthSubscribe);
friend class EthereumProviderImplUnitTest;

// mojom::BraveWalletProvider:
Expand Down Expand Up @@ -353,6 +363,13 @@ class EthereumProviderImpl final
const std::string& tx_hash,
mojom::ProviderError error,
const std::string& error_message);
void OnGetBlockByNumber(base::Value result,
mojom::ProviderError,
const std::string&);

// EthBlockTracker::Observer:
void OnLatestBlock(uint256_t block_num) override;
void OnNewBlock(uint256_t block_num) override;

raw_ptr<HostContentSettingsMap> host_content_settings_map_ = nullptr;
std::unique_ptr<BraveWalletProviderDelegate> delegate_;
Expand All @@ -374,6 +391,8 @@ class EthereumProviderImpl final
mojo::Receiver<brave_wallet::mojom::KeyringServiceObserver>
keyring_observer_receiver_{this};
std::vector<std::string> known_allowed_accounts;
std::vector<std::string> eth_subscriptions_;
EthBlockTracker eth_block_tracker_;
bool first_known_accounts_check = true;
PrefService* prefs_ = nullptr;
bool wallet_onboarding_shown_ = false;
Expand Down
Loading