Skip to content
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
133 changes: 35 additions & 98 deletions crates/malachite-app/src/handlers/restream_proposal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ use crate::store::repositories::UndecidedBlocksRepository;
/// Handles the `RestreamProposal` message from the consensus engine.
///
/// This is called when the consensus engine requests to restream a proposal for a specific height and round.
/// If a valid round is provided, the application first fetches the block from the valid round,
/// updates its round to the new round, and stores it. Then, it fetches the
/// block for the specified height and round, streams the proposal parts, and sends them over the network.
/// The block is looked up by height and block hash (ignoring round), so it will be found
/// regardless of which round it was originally stored under. The block's round and valid_round
/// are updated to match the current proposal context before restreaming.
///
/// ## Errors
/// - If no block is found for the specified height and round
Expand Down Expand Up @@ -107,18 +107,17 @@ async fn get_block_to_restream(
valid_round: Round,
block_hash: BlockHash,
) -> eyre::Result<Option<ConsensusBlock>> {
if valid_round.is_defined()
&& let Some(mut block) = undecided_blocks
.get(height, valid_round, block_hash)
.await
.wrap_err_with(|| {
format!(
"Failed to fetch block from valid round for restreaming it \
(height={height}, valid_round={valid_round}, block_hash={block_hash})"
)
})?
{
// Update the block for the new round and store it
let block = undecided_blocks
.get_first(height, block_hash)
.await
.wrap_err_with(|| {
format!(
"Failed to fetch block for restreaming \
(height={height}, round={round}, block_hash={block_hash})"
)
})?;

if let Some(mut block) = block {
block.round = round;
block.valid_round = valid_round;

Expand All @@ -127,24 +126,14 @@ async fn get_block_to_restream(
.await
.wrap_err_with(|| {
format!(
"Failed to store updated undecided block from valid round before restreaming it \
(height={height}, valid_round={valid_round}, block_hash={block_hash})"
)
})?;

Ok(Some(block))
} else {
let block_to_restream = undecided_blocks
.get(height, round, block_hash)
.await
.wrap_err_with(|| {
format!(
"Failed to fetch block from round for restreaming it \
"Failed to store updated block before restreaming \
(height={height}, round={round}, block_hash={block_hash})"
)
})?;

Ok(block_to_restream)
Ok(Some(block))
} else {
Ok(None)
}
}

Expand Down Expand Up @@ -179,25 +168,22 @@ mod tests {
}

#[tokio::test]
async fn get_block_from_valid_round_and_store_update() {
async fn get_block_found_and_updated() {
let mut mock_repo = MockUndecidedBlocksRepository::new();

let height = Height::new(10);
let round = Round::new(5); // The new round we are proposing in
let valid_round = Round::new(3); // The previous round where we saw the block
let round = Round::new(5);
let valid_round = Round::new(3);
let block_hash = BlockHash::default();

// Original block proposed in round 3
let original_block = create_dummy_block(height, valid_round, Round::Nil);
let original_block = create_dummy_block(height, Round::new(0), Round::Nil);

// Expectation: Fetch from valid_round (3)
mock_repo
.expect_get()
.with(eq(height), eq(valid_round), eq(block_hash))
.expect_get_first()
.with(eq(height), eq(block_hash))
.times(1)
.returning(move |_, _, _| Ok(Some(original_block.clone())));
.returning(move |_, _| Ok(Some(original_block.clone())));

// Expectation: Store the block updated with the NEW round (5) and valid_round (3)
mock_repo
.expect_store()
.withf(move |b| b.round == round && b.valid_round == valid_round)
Expand All @@ -207,79 +193,30 @@ mod tests {
let result =
get_block_to_restream(&mock_repo, height, round, valid_round, block_hash).await;

assert!(result.is_ok());
let block = result.unwrap();
assert!(block.is_some());

let b = block.unwrap();
assert_eq!(b.round, round); // Ensure returned block has updated round
assert_eq!(b.valid_round, valid_round);
}

#[tokio::test]
async fn get_block_no_valid_round_fetch_current() {
let mut mock_repo = MockUndecidedBlocksRepository::new();

let height = Height::new(10);
let round = Round::new(5);
let valid_round = Round::Nil; // No valid round
let block_hash = BlockHash::default();

// A block proposed for the first time at round 5, no valid round
let current_block = create_dummy_block(height, round, valid_round);

// Expectation: we are restreaming this block because we received it at round 5.
mock_repo
.expect_get()
.with(eq(height), eq(round), eq(block_hash))
.times(1)
.returning(move |_, _, _| Ok(Some(current_block.clone())));

// Expectation: Store should NOT be called
mock_repo.expect_store().times(0);

let result =
get_block_to_restream(&mock_repo, height, round, valid_round, block_hash).await;

assert!(result.is_ok());
assert_eq!(result.unwrap().unwrap().round, round);
let block = result.unwrap().expect("block should be found");
assert_eq!(block.round, round);
assert_eq!(block.valid_round, valid_round);
}

#[tokio::test]
async fn fallback_when_valid_round_block_missing() {
async fn get_block_not_found() {
let mut mock_repo = MockUndecidedBlocksRepository::new();

let height = Height::new(10);
let round = Round::new(5);
let valid_round = Round::new(3);
let block_hash = BlockHash::default();

let block_at_current = create_dummy_block(height, round, valid_round);

// 1. First fetch at valid_round returns None
// The proposed value can be valid for the proposer but not for us.
mock_repo
.expect_get()
.with(eq(height), eq(valid_round), eq(block_hash))
.times(1)
.returning(|_, _, _| Ok(None));

// 2. Fallback: fetch at current round
// Since it was restreamed by the proposer, we have it as current's round value.
mock_repo
.expect_get()
.with(eq(height), eq(round), eq(block_hash))
.expect_get_first()
.with(eq(height), eq(block_hash))
.times(1)
.returning(move |_, _, _| Ok(Some(block_at_current.clone())));

// Store should NOT be called in the fallback path
mock_repo.expect_store().times(0);
.returning(|_, _| Ok(None));

let result =
get_block_to_restream(&mock_repo, height, round, valid_round, block_hash).await;

assert!(result.is_ok());
assert!(result.unwrap().is_some());
assert!(result.unwrap().is_none());
}

#[tokio::test]
Expand All @@ -290,8 +227,8 @@ mod tests {
let valid_round = Round::Nil;

mock_repo
.expect_get()
.returning(|_, _, _| Err(std::io::Error::other("DB connection failed")));
.expect_get_first()
.returning(|_, _| Err(std::io::Error::other("DB connection failed")));

let result =
get_block_to_restream(&mock_repo, height, round, valid_round, BlockHash::default())
Expand Down
2 changes: 1 addition & 1 deletion crates/quake/src/infra/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ impl RemoteInfra {
.map(|node| self.node_private_ip(node).map(String::as_str))
.collect::<Result<Vec<_>>>()?;

let pssh_cmd = format!("./pssh.sh \"{cmd}\" {}", node_ips.join(" "));
let pssh_cmd = format!("./pssh.sh '{cmd}' {}", node_ips.join(" "));
self.ssh_cc(&pssh_cmd, false)
.wrap_err_with(|| format!("Failed to run '{cmd}' on {nodes:?}"))
}
Expand Down
18 changes: 9 additions & 9 deletions crates/quake/terraform/cc-data.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,23 +39,23 @@ write_files:
# Usage: pssh.sh "command" ip1 ip2 ip3 ...
# Executes command on all IPs in parallel, fails if any command fails.
set -euo pipefail
CMD="$$1"
CMD="$1"
shift
SSH_OPTS="-o StrictHostKeyChecking=no -o LogLevel=ERROR"
USER="${username}"
TMPDIR=$$(mktemp -d)
trap "rm -rf $$TMPDIR" EXIT
TMPDIR=$(mktemp -d)
trap "rm -rf $TMPDIR" EXIT
i=0
for IP in "$$@"; do
(ssh $$SSH_OPTS $$USER@$$IP "$$CMD"; echo $$? > "$$TMPDIR/$$i") &
((i++))
for IP in "$@"; do
(ssh $SSH_OPTS $USER@$IP "$CMD"; echo $? > "$TMPDIR/$i") &
i="$((i+1))"
done
wait
FAILED=0
for f in "$$TMPDIR"/*; do
[ -f "$$f" ] && [ "$$(cat "$$f")" != "0" ] && FAILED=1
for f in "$TMPDIR"/*; do
[ -f "$f" ] && [ "$(cat "$f")" != "0" ] && FAILED=1
done
exit $$FAILED
exit $FAILED
runcmd:
# Set up 4 GiB swap to prevent hard OOM kills (dd instead of fallocate for XFS compatibility)
- dd if=/dev/zero of=/swapfile bs=1M count=4096
Expand Down
72 changes: 72 additions & 0 deletions deployments/blockscout.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
version: '3.9'

name: blockscout

services:
proxy:
image: nginx
container_name: proxy
extra_hosts:
- 'host.docker.internal:host-gateway'
volumes:
- "../deployments/monitoring/config-blockscout/proxy:/etc/nginx/templates:ro"
environment:
BACK_PROXY_PASS: ${BACK_PROXY_PASS:-http://backend:4000}
FRONT_PROXY_PASS: ${FRONT_PROXY_PASS:-http://frontend:3000}
ports:
- target: 80
published: 80

frontend:
image: ghcr.io/blockscout/frontend:${BLOCKSCOUT_FRONTEND_TAG:-latest}
pull_policy: always
restart: always
container_name: 'frontend'
volumes:
- ../deployments/monitoring/config-blockscout/frontend/.curlrc:/home/nextjs/.curlrc:ro
env_file:
- ../deployments/monitoring/config-blockscout/frontend/frontend.env

backend:
image: ghcr.io/blockscout/${BLOCKSCOUT_REPO:-blockscout}:${BLOCKSCOUT_TAG:-latest}
pull_policy: always
restart: always
stop_grace_period: 5m
container_name: 'backend'
command: sh -c "bin/blockscout eval \"Elixir.Explorer.ReleaseTasks.create_and_migrate()\" && bin/blockscout start"
extra_hosts:
- 'host.docker.internal:host-gateway'
env_file:
- ../deployments/monitoring/config-blockscout/backend.env
volumes:
- ../deployments/volumes/blockscout/logs/:/app/logs/
- ../deployments/volumes/blockscout/dets/:/app/dets/

db-init:
image: postgres:17
entrypoint:
- sh
- -c
- |
chown -R 2000:2000 /var/lib/postgresql/data

db:
image: postgres:17
user: 2000:2000
shm_size: 256m
restart: always
container_name: 'db'
command: postgres -c 'max_connections=200' -c 'client_connection_check_interval=60000'
environment:
POSTGRES_DB: 'blockscout'
POSTGRES_USER: 'blockscout'
POSTGRES_HOST_AUTH_METHOD: 'trust'
ports:
- target: 5432
published: 7432
healthcheck:
test: ["CMD-SHELL", "pg_isready -U blockscout -d blockscout"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
35 changes: 35 additions & 0 deletions deployments/monitoring/config-blockscout/backend.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
ETHEREUM_JSONRPC_VARIANT=geth
DATABASE_URL=postgresql://blockscout:@db:5432/blockscout
ETHEREUM_JSONRPC_TRANSPORT=http
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES=false
SECRET_KEY_BASE=0000000000000000000000000000000000000000000000000000000000000000
PORT=4000
COIN_NAME=
COIN=
DISABLE_MARKET=true
POOL_SIZE=80
POOL_SIZE_API=10
ECTO_USE_SSL=false
HEART_BEAT_TIMEOUT=30
RELEASE_LINK=
ADMIN_PANEL_ENABLED=false
API_V1_READ_METHODS_DISABLED=false
API_V1_WRITE_METHODS_DISABLED=false
TXS_STATS_DAYS_TO_COMPILE_AT_INIT=10
COIN_BALANCE_HISTORY_DAYS=90
RE_CAPTCHA_DISABLED=false
MICROSERVICE_SC_VERIFIER_TYPE=eth_bytecode_db
MICROSERVICE_VISUALIZE_SOL2UML_ENABLED=true
MICROSERVICE_VISUALIZE_SOL2UML_URL=http://visualizer:8050/
MICROSERVICE_SIG_PROVIDER_ENABLED=true
MICROSERVICE_SIG_PROVIDER_URL=http://sig-provider:8050/
MICROSERVICE_ACCOUNT_ABSTRACTION_URL=http://user-ops-indexer:8050/
DECODE_NOT_A_CONTRACT_CALLS=true
ACCOUNT_CLOAK_KEY=
ACCOUNT_ENABLED=false
ACCOUNT_REDIS_URL=redis://redis-db:6379
NFT_MEDIA_HANDLER_ENABLED=true
NFT_MEDIA_HANDLER_REMOTE_DISPATCHER_NODE_MODE_ENABLED=true
RELEASE_NODE=producer@172.18.0.4
RELEASE_DISTRIBUTION=name
RELEASE_COOKIE=secret_cookie
12 changes: 9 additions & 3 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ https://github.com/circlefin/arc-node repository:
git clone https://github.com/circlefin/arc-node.git
cd arc-node
git checkout $VERSION
git submodule update --init --recursive
```

`$VERSION` is a tag for a released version.
Expand All @@ -43,9 +42,17 @@ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
```

With Rust installed, install the dependencies for your operating system:

- **Ubuntu:** `sudo apt-get install libclang-dev pkg-config build-essential`
- **macOS:** `brew install llvm pkg-config`
- **Windows:** `choco install llvm` or `winget install LLVM.LLVM`

These are needed to build bindings for Arc node execution's database.

**3. Build and install:**

The following commands produce three Arc node binaries:
The following commands produce three Arc node binaries:
`arc-node-execution`, `arc-node-consensus`, and `arc-snapshots`:

```sh
Expand All @@ -67,4 +74,3 @@ arc-snapshots --version
arc-node-execution --version
arc-node-consensus --version
```

Loading
Loading