Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
76654be
Initial Crank: Error including cron_plugin
brianjohnson5972 Mar 2, 2026
429c899
Initial Crank: Needed changes for client
brianjohnson5972 Mar 20, 2026
f2ac82c
Initial Crank: Adding DepositManager and WithdrawalQueue contracts
brianjohnson5972 Mar 20, 2026
dff465d
Initial Crank: Working
brianjohnson5972 Mar 25, 2026
0fc09cc
Initial Crank: Fixed last errors
brianjohnson5972 Mar 30, 2026
04f8348
Initial Crank: Simplified code
brianjohnson5972 Mar 31, 2026
0402d22
Initial Crank: Added unit tests
brianjohnson5972 Mar 31, 2026
92bb896
Initial Crank: Fix to prevent ethereum_client::identify_block_for_tra…
brianjohnson5972 Apr 2, 2026
e1c9aa3
Initial Crank: Fixed issue with http::read blocking indefinitely.
brianjohnson5972 Apr 2, 2026
24d6374
Initial Crank: Added initialization for curl.
brianjohnson5972 Apr 2, 2026
09cd13c
Initial Crank: Fixed tracking default schedule and updated README.md.
brianjohnson5972 Apr 2, 2026
8f20ec5
Apply suggestions from code review
brianjohnson5972 Apr 2, 2026
b56d806
Initial Crank: Fixed remaining PR comments from Kevin.
brianjohnson5972 Apr 10, 2026
f92c2c6
Initial Crank: Fixed remaining PR comments from Kevin and Jonathan.
brianjohnson5972 Apr 13, 2026
d8b7d9e
Initial Crank: Refactored retry logic into cron_service to prevent de…
brianjohnson5972 Apr 13, 2026
8907468
Initial Crank: Added caching the last nonce to ensure that the client…
brianjohnson5972 Apr 15, 2026
70612d2
Initial Crank: Changed retry method to blocking_retry for clearity.
brianjohnson5972 Apr 15, 2026
7ddd427
Initial Crank: Refactoring of beacon_chain_update_plugin and other ch…
brianjohnson5972 Apr 17, 2026
92634ce
Initial Crank: Renaming of beacon_chain_update_plugin and other PR ch…
brianjohnson5972 Apr 17, 2026
3d10584
Initial Crank: Simplifying support for json exceeding 64 bit integers…
brianjohnson5972 Apr 17, 2026
1624347
Initial Crank: Fixed PR comments from Kevin
brianjohnson5972 Apr 17, 2026
4816e6d
Initial Crank: Fixed PR comments from Chuy
brianjohnson5972 Apr 20, 2026
351a94a
Initial Crank: Minor Cleanup
brianjohnson5972 Apr 20, 2026
6f11d07
Initial Crank: Cleanups found by Claude
brianjohnson5972 Apr 20, 2026
5699d75
Initial Crank: Final cleanup by Claude
brianjohnson5972 Apr 20, 2026
8804659
Merge remote-tracking branch 'origin/master' into feature/crank_queue…
brianjohnson5972 Apr 21, 2026
69a0a96
Initial Crank: Fixing merge from master error
brianjohnson5972 Apr 21, 2026
a3e44e4
Initial Crank: Making json integer conversion simpler and more effici…
brianjohnson5972 Apr 21, 2026
2df59b1
Initial Crank: Last comment fixes
brianjohnson5972 Apr 21, 2026
18efd89
Initial Crank: Cleanup of cron_parser::split
brianjohnson5972 Apr 21, 2026
cbb1cf4
Initial Crank: Added handling for Interrupt signal handling for clean…
brianjohnson5972 Apr 22, 2026
723c19a
Initial Crank: Reverted code that wasn't meant to be committed
brianjohnson5972 Apr 22, 2026
9580154
Initial Crank: Remaining heifner PR comment fixes
brianjohnson5972 Apr 27, 2026
18603cb
Initial Crank: Missed code to remove.
brianjohnson5972 Apr 27, 2026
ec2429c
Initial Crank: Optimized u/int64 conversion path
brianjohnson5972 Apr 27, 2026
12dceb5
Initial Crank: actual remaining heifner PR comment fixes
brianjohnson5972 Apr 27, 2026
a4f1a71
chain_plugin: run read_table_rows inline when called from main thread
heifner Apr 27, 2026
1bf51df
# Achieved memory & on-chain storage stability (`>2400` epochs over `…
jglanz Apr 27, 2026
abe0a31
Initial Crank: final heifner PR comment fixes
brianjohnson5972 Apr 28, 2026
7ec6547
Initial Crank: cleanup
brianjohnson5972 Apr 28, 2026
44a5135
Initial Crank: Reverting change for signature provider plugin
brianjohnson5972 Apr 28, 2026
45882f6
Moved `file:line` to the end of `line-format` for readability in lnav.
jglanz Apr 28, 2026
7a32fe1
# Moved `protoc-gen-<solana|solidity>` plugins and protobuf-bundler i…
jglanz Apr 28, 2026
2bcb00b
Initial Crank: Get rid of Curl
brianjohnson5972 Apr 28, 2026
736dc1d
Initial Crank: Removing curl_init.*
brianjohnson5972 Apr 28, 2026
f08351c
Initial Crank: Missed committing reverting of cmake change
brianjohnson5972 Apr 29, 2026
8b86db2
Initial Crank: Removed non-necessary code changes
brianjohnson5972 Apr 30, 2026
4e3efa0
Initial Crank: Missed removing two code changes
brianjohnson5972 Apr 30, 2026
3a5eabf
Initial Crank: Adding back in waiting on transaction making it in a b…
brianjohnson5972 Apr 30, 2026
87e418c
Updated OPP generate bundles script
jglanz Apr 30, 2026
7b22e17
Updated OPP generate bundles script
jglanz Apr 30, 2026
bc58ab1
Updated OPP generate bundles script
jglanz Apr 30, 2026
b3a4bd3
Updated OPP generate bundles script
jglanz Apr 30, 2026
8813d87
Initial Crank: Removing unintended change
brianjohnson5972 Apr 30, 2026
1acf232
Initial Crank: Adding back in intended ellision variant variable return
brianjohnson5972 Apr 30, 2026
36cca89
Initial Crank: Cleanup includes
brianjohnson5972 Apr 30, 2026
d743f96
Removed weird leftover dep `z`
jglanz May 4, 2026
63e9910
Removed weird leftover dep `z` - updated lockfile
jglanz May 4, 2026
99ea74f
Initial Crank: Removed unneeded change
brianjohnson5972 May 4, 2026
dbf4fac
Initial Crank: Removed unneeded change and improved exception handling
brianjohnson5972 May 4, 2026
996a817
Initial Crank: Removing change to number of threads.
brianjohnson5972 May 4, 2026
f4c232a
Merge remote-tracking branch 'origin/feature/opp-part2' into feature/…
brianjohnson5972 May 5, 2026
0ba6177
Initial Crank: Merged in branch retry implementation.
brianjohnson5972 May 6, 2026
5711f75
Merge remote-tracking branch 'origin/master' into feature/crank_queue…
brianjohnson5972 May 21, 2026
7dd0e3b
revert: restore contracts artifacts to origin/master
brianjohnson5972 May 21, 2026
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
182 changes: 182 additions & 0 deletions CRON_PARSER_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Cron String Parser - Implementation Summary

A complete cron expression parser has been added to the `cron_plugin` to enable string-based schedule configuration.

## Files Created

### 1. Header File
**`plugins/cron_plugin/include/sysio/services/cron_parser.hpp`**
- Public API for parsing cron expressions
- Two functions:
- `parse_cron_schedule()` - Returns `std::optional` (safe)
- `parse_cron_schedule_or_throw()` - Throws on error

### 2. Implementation
**`plugins/cron_plugin/src/services/cron_parser.cpp`**
- Complete parser implementation supporting:
- Wildcards: `*`
- Exact values: `5`
- Ranges: `1-5`
- Steps: `*/5` or `10-50/5`
- Lists: `1,3,5,7`
- Validates all field ranges
- Supports standard 5-field and extended 6-field formats

### 3. Tests
**`plugins/cron_plugin/test/test_cron_parser.cpp`**
- Comprehensive test suite with 25+ test cases
- Tests valid parsing, error handling, and real-world examples

### 4. Documentation
**`plugins/cron_plugin/CRON_PARSER_USAGE.md`**
- Complete usage guide with examples
- Common schedule patterns
- Integration examples

## Quick Start

### Include the header
```cpp
#include <sysio/services/cron_parser.hpp>
```

### Parse a cron expression
```cpp
using namespace sysio::services;

// Safe parsing (returns optional)
auto sched_opt = parse_cron_schedule("*/5 * * * *");
if (sched_opt) {
auto& cron = app().get_plugin<cron_plugin>();
cron.add_job(*sched_opt, []() {
ilog("Runs every 5 minutes");
});
}

// Or with error handling (throws on failure)
try {
auto sched = parse_cron_schedule_or_throw("0 9-17 * * 1-5");
// Use schedule...
} catch (const fc::exception& e) {
elog("Parse error: {}", e.to_detail_string());
}
```

## Format Support

### Standard Format (5 fields)
```
minute hour day-of-month month day-of-week
```

**Example:** `"*/15 9-17 * * 1-5"` = Every 15 minutes, 9 AM-5 PM, weekdays

### Extended Format (6 fields - with milliseconds)
```
milliseconds minute hour day-of-month month day-of-week
```

**Example:** `"*/5000 * * * * *"` = Every 5 seconds

## Common Patterns

| Description | Expression |
|-------------|------------|
| Every minute | `* * * * *` |
| Every 5 minutes | `*/5 * * * *` |
| Hourly at :00 | `0 * * * *` |
| Daily at midnight | `0 0 * * *` |
| Business hours (9-5, weekdays) | `0 9-17 * * 1-5` |
| Every 15 minutes during business hours | `*/15 9-17 * * 1-5` |
| First of month | `0 0 1 * *` |
| Weekly (Sunday 2 AM) | `0 2 * * 0` |
| Every 5 seconds (extended) | `*/5000 * * * * *` |

## Integration Example

### Using in beacon_chain_update_plugin

```cpp
void beacon_chain_update_plugin::plugin_initialize(const variables_map& options) {
// Get schedule from config
std::string schedule_expr = "0 */6 * * *"; // Every 6 hours

if (options.count("beacon-chain-update-schedule")) {
schedule_expr = options.at("beacon-chain-update-schedule").as<std::string>();
}

try {
_update_schedule = parse_cron_schedule_or_throw(schedule_expr);
ilog("Beacon chain update schedule: {}", schedule_expr);
} catch (const fc::exception& e) {
elog("Invalid schedule expression '{}': {}",
schedule_expr, e.to_detail_string());
throw;
}
}

void beacon_chain_update_plugin::plugin_startup() {
auto& cron = app().get_plugin<cron_plugin>();

_update_job_id = cron.add_job(
_update_schedule,
[this]() {
update_beacon_chain_data();
},
cron_service::job_metadata_t{
.one_at_a_time = true,
.tags = {"beacon-chain", "update"},
.label = "beacon_chain_updater"
}
);

ilog("Started beacon chain update job: {}", _update_job_id);
}
```

## Building

The parser is automatically included when building the `cron_plugin`. The `plugin_target()` macro in CMakeLists.txt will pick up the new source file.

To build:
```bash
ninja -C build/debug-claude cron_plugin
```

To run tests:
```bash
./build/debug-claude/plugins/cron_plugin/test/test_cron_plugin --run_test=cron_parser_tests
```

## Features

✅ Standard cron syntax support
✅ Extended format with milliseconds (sub-minute precision)
✅ All operators: wildcards, ranges, steps, lists
✅ Comprehensive validation
✅ Error handling (optional or exception-based)
✅ Full test coverage
✅ Documentation with examples
✅ Zero external dependencies (uses C++20 standard library)

## Next Steps

1. **Build and test:**
```bash
ninja -C build/debug-claude cron_plugin
./build/debug-claude/plugins/cron_plugin/test/test_cron_plugin
```

2. **Use in your plugin:**
```cpp
#include <sysio/services/cron_parser.hpp>
auto schedule = parse_cron_schedule_or_throw("*/5 * * * *");
```

3. **Add config option** (optional):
```cpp
cfg.add_options()
("my-schedule",
bpo::value<std::string>()->default_value("*/5 * * * *"),
"Cron expression for scheduling (e.g., '*/5 * * * *' for every 5 minutes)");
```
1 change: 1 addition & 0 deletions cmake/chain-tools.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ macro(chain_target TARGET)
outpost_client_plugin
outpost_ethereum_client_plugin
outpost_solana_client_plugin
beacon_chain_update_plugin
Comment thread
heifner marked this conversation as resolved.
Outdated
test_control_api_plugin
test_control_plugin
trace_api_plugin
Expand Down
4 changes: 2 additions & 2 deletions libraries/libfc/include/fc/network/ethereum/ethereum_abi.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
namespace fc::network::ethereum {

namespace abi {
enum class invoke_target_type { function, constructor, event, error };
enum class invoke_target_type { function, constructor, event, error, receive };

enum class data_type : int64_t {
boolean,
Expand Down Expand Up @@ -295,7 +295,7 @@ struct get_typename<fc::network::ethereum::abi::data_type> {
};
}; // namespace fc

FC_REFLECT_ENUM(fc::network::ethereum::abi::invoke_target_type, (function)(constructor)(event)(error));
FC_REFLECT_ENUM(fc::network::ethereum::abi::invoke_target_type, (function)(constructor)(event)(error)(receive));

FC_REFLECT(fc::network::ethereum::abi::component_type::list_config_type, (is_list)(size));
FC_REFLECT(fc::network::ethereum::abi::component_type, (name)(type)(list_config)(components)(internal_type));
Expand Down
13 changes: 13 additions & 0 deletions libraries/libfc/include/fc/network/ethereum/ethereum_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
#include <fc/network/ethereum/ethereum_abi.hpp>
#include <fc/network/json_rpc/json_rpc_client.hpp>

#include <future>
#include <utility>

namespace fc::network::ethereum {
using namespace fc::crypto;
using namespace fc::crypto::ethereum;
Expand Down Expand Up @@ -355,6 +358,16 @@ class ethereum_client : public std::enable_shared_from_this<ethereum_client> {
*/
std::string send_raw_transaction(const std::string& raw_tx_data);

/**
* @brief Receives a transaction hash that resolves to the block number once the transaction is included in a block.
* @param tx_hash The transaction hash
* @return A future<uint64_t> that resolves to the block number of the block
* the transaction was included in. The future is fulfilled by a background
* thread that polls eth_getTransactionReceipt until the receipt is available.
* @throws fc::network::json_rpc::json_rpc_exception if the initial RPC call fails.
*/
std::future<uint64_t> identify_block_for_transaction(const std::string& tx_hash);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

template <typename Fn, typename ... Args>
std::expected<decltype(Fn(Args...)), fc::exception> retry(retry_config_t config, Fn fn, Args&& ...) { ... }


/**
* @brief Retrieves logs based on filter parameters.
* @param params The filter parameters for fetching logs.
Expand Down
75 changes: 69 additions & 6 deletions libraries/libfc/src/io/json.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include <fc/io/json.hpp>
//#include <fc/io/fstream.hpp>
//#include <fc/io/sstream.hpp>
#include <fc/int128.hpp>
#include <fc/int256.hpp>
#include <fc/log/logger.hpp>
//#include <utfcpp/utf8.h>
#include <fc/utf8.hpp>
Expand Down Expand Up @@ -28,6 +30,41 @@ namespace fc

#include <fc/io/json_relaxed.hpp>

namespace
{

template<typename I>
struct big_int_as_str;

template<>
struct big_int_as_str<fc::int128> {
static constexpr std::string_view min_str = "9223372036854775808";
static constexpr auto min_len = min_str.size();
};
big_int_as_str<fc::int128> check_int128;

template<>
struct big_int_as_str<fc::int256> {
static constexpr std::string_view min_str = "170141183460469231731687303715884105728";
static constexpr auto min_len = min_str.size();
};
big_int_as_str<fc::int256> check_int256;

template<>
struct big_int_as_str<fc::uint128> {
static constexpr std::string_view min_str = "18446744073709551615";
static constexpr auto min_len = min_str.size();
};
big_int_as_str<fc::uint128> check_uint128;

template<>
struct big_int_as_str<fc::uint256> {
static constexpr std::string_view min_str = "340282366920938463463374607431768211455";
static constexpr auto min_len = min_str.size();
};
big_int_as_str<fc::uint256> check_uint256;
}

Comment thread
heifner marked this conversation as resolved.
namespace fc
{
template<typename T>
Expand Down Expand Up @@ -293,14 +330,40 @@ namespace fc
catch (const std::ios_base::failure&)
{
}
const std::string& str = s;
if (str == "-." || str == "." || str == "-") // check the obviously wrong things we could have encountered

const std::string& const_s = s;
const auto no_neg_start = neg ? 1 : 0;
const auto start = s.find_first_not_of('0', no_neg_start);
const auto str = (start != std::string::npos) ? std::string_view(const_s).substr(start) : std::string_view(const_s);

// if the string is empty and we dropped zeros
if (str.empty() && no_neg_start < start)
return 0;
// check for s== ".", "-","-.", since "[-]0*" is checked above
if (str == "." || str.empty()) // check the obviously wrong things we could have encountered
Comment thread
brianjohnson5972 marked this conversation as resolved.
FC_THROW_EXCEPTION(parse_error_exception, "Can't parse token \"{}\" as a JSON numeric constant", str);
if( dot )
return parser_type == json::parse_type::legacy_parser_with_string_doubles ? variant(str) : variant(to_double(str));
if( neg )
return to_int64(str);
return to_uint64(str);
return parser_type == json::parse_type::legacy_parser_with_string_doubles ? variant(s) : variant(to_double(s));
if( neg ) {
if( str.size() < check_int128.min_len ||
Comment thread
brianjohnson5972 marked this conversation as resolved.
Outdated
(str.size() == check_int128.min_len && str < check_int128.min_str) )
Comment thread
heifner marked this conversation as resolved.
Outdated
return to_int64(s);

if( str.size() > check_int256.min_len ||
(str.size() == check_int256.min_len && str >= check_int256.min_str) )
return variant(fc::int256(s));

return variant(fc::int128_from_string(s));
}
if( str.size() < check_uint128.min_len ||
(str.size() == check_uint128.min_len && str <= check_uint128.min_str) )
return to_uint64(s);

if( str.size() > check_uint256.min_len ||
(str.size() == check_uint256.min_len && str >= check_uint256.min_str) )
return variant(fc::uint256(s));

return variant(fc::uint128_from_string(s));
}

template<typename T>
Expand Down
22 changes: 21 additions & 1 deletion libraries/libfc/src/network/ethereum/ethereum_abi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,12 @@ void fc::from_variant(const fc::variant& var, fc::network::ethereum::abi::contra

FC_ASSERT(var.is_object(), "Variant must be an object to deserialize ABI contract");
auto& obj = var.get_object();
vo.name = obj["name"].as_string();
const auto name_itr = obj.find("name");
const bool deferred_name = name_itr == obj.end();
if (!deferred_name) {
vo.name = name_itr->value().as_string();
}

auto type_str = obj["type"].as_string();
vo.type = fc::reflector<fc::network::ethereum::abi::invoke_target_type>::from_string(type_str.c_str());

Expand All @@ -1072,4 +1077,19 @@ void fc::from_variant(const fc::variant& var, fc::network::ethereum::abi::contra

parse_components(vo.inputs, "inputs");
parse_components(vo.outputs, "outputs");
bool missed = true;
if(deferred_name) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had initially put this in for reporting what we don't expect to see, and determine if something else needs to be reported. Not sure if we should drop or at least keep it in here while we are still working on ethereum client scripts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to throwing exception to identify what we may be missing.

if(type_str == "receive") {
auto state_mutability_str = obj["stateMutability"].as_string();
if (state_mutability_str == "payable") {
missed = false;
}
}
if(missed) {
elog("no name for:");
for(auto itr = obj.begin(); itr != obj.end(); ++itr) {
ilog("key: {}", itr->key());
}
Comment thread
brianjohnson5972 marked this conversation as resolved.
Outdated
}
}
}
Loading
Loading