Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ machine-snapshot/**
/cartesi-rollups-claimer
/cartesi-rollups-jsonrpc-api
/cartesi-rollups-prt
/cartesi-rollups-machine-tool
/rollups-contracts
/rollups-prt-contracts
/applications
Expand Down
66 changes: 62 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ DOCKER_PLATFORM=--platform $(BUILD_PLATFORM)
endif

# Go artifacts
GO_ARTIFACTS := $(addprefix cartesi-rollups-,node cli evm-reader advancer validator claimer jsonrpc-api prt)
GO_ARTIFACTS := $(addprefix cartesi-rollups-,node cli evm-reader advancer validator claimer jsonrpc-api prt machine-tool)

# fixme(vfusco): path on all oses
CGO_CFLAGS:= -I$(PREFIX)/include
Expand Down Expand Up @@ -121,14 +121,16 @@ env:
@echo export CARTESI_LOG_LEVEL="info"
@echo export CARTESI_BLOCKCHAIN_DEFAULT_BLOCK="latest"
@echo export CARTESI_BLOCKCHAIN_HTTP_ENDPOINT="http://localhost:8545"
@echo export CARTESI_BLOCKCHAIN_WS_ENDPOINT="ws://localhost:8545"
@echo export CARTESI_BLOCKCHAIN_POLLING_INTERVAL="1"
@echo export CARTESI_BLOCKCHAIN_ID="31337"
@echo export CARTESI_CONTRACTS_INPUT_BOX_ADDRESS="0x346B3df038FE9f8380071eC6514D5a83aD143939"
@echo export CARTESI_CONTRACTS_AUTHORITY_FACTORY_ADDRESS="0x3C1FE01c542a88A523FF6847eD1E26176c8C4ED0"
@echo export CARTESI_CONTRACTS_QUORUM_FACTORY_ADDRESS="0x1f94009389F408B8D0ADfFcF8BBDCe5552BaCa5F"
@echo export CARTESI_CONTRACTS_APPLICATION_FACTORY_ADDRESS="0xC549F89cF1ca43eDDECC64Ac2208F4b283B1c483"
@echo export CARTESI_CONTRACTS_SELF_HOSTED_APPLICATION_FACTORY_ADDRESS="0x6145C5996a71a379E030aEb0440df79D60833418"
@echo export CARTESI_CONTRACTS_DAVE_APP_FACTORY_ADDRESS="0x33FFf0b681c90664dD048a60400AE2D827a4c5bb"
@echo export CARTESI_DEVNET_ERC20_PORTAL_ADDRESS="0x22E57511C30CcE6CDaa742E13CE3b774fDC663b1"
@echo export CARTESI_DEVNET_TEST_ERC20_ADDRESS="0x88A2120B7068E78692C8fd12E751d610B6377E4d"
@echo export CARTESI_DEVNET_WITHDRAWAL_OUTPUT_BUILDER_ADDRESS="0x0745787835A019cd4dae8EDB541Fbc0647793d63"
@echo export CARTESI_AUTH_MNEMONIC=\"test test test test test test test test test test test junk\"
@echo export CARTESI_DATABASE_CONNECTION="postgres://postgres:password@localhost:5432/rollupsdb?sslmode=disable"
Expand Down Expand Up @@ -295,6 +297,8 @@ reject-loop-dapp: applications/reject-loop-dapp ## Reject loop dapp

exception-loop-dapp: applications/exception-loop-dapp ## Exception loop dapp

erc20-withdrawal-dapp: applications/erc20-withdrawal-dapp ## ERC-20 withdrawal test dapp

applications/reject-loop-dapp: ## Create reject-loop-dapp test application
@echo "Creating reject-loop-dapp test application"
@mkdir -p applications
Expand All @@ -305,6 +309,18 @@ applications/exception-loop-dapp: ## Create exception-loop-dapp test application
@mkdir -p applications
@cartesi-machine --ram-length=128Mi --store=applications/exception-loop-dapp --final-hash -- ioctl-echo-loop --vouchers=1 --notices=1 --reports=1 --exception=1 --verbose=1

applications/erc20-withdrawal-dapp: test/dapps/erc20-withdrawal/install.sh ## Create ERC-20 withdrawal test application
@echo "Creating ERC-20 withdrawal test application"
@mkdir -p applications
@PORTAL=$${CARTESI_DEVNET_ERC20_PORTAL_ADDRESS:-0x22E57511C30CcE6CDaa742E13CE3b774fDC663b1}; \
TOKEN=$${CARTESI_DEVNET_TEST_ERC20_ADDRESS:-0x88A2120B7068E78692C8fd12E751d610B6377E4d}; \
cartesi-machine --ram-length=128Mi \
--flash-drive=label:accounts,length:4Mi,mke2fs:false,mount:false,user:dapp \
--env=TRUSTED_ERC20_PORTAL=$$PORTAL \
--env=TRUSTED_ERC20_TOKEN=$$TOKEN \
--append-init-file=test/dapps/erc20-withdrawal/install.sh \
--store=applications/erc20-withdrawal-dapp --final-hash -- /usr/local/bin/erc20-withdrawal-dapp

deploy-echo-dapp: applications/echo-dapp ## Deploy echo-dapp test application
@echo "Deploying echo-dapp test application"
@./cartesi-rollups-cli deploy application echo-dapp applications/echo-dapp/
Expand All @@ -321,6 +337,46 @@ deploy-prt-echo-dapp: applications/echo-dapp ## Deploy echo-dapp test applicatio
@echo "Deploying echo-dapp test application"
@./cartesi-rollups-cli deploy application prt-echo-dapp applications/echo-dapp/ --prt

deploy-erc20-withdrawal-dapp: applications/erc20-withdrawal-dapp ## Deploy ERC-20 withdrawal test application
@set -e; \
APP=$${APP:-erc20-withdrawal-dapp}; \
GUARDIAN=$${GUARDIAN:-0x70997970C51812dc3A010C7d01b50e0d17dc79C8}; \
BUILDER=$${CARTESI_DEVNET_WITHDRAWAL_OUTPUT_BUILDER_ADDRESS:-0x0745787835A019cd4dae8EDB541Fbc0647793d63}; \
DRIVE_START_INDEX=$$(jq -r '.config.flash_drive[] | select(.length == 4194304) | (.start / 4194304 | floor)' \
applications/erc20-withdrawal-dapp/config.json); \
WITHDRAWAL_CONFIG=$$(jq -cn \
--arg guardian "$$GUARDIAN" \
--arg builder "$$BUILDER" \
--argjson drive "$$DRIVE_START_INDEX" \
'{guardian:$$guardian,log2_leaves_per_account:0,log2_max_num_of_accounts:17,accounts_drive_start_index:$$drive,withdrawal_output_builder:$$builder}'); \
echo "Deploying $$APP with accounts-drive start index $$DRIVE_START_INDEX"; \
./cartesi-rollups-cli deploy application "$$APP" applications/erc20-withdrawal-dapp \
--salt "$$(openssl rand -hex 32)" \
--withdrawal-config "$$WITHDRAWAL_CONFIG" \
--enable=false; \
./cartesi-rollups-cli app execution-parameters set "$$APP" snapshot_policy EVERY_EPOCH; \
./cartesi-rollups-cli app status "$$APP" enabled --yes

fund-wallet: ## Fund the default Anvil wallet with ETH and test ERC-20
@set -e; \
RPC_URL=$${CARTESI_BLOCKCHAIN_HTTP_ENDPOINT:-http://localhost:8545}; \
TOKEN=$${CARTESI_DEVNET_TEST_ERC20_ADDRESS:-0x88A2120B7068E78692C8fd12E751d610B6377E4d}; \
WALLET=$${WALLET:-$$(cast rpc --rpc-url "$$RPC_URL" eth_accounts | jq -r '.[0]')}; \
ETH_WEI=$${ETH_WEI:-0x8ac7230489e80000}; \
TOKEN_AMOUNT=$${TOKEN_AMOUNT:-1000000}; \
echo "Funding $$WALLET with $$ETH_WEI wei"; \
cast rpc --rpc-url "$$RPC_URL" anvil_setBalance "$$WALLET" "$$ETH_WEI" >/dev/null; \
echo "Minting $$TOKEN_AMOUNT test ERC-20 units to $$WALLET"; \
cast send --rpc-url "$$RPC_URL" --from "$$WALLET" --unlocked "$$TOKEN" "mint(uint256)" "$$TOKEN_AMOUNT" >/dev/null

withdraw-wallet: cartesi-rollups-cli ## Send a test withdrawal request from the current signer
@set -e; \
APP=$${APP:-erc20-withdrawal-dapp}; \
AMOUNT=$${AMOUNT:-25}; \
PAYLOAD=$$(printf '0x01%016x' "$$AMOUNT"); \
echo "Sending withdrawal request to $$APP: amount=$$AMOUNT payload=$$PAYLOAD"; \
./cartesi-rollups-cli send "$$APP" "$$PAYLOAD" --hex --yes --json

# Temporary test dependencies target while we are not using distribution packages
DOWNLOADS_DIR = test/downloads
CARTESI_TEST_MACHINE_IMAGES = $(DOWNLOADS_DIR)/linux.bin
Expand Down Expand Up @@ -449,7 +505,7 @@ test-with-compose: ## Run all tests using docker compose with auto-shutdown
@$(MAKE) unit-test-with-compose
@$(MAKE) integration-test-with-compose

integration-test-local: build echo-dapp reject-loop-dapp exception-loop-dapp ## Run integration tests locally (requires: make start && eval $$(make env))
integration-test-local: build cartesi-rollups-machine-tool echo-dapp reject-loop-dapp exception-loop-dapp erc20-withdrawal-dapp ## Run integration tests locally (requires: make start && eval $$(make env))
@cartesi-rollups-cli db init
@if lsof -ti:10000 >/dev/null 2>&1; then \
echo "Killing stale node on port 10000..."; \
Expand All @@ -459,6 +515,7 @@ integration-test-local: build echo-dapp reject-loop-dapp exception-loop-dapp ##
@export CARTESI_TEST_DAPP_PATH=$(CURDIR)/applications/echo-dapp; \
export CARTESI_TEST_REJECT_DAPP_PATH=$(CURDIR)/applications/reject-loop-dapp; \
export CARTESI_TEST_EXCEPTION_DAPP_PATH=$(CURDIR)/applications/exception-loop-dapp; \
export CARTESI_TEST_ERC20_WITHDRAWAL_DAPP_PATH=$(CURDIR)/applications/erc20-withdrawal-dapp; \
$(MAKE) integration-test

deploy-load-test-apps: applications/echo-dapp ## Deploy 3 echo-dapp instances for load testing
Expand Down Expand Up @@ -516,7 +573,7 @@ build-debian-package: install
dpkg-deb -Zxz --root-owner-group --build $(DESTDIR) $(DEB_FILENAME)

.PHONY: \
build build-go $(GO_ARTIFACTS) \
build build-go $(GO_ARTIFACTS) cartesi-rollups-machine-tool \
clean clean-go clean-contracts clean-docs clean-devnet-files clean-dapps clean-test-dependencies clean-debian-packages \
test unit-test unit-test-with-compose integration-test integration-test-with-compose integration-test-local test-with-compose ci-test coverage-report \
generate generate-contracts generate-config generate-inspect check-generate generate-db \
Expand All @@ -525,4 +582,5 @@ build-debian-package: install
devnet image tester-image debian-packager run-with-compose shutdown-compose \
start start-devnet start-postgres stop stop-devnet stop-postgres restart restart-devnet restart-postgres \
install copy-debian-package build-debian-package \
deploy-erc20-withdrawal-dapp fund-wallet withdraw-wallet \
env help version
18 changes: 14 additions & 4 deletions api/openapi/inspect.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,24 @@ paths:

"503":
description: |
The application is registered but its Cartesi Machine instance
has not been initialized yet. If the application is in the
enabled state, the machine will be available once the advancer
service completes initialization.
The inspect request cannot be served right now.

This can happen when the application is registered but its
Cartesi Machine instance has not been initialized yet, when the
application has been foreclosed and its machine is no longer
available for live inspect requests, or when the application's
inspect capacity is exhausted.
content:
text/plain:
schema:
$ref: "#/components/schemas/Error"
examples:
machineNotReady:
value: Machine not ready
foreclosed:
value: Application was foreclosed; machine unavailable
atCapacity:
value: Application inspect at capacity

default:
description: Error response.
Expand Down
151 changes: 128 additions & 23 deletions cmd/cartesi-rollups-cli/root/app/register/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@ import (
"github.com/cartesi/rollups-node/internal/model"
"github.com/cartesi/rollups-node/internal/repository/factory"
"github.com/cartesi/rollups-node/pkg/contracts/dataavailability"
"github.com/cartesi/rollups-node/pkg/contracts/iquorum"
"github.com/cartesi/rollups-node/pkg/ethutil"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -48,6 +50,7 @@ var (
templatePath string
templateHash string
epochLength uint64
claimStagingPeriod uint64
inputBoxBlockNumber uint64
inputBoxAddressFromEnv bool
dataAvailability string
Expand Down Expand Up @@ -80,14 +83,19 @@ func init() {
"Consensus Epoch length. (DO NOT USE IN PRODUCTION)\nThis value is retrieved from the consensus contract",
)

Cmd.Flags().Uint64Var(&claimStagingPeriod, "claim-staging-period", 0,
"Consensus claim staging period in blocks (Authority/Quorum only). "+
"(DO NOT USE IN PRODUCTION)\nThis value is retrieved from the consensus contract",
)

Cmd.Flags().StringVarP(&dataAvailability, "data-availability", "D", "",
"Application ABI encoded Data Availability. If not provided, it will be read from the InputBox Address",
)

Cmd.Flags().BoolVar(&inputBoxAddressFromEnv, "inputbox-from-env", false, "Read Input Box contract address from environment")
Cmd.Flags().Uint64Var(&inputBoxBlockNumber, "inputbox-block-number", 0, "InputBox deployment block number")

Cmd.Flags().BoolVarP(&disabled, "disabled", "d", false, "Sets the application state to disabled")
Cmd.Flags().BoolVarP(&disabled, "disabled", "d", false, "Registers the application with enabled=false")

Cmd.Flags().BoolVarP(&printAsJSON, "print-json", "j", false, "Prints the application data as JSON")

Expand Down Expand Up @@ -123,10 +131,7 @@ func run(cmd *cobra.Command, args []string) {
cobra.CheckErr(err)
defer repo.Close()

applicationState := model.ApplicationState_Enabled
if disabled {
applicationState = model.ApplicationState_Disabled
}
applicationEnabled := !disabled

address := common.HexToAddress(applicationAddress)

Expand Down Expand Up @@ -175,6 +180,20 @@ func run(cmd *cobra.Command, args []string) {
}
}

if !cmd.Flags().Changed("claim-staging-period") && !applicationTypePRT {
claimStagingPeriod, err = getClaimStagingPeriod(ctx, consensus)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to get claim staging period from consensus: %v\n", err)
os.Exit(1)
}
}

withdrawalConfig, err := readApplicationWithdrawalConfig(ctx, address)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to read withdrawal config from application: %v\n", err)
os.Exit(1)
}

inputBoxAddress, encodedDA, err := processDataAvailability(
ctx,
address,
Expand Down Expand Up @@ -203,27 +222,34 @@ func run(cmd *cobra.Command, args []string) {
os.Exit(1)
}

consensusType := model.Consensus_Authority
if applicationTypePRT {
consensusType = model.Consensus_PRT
consensusType, err := getConsensusType(ctx, consensus, applicationTypePRT)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to detect consensus type: %v\n", err)
os.Exit(1)
}

application := model.Application{
Name: validName,
IApplicationAddress: address,
IConsensusAddress: consensus,
IInputBoxAddress: *inputBoxAddress,
TemplateURI: templatePath,
TemplateHash: parsedTemplateHash,
EpochLength: epochLength,
DataAvailability: encodedDA,
ConsensusType: consensusType,
State: applicationState,
IInputBoxBlock: inputBoxBlockNumber,
LastEpochCheckBlock: 0,
LastInputCheckBlock: 0,
LastOutputCheckBlock: 0,
LastTournamentCheckBlock: 0,
Name: validName,
IApplicationAddress: address,
IConsensusAddress: consensus,
IInputBoxAddress: *inputBoxAddress,
TemplateURI: templatePath,
TemplateHash: parsedTemplateHash,
EpochLength: epochLength,
ClaimStagingPeriod: claimStagingPeriod,
WithdrawalConfig: withdrawalConfig,
DataAvailability: encodedDA,
ConsensusType: consensusType,
Enabled: applicationEnabled,
Status: model.ApplicationStatus_OK,
IInputBoxBlock: inputBoxBlockNumber,
LastEpochCheckBlock: 0,
LastInputCheckBlock: 0,
LastOutputCheckBlock: 0,
LastTournamentCheckBlock: 0,
LastForecloseCheckBlock: 0,
LastAccountsDriveProvedCheckBlock: 0,
LastWithdrawalCheckBlock: 0,
}

// load execution parameters from a file?
Expand Down Expand Up @@ -320,6 +346,85 @@ func getEpochLength(
return ethutil.GetEpochLength(ctx, client, consensusAddr)
}

func getClaimStagingPeriod(
ctx context.Context,
consensusAddr common.Address,
) (uint64, error) {
ethEndpoint, err := config.GetBlockchainHttpEndpoint()
if err != nil {
return 0, fmt.Errorf("failed to get blockchain http endpoint address: %w", err)
}
client, err := ethclient.Dial(ethEndpoint.Raw())
if err != nil {
return 0, fmt.Errorf("failed to connect to the blockchain http endpoint: %s", ethEndpoint)
}
return ethutil.GetClaimStagingPeriod(ctx, client, consensusAddr)
}

type quorumConsensusProbe interface {
NumOfValidators(opts *bind.CallOpts) (*big.Int, error)
}

func getConsensusType(
ctx context.Context,
consensusAddr common.Address,
applicationTypePRT bool,
) (model.Consensus, error) {
if applicationTypePRT {
return model.Consensus_PRT, nil
}

ethEndpoint, err := config.GetBlockchainHttpEndpoint()
if err != nil {
return "", fmt.Errorf("failed to get blockchain http endpoint address: %w", err)
}
client, err := ethclient.DialContext(ctx, ethEndpoint.Raw())
if err != nil {
return "", fmt.Errorf("failed to connect to the blockchain http endpoint: %s", ethEndpoint)
}
quorum, err := iquorum.NewIQuorum(consensusAddr, client)
if err != nil {
return "", err
}
return consensusTypeFromQuorumProbe(applicationTypePRT, quorum)
}

func consensusTypeFromQuorumProbe(
applicationTypePRT bool,
probe quorumConsensusProbe,
) (model.Consensus, error) {
if applicationTypePRT {
return model.Consensus_PRT, nil
}
numOfValidators, err := probe.NumOfValidators(nil)
if err != nil {
return model.Consensus_Authority, nil
}
if numOfValidators == nil || numOfValidators.Sign() == 0 {
return "", fmt.Errorf("quorum consensus reports zero validators")
}
return model.Consensus_Quorum, nil
}

func readApplicationWithdrawalConfig(
ctx context.Context,
appAddr common.Address,
) (model.WithdrawalConfig, error) {
ethEndpoint, err := config.GetBlockchainHttpEndpoint()
if err != nil {
return model.WithdrawalConfig{}, fmt.Errorf("failed to get blockchain http endpoint address: %w", err)
}
client, err := ethclient.Dial(ethEndpoint.Raw())
if err != nil {
return model.WithdrawalConfig{}, fmt.Errorf("failed to connect to the blockchain http endpoint: %s", ethEndpoint)
}
wc, err := ethutil.GetApplicationWithdrawalConfig(ctx, client, appAddr)
if err != nil {
return model.WithdrawalConfig{}, err
}
return model.WithdrawalConfig(wc), nil
}

func getInputBoxDeploymentBlock(
ctx context.Context,
inputBoxAddress common.Address,
Expand Down
Loading
Loading