From 3d0a107562085a1c048a8e272e4bbb8529793888 Mon Sep 17 00:00:00 2001 From: Philip Taffet Date: Thu, 13 Feb 2025 16:59:03 +0000 Subject: [PATCH] bundle: add cranks, plumbing to crank tip programs; rework pack bundle plumbing --- agave | 2 +- book/api/metrics-generated.md | 1 + plugin/bundle/test-server/Cargo.toml | 1 + .../bundle/test-server/src/bin/bundlesvr.rs | 15 +- src/app/fdctl/config.h | 4 + src/app/fdctl/config/default.toml | 29 ++ src/app/fdctl/config_parse.c | 4 + src/app/fdctl/run/tiles/fd_pack.c | 197 ++++++++- src/app/fdctl/run/tiles/fd_poh.c | 86 +++- src/app/fdctl/run/tiles/fd_sign.c | 5 + src/app/fdctl/run/topos/fd_frankendancer.c | 41 +- src/app/fddev/dev.c | 13 +- src/disco/keyguard/fd_keyguard.h | 17 +- src/disco/keyguard/fd_keyguard_authorize.c | 34 ++ src/disco/metrics/generated/fd_metrics_poh.c | 1 + src/disco/metrics/generated/fd_metrics_poh.h | 10 +- src/disco/metrics/metrics.xml | 3 + src/disco/pack/fd_pack.c | 185 ++++---- src/disco/pack/fd_pack.h | 164 ++++---- src/disco/plugin/Local.mk | 5 + src/disco/plugin/fd_bundle_crank.c | 395 ++++++++++++++++++ src/disco/plugin/fd_bundle_crank.h | 303 ++++++++++++++ src/disco/plugin/fd_bundle_crank_constants.h | 17 + ...jZiG5AR8PRYHAaQ3JqA8JYyCRoc3VCwohQVwYP.bin | Bin 0 -> 606 bytes src/disco/plugin/test_bundle_crank.c | 242 +++++++++++ src/disco/tiles.h | 15 + src/disco/topo/fd_topo.h | 15 + 27 files changed, 1562 insertions(+), 242 deletions(-) create mode 100644 src/disco/plugin/Local.mk create mode 100644 src/disco/plugin/fd_bundle_crank.c create mode 100644 src/disco/plugin/fd_bundle_crank.h create mode 100644 src/disco/plugin/fd_bundle_crank_constants.h create mode 100644 src/disco/plugin/fixtures/2niAy2BYpfqTuBM7pZtTkHFnWM2x7bFcFL1oyR1ixqGRcG5uydjZiG5AR8PRYHAaQ3JqA8JYyCRoc3VCwohQVwYP.bin create mode 100644 src/disco/plugin/test_bundle_crank.c diff --git a/agave b/agave index f37921172f..943bd6bbaf 160000 --- a/agave +++ b/agave @@ -1 +1 @@ -Subproject commit f37921172fdb1faaa90ca148c4c6b94b2890eeb5 +Subproject commit 943bd6bbafc85c729eb276dcc0564f5fbcca9814 diff --git a/book/api/metrics-generated.md b/book/api/metrics-generated.md index 74ca531c38..7ccf057674 100644 --- a/book/api/metrics-generated.md +++ b/book/api/metrics-generated.md @@ -315,6 +315,7 @@ | poh_​begin_​leader_​delay_​seconds | `histogram` | Delay between when we become leader in a slot and when we receive the bank. | | poh_​first_​microblock_​delay_​seconds | `histogram` | Delay between when we become leader in a slot and when we receive the first microblock. | | poh_​slot_​done_​delay_​seconds | `histogram` | Delay between when we become leader in a slot and when we finish the slot. | +| poh_​bundle_​initialize_​delay_​seconds | `histogram` | Delay in starting the slot caused by loading the information needed to generate the bundle crank transactions | ## Shred Tile | Metric | Type | Description | diff --git a/plugin/bundle/test-server/Cargo.toml b/plugin/bundle/test-server/Cargo.toml index 52d00807b9..b06f00057f 100644 --- a/plugin/bundle/test-server/Cargo.toml +++ b/plugin/bundle/test-server/Cargo.toml @@ -19,6 +19,7 @@ thiserror = "1.0.64" bs58 = "0.5.1" futures-util = "0.3.31" env_logger = "0.11.5" +base64="0.22.1" [build-dependencies] tonic-build = "0.12.2" diff --git a/plugin/bundle/test-server/src/bin/bundlesvr.rs b/plugin/bundle/test-server/src/bin/bundlesvr.rs index 2a63fe3e5c..987863d3db 100644 --- a/plugin/bundle/test-server/src/bin/bundlesvr.rs +++ b/plugin/bundle/test-server/src/bin/bundlesvr.rs @@ -11,6 +11,7 @@ use log::info; use prost_types::Timestamp; use tonic::{transport::Server, Request, Response, Status}; use futures_util::stream::Stream; +use base64::prelude::*; use bundle_test_server::proto::block_engine::block_engine_validator_server::{BlockEngineValidator, BlockEngineValidatorServer}; use bundle_test_server::proto::block_engine::{SubscribePacketsRequest, SubscribePacketsResponse, SubscribeBundlesRequest, SubscribeBundlesResponse, BlockBuilderFeeInfoRequest, BlockBuilderFeeInfoResponse}; @@ -63,23 +64,15 @@ impl BlockEngineValidator for BlockEngineValidatorService { packets: vec![ Packet { meta: None, - data: vec![0; 1232], + data: BASE64_STANDARD.decode(b"AplgYeL7lZ//2fZyq0hgs57Pqevr2XhcDslxchP9MCwSL1T53TQquo1Q8YjXW87hsLOZUrGf9rJSNOZxkjkzuQkqX7ELf9NRH4IduEUMCdNk+4eQKaeNPD0qxwjMnThpPy4W5twMcpqsIwBUn8nBin9zDiGIw45q9+cAIRnxQcIFAgEFCmK1c3bBvURtMIVYcRrqWeDmpFe3rMTamh3isH57X+VPJoNzHmgKQDMBH7YdnfpZnoZziXznBbYn0l84zte8nzqeV4BovTKBuUW4r+kDb0rcqF1E1a3dEQrWAap98xF9WXtE2rBhEteCr6t2wb2dkwc3bua8xiAAl5CvmaQjcZf1u2Zrh2RnqnIAyiqCAgVGWHAqq0RQXvouEI3tZUSNWDAGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAAmQEHCdkn/s4XyQQwWBqtUGKmQGpVL+cpvyQlwCxD/PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpW0bsAXCA/sc0H8S3yQY1dU5OrWgtx5+nJyXamTjQ7tAEBwMCBQEEBAAAAAgACQOAw8kBAAAAAAgABQIQJwAACQQDBgQACgymPaYcAgAAAAY=").unwrap(), }, Packet { meta: None, - data: vec![0; 1232], + data: BASE64_STANDARD.decode(b"AijKJ5H5jDNza6wwLLB5pFCMLaHBAJ1P5spx4QkBg0vdKsnfEmdf+fgI7sJgcp68OONRxp1Tuwrpkc5Q8BTuDQWZAsNYQnvXD4n4RPImcMrus0SUBMbQrX+t7fUlMU/SBscsCPjQRAoVlTWlc5/8MA0cg89W0dSlonX8UJXbMwUAAgEFCiA0jtjBgydimHmbOX9EM63N1d6wTAAyO4LyrIAh6nJiJoNzHmgKQDMBH7YdnfpZnoZziXznBbYn0l84zte8nzqfQneRHfpJJef849c/Ti3mq0yjQHCFQZPzdPEb644M0I9vSd8BuAJ2fFMkc1ynhn6lJ9DMAkzNWj21LlOVQI7veaXsBCiWbSSY43Sr0fBad1SBWBG56mAt1QkTqxVpiacGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAM4BDmCv7bInF71jGS9UFFo/llozu4LSxwKess4eIIJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpW0bsAXCA/sc0H8S3yQY1dU5OrWgtx5+nJyXamTjQ7tAEBwMCBQEEBAAAAAgACQOAw8kBAAAAAAgABQIQJwAACQQDBgQACgwAZc0dAAAAAAY=").unwrap(), }, Packet { meta: None, - data: vec![0; 1232], - }, - Packet { - meta: None, - data: vec![0; 1232], - }, - Packet { - meta: None, - data: vec![0; 1232], + data: BASE64_STANDARD.decode(b"ApbANVN5DAFCeUVlGFMJiDoblInErsUb3SXvH/zedTvI3N/2VQXbiD7YBCMMM7z70CHDAtwUN6QyDXTfX9f50AeLiJsJFALf+yx+y8VCmkIHrydT0xO2AktOUAPAwSEUrLO9vx2w34zTQH7825CP0KJq0P0qbh8s5ZnqMRCWomQGAgEFCq0GX6spT4f2ZZTU4jmnw1c4N5Rh8PnkcZSKcTZtN125JoNzHmgKQDMBH7YdnfpZnoZziXznBbYn0l84zte8nzo9Qv0uUDVQ5kxt1XdzjHfKkFC6suwka3+AymtKrvnB+QfuvVvIlFZT5CJZqW9OOIKqfTbS7kzxgNWErlC5OF1WeaXsBCiWbSSY43Sr0fBad1SBWBG56mAt1QkTqxVpiacGp9UXGSxWjuCKhF9z0peIzwNcMUWyGrNE2AYuqUAAAM4BDmCv7bInF71jGS9UFFo/llozu4LSxwKess4eIIJkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADBkZv5SEXMv/srbpyw5vnvIzlu8X3EmssQ5s6QAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpW0bsAXCA/sc0H8S3yQY1dU5OrWgtx5+nJyXamTjQ7tAEBwMCBQEEBAAAAAgACQOAw8kBAAAAAAgABQIQJwAACQQDBgQACgwpYx0gAAAAAAY=").unwrap(), }, ], }) diff --git a/src/app/fdctl/config.h b/src/app/fdctl/config.h index 7b5bd2c334..f24eb1ea7c 100644 --- a/src/app/fdctl/config.h +++ b/src/app/fdctl/config.h @@ -258,6 +258,10 @@ struct fdctl_config { int enabled; char url[ 256 ]; char tls_domain_name[ 256 ]; + char tip_distribution_program_addr[ FD_BASE58_ENCODED_32_SZ ]; + char tip_payment_program_addr[ FD_BASE58_ENCODED_32_SZ ]; + char tip_distribution_authority[ FD_BASE58_ENCODED_32_SZ ]; + uint commission_bps; } bundle; struct { diff --git a/src/app/fdctl/config/default.toml b/src/app/fdctl/config/default.toml index 4501f6681c..9c16e08663 100644 --- a/src/app/fdctl/config/default.toml +++ b/src/app/fdctl/config/default.toml @@ -1059,6 +1059,35 @@ dynamic_port_range = "8900-9000" # it is being accessed. tls_domain_name = "" + # The tip distribution program is used to control the + # distribution of tips that transactions from bundles may pay. + # This should be set to the public key of the tip distribution + # program. Consult the block engine provider's documentation + # for the appropriate value of this field. + tip_distribution_program_addr = "" + + # The tip payment program handles the regular collection of tips + # into the account configured by the tip distribution program. + # This should be set to the public key of the tip payment + # program. Consult the block engine provider's documentation + # for the appropriate value of this field. + tip_payment_program_addr = "" + + # A validator may want to distribute tips from transactions to + # their stakers or to any other relevant parties. Authority is + # delegated to the public key provided in the + # tip_distribution_authority to determine the distribution by + # uploading a Merkle tree. This field should be set to the + # base58 public key. + tip_distribution_authority = "" + + # The standard tip distribution mechanism distributes tips + # pro-rata to stakers with the validator optionally taking a + # portion as a fee. The commission set below, measured in basis + # points (100ths of a percent), determines the validator's + # portion. + commission_bps = 0 + # The pack tile takes incoming transactions that have been verified # by the verify tile and then deduplicated, and attempts to order # them in an optimal way to generate the most fees per compute diff --git a/src/app/fdctl/config_parse.c b/src/app/fdctl/config_parse.c index 293d2342df..15e534d4b9 100644 --- a/src/app/fdctl/config_parse.c +++ b/src/app/fdctl/config_parse.c @@ -314,6 +314,10 @@ fdctl_pod_to_cfg( config_t * config, CFG_POP ( bool, tiles.bundle.enabled ); CFG_POP ( cstr, tiles.bundle.url ); CFG_POP ( cstr, tiles.bundle.tls_domain_name ); + CFG_POP ( cstr, tiles.bundle.tip_distribution_program_addr ); + CFG_POP ( cstr, tiles.bundle.tip_payment_program_addr ); + CFG_POP ( cstr, tiles.bundle.tip_distribution_authority ); + CFG_POP ( uint, tiles.bundle.commission_bps ); CFG_POP ( uint, tiles.pack.max_pending_transactions ); CFG_POP ( bool, tiles.pack.use_consumed_cus ); diff --git a/src/app/fdctl/run/tiles/fd_pack.c b/src/app/fdctl/run/tiles/fd_pack.c index 5980f88c0e..182c1c2c83 100644 --- a/src/app/fdctl/run/tiles/fd_pack.c +++ b/src/app/fdctl/run/tiles/fd_pack.c @@ -3,11 +3,16 @@ #include "generated/pack_seccomp.h" #include "../../../../disco/topo/fd_pod_format.h" +#include "../../../../disco/keyguard/fd_keyload.h" +#include "../../../../disco/keyguard/fd_keyswitch.h" +#include "../../../../disco/keyguard/fd_keyguard.h" #include "../../../../disco/shred/fd_shredder.h" #include "../../../../disco/metrics/fd_metrics.h" #include "../../../../disco/pack/fd_pack.h" #include "../../../../disco/pack/fd_pack_pacing.h" +#include "../../../../ballet/base64/fd_base64.h" + #include /* fd_pack is responsible for taking verified transactions, and @@ -19,6 +24,7 @@ #define IN_KIND_RESOLV (0UL) #define IN_KIND_POH (1UL) #define IN_KIND_BANK (2UL) +#define IN_KIND_SIGN (3UL) #define MAX_SLOTS_PER_EPOCH 432000UL @@ -102,6 +108,10 @@ FD_IMPORT( wait_duration, "src/disco/pack/pack_delay.bin", ulong, 6, "" ); #endif +typedef struct { + fd_acct_addr_t commission_pubkey[1]; + ulong commission; +} block_builder_info_t; typedef struct { fd_wksp_t * mem; @@ -240,12 +250,23 @@ typedef struct { fd_txn_e_t * const * bundle; /* points to _txn when non-NULL */ } current_bundle[1]; + block_builder_info_t blk_engine_cfg[1]; + struct { - struct { - fd_acct_addr_t commission_pubkey[1]; - ulong commision; - } current, next; - } blk_engine_cfg[1]; + int enabled; + int ib_inserted; /* in this slot */ + fd_acct_addr_t vote_pubkey[1]; + fd_acct_addr_t identity_pubkey[1]; + fd_bundle_crank_gen_t gen[1]; + fd_acct_addr_t tip_receiver_owner[1]; + ulong epoch; + fd_bundle_crank_tip_payment_config_t prev_config[1]; /* as of start of slot, then updated */ + uchar recent_blockhash[32]; + fd_ed25519_sig_t last_sig[1]; + + fd_keyswitch_t * keyswitch; + fd_keyguard_client_t keyguard_client[1]; + } crank[1]; /* Used between during_frag and after_frag */ @@ -253,6 +274,8 @@ typedef struct { fd_txn_p_t pending_rebate[ MAX_TXN_PER_MICROBLOCK ]; /* indexed [0, pending_rebate_cnt) */ } fd_pack_ctx_t; +#define BUNDLE_META_SZ 40UL +FD_STATIC_ASSERT( sizeof(block_builder_info_t)==BUNDLE_META_SZ, blk_engine_cfg ); #define FD_PACK_METRIC_STATE_TRANSACTIONS 0 #define FD_PACK_METRIC_STATE_BANKS 1 @@ -274,6 +297,15 @@ update_metric_state( fd_pack_ctx_t * ctx, } } +static inline void +remove_ib( fd_pack_ctx_t * ctx ) { + /* It's likely the initializer bundle is long scheduled, but we want to + try deleting it just in case. */ + if( FD_UNLIKELY( ctx->crank->enabled & ctx->crank->ib_inserted ) ) + fd_pack_delete_transaction( ctx->pack, (fd_ed25519_sig_t const *)ctx->crank->last_sig ); + ctx->crank->ib_inserted = 0; +} + FD_FN_CONST static inline ulong scratch_align( void ) { @@ -295,7 +327,7 @@ scratch_footprint( fd_topo_tile_t const * tile ) { l = FD_LAYOUT_APPEND( l, alignof( fd_pack_ctx_t ), sizeof( fd_pack_ctx_t ) ); l = FD_LAYOUT_APPEND( l, fd_rng_align(), fd_rng_footprint() ); l = FD_LAYOUT_APPEND( l, fd_pack_align(), fd_pack_footprint( tile->pack.max_pending_transactions, - 1, + BUNDLE_META_SZ, tile->pack.bank_tile_count, limits ) ); #if FD_PACK_USE_EXTRA_STORAGE @@ -337,6 +369,11 @@ metrics_write( fd_pack_ctx_t * ctx ) { static inline void during_housekeeping( fd_pack_ctx_t * ctx ) { ctx->approx_wallclock_ns = fd_log_wallclock(); + + if( FD_UNLIKELY( ctx->crank->enabled && fd_keyswitch_state_query( ctx->crank->keyswitch )==FD_KEYSWITCH_STATE_SWITCH_PENDING ) ) { + fd_memcpy( ctx->crank->identity_pubkey, ctx->crank->keyswitch->bytes, 32UL ); + fd_keyswitch_state( ctx->crank->keyswitch, FD_KEYSWITCH_STATE_COMPLETED ); + } } static inline void @@ -479,7 +516,8 @@ after_credit( fd_pack_ctx_t * ctx, ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; fd_pack_end_block( ctx->pack ); - fd_pack_set_initializer_bundles_ready( ctx->pack ); + remove_ib( ctx ); + update_metric_state( ctx, now, FD_PACK_METRIC_STATE_LEADER, 0 ); update_metric_state( ctx, now, FD_PACK_METRIC_STATE_BANKS, 0 ); update_metric_state( ctx, now, FD_PACK_METRIC_STATE_MICROBLOCKS, 0 ); @@ -524,6 +562,52 @@ after_credit( fd_pack_ctx_t * ctx, *charge_busy = 1; + /* Consider creating initializer bundles to crank the tip programs. + Craning requires inserting a bundle, which can't be done while + another insert is in progress. */ + if( FD_LIKELY( ctx->crank->enabled && !ctx->current_bundle->bundle ) ) { + block_builder_info_t const * top_meta = fd_pack_peek_bundle_meta( ctx->pack ); + if( FD_UNLIKELY( top_meta ) ) { + /* Have bundles, in a reasonable state to crank. */ + + fd_txn_e_t * _bundle[ 1UL ]; + fd_txn_e_t * const * bundle = fd_pack_insert_bundle_init( ctx->pack, _bundle, 1UL ); + + ulong txn_sz = fd_bundle_crank_generate( ctx->crank->gen, ctx->crank->prev_config, top_meta->commission_pubkey, + ctx->crank->identity_pubkey, ctx->crank->tip_receiver_owner, ctx->crank->epoch, top_meta->commission, + bundle[0]->txnp->payload, TXN( bundle[0]->txnp ) ); + + if( FD_LIKELY( txn_sz==0UL ) ) { /* Everything in good shape! */ + fd_pack_insert_bundle_cancel( ctx->pack, bundle, 1UL ); + fd_pack_set_initializer_bundles_ready( ctx->pack ); + } + else if( FD_LIKELY( txn_sztxnp->payload_sz = (ushort)txn_sz; + memcpy( bundle[0]->txnp->payload+TXN(bundle[0]->txnp)->recent_blockhash_off, ctx->crank->recent_blockhash, 32UL ); + + fd_keyguard_client_sign( ctx->crank->keyguard_client, bundle[0]->txnp->payload+1UL, + bundle[0]->txnp->payload+65UL, txn_sz-65UL, FD_KEYGUARD_SIGN_TYPE_ED25519 ); + + memcpy( ctx->crank->last_sig, bundle[0]->txnp->payload+1UL, 64UL ); + + ctx->crank->ib_inserted = 1; + int retval = fd_pack_insert_bundle_fini( ctx->pack, bundle, 1UL, ctx->leader_slot-1UL, 1, NULL ); + if( FD_UNLIKELY( retval<0 ) ) FD_LOG_WARNING(( "inserting initializer bundle returned %i", retval )); + else { + /* Update the cached copy of the on-chain state. It won't be read + again until after scheduling the initializer bundle we just + inserted because peek_bundle_meta will return NULL */ + + *(ctx->crank->prev_config->block_builder) = *(top_meta->commission_pubkey); + ctx->crank->prev_config->commission_pct = top_meta->commission; + } + } else { + /* Already logged a warning in this case */ + fd_pack_insert_bundle_cancel( ctx->pack, bundle, 1UL ); + } + } + } + /* Try to schedule the next microblock. Do we have any idle bank tiles in the first `pacing_bank_cnt`? */ if( FD_LIKELY( ctx->bank_idle_bitset & fd_ulong_mask_lsb( pacing_bank_cnt ) ) ) { /* Optimize for schedule */ @@ -604,6 +688,8 @@ after_credit( fd_pack_ctx_t * ctx, ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; fd_pack_end_block( ctx->pack ); + remove_ib( ctx ); + } } @@ -641,6 +727,7 @@ during_frag( fd_pack_ctx_t * ctx, ctx->leader_slot = ULONG_MAX; ctx->slot_microblock_cnt = 0UL; fd_pack_end_block( ctx->pack ); + remove_ib( ctx ); } ctx->leader_slot = fd_disco_poh_sig_slot( sig ); @@ -662,6 +749,15 @@ during_frag( fd_pack_ctx_t * ctx, reinitialize it the next time when we actually become leader. */ fd_pack_pacing_init( ctx->pacer, now_ticks, end_ticks, (float)ctx->ticks_per_ns, ctx->slot_max_cost ); + if( FD_UNLIKELY( ctx->crank->enabled ) ) { + /* If we get overrun, we'll just never use these values, but the + old values aren't really useful either. */ + ctx->crank->epoch = became_leader->epoch; + *(ctx->crank->prev_config) = *(became_leader->bundle->config); + memcpy( ctx->crank->recent_blockhash, became_leader->bundle->last_blockhash, 32UL ); + memcpy( ctx->crank->tip_receiver_owner, became_leader->bundle->tip_receiver_owner, 32UL ); + } + FD_LOG_INFO(( "pack_became_leader(slot=%lu,ends_at=%ld)", ctx->leader_slot, became_leader->slot_end_ns )); /* The dcache might get overrun, so set slot_end_ns to 0, so if it does @@ -690,6 +786,11 @@ during_frag( fd_pack_ctx_t * ctx, if( FD_UNLIKELY( chunkin[ in_idx ].chunk0 || chunk>ctx->in[ in_idx ].wmark || sz>FD_TPU_RESOLVED_MTU ) ) FD_LOG_ERR(( "chunk %lu %lu corrupt, not in range [%lu,%lu]", chunk, sz, ctx->in[ in_idx ].chunk0, ctx->in[ in_idx ].wmark )); + fd_txn_m_t * txnm = (fd_txn_m_t *)dcache_entry; + FD_TEST( txnm->payload_sz<=FD_TPU_MTU ); + FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ ); + fd_txn_t * txn = fd_txn_m_txn_t( txnm ); + if( FD_UNLIKELY( (ctx->leader_slot==ULONG_MAX) & (sig>ctx->highest_observed_slot) ) ) { /* Using the resolv tile's knowledge of the current slot is a bit of a hack, since we don't get any info if there are no @@ -704,10 +805,6 @@ during_frag( fd_pack_ctx_t * ctx, FD_MCNT_INC( PACK, TRANSACTION_EXPIRED, exp_cnt ); } - fd_txn_m_t * txnm = (fd_txn_m_t *)dcache_entry; - FD_TEST( txnm->payload_sz<=FD_TPU_MTU ); - FD_TEST( txnm->txn_t_sz<=FD_TXN_MAX_SZ ); - fd_txn_t * txn = fd_txn_m_txn_t( txnm ); if( FD_UNLIKELY( txnm->block_engine.bundle_id ) ) { ctx->is_bundle = 1; @@ -724,11 +821,14 @@ during_frag( fd_pack_ctx_t * ctx, FD_LOG_WARNING(( "pack got a partial bundle" )); return; } + ctx->blk_engine_cfg->commission = txnm->block_engine.commission; + memcpy( ctx->blk_engine_cfg->commission_pubkey->b, txnm->block_engine.commission_pubkey, 32UL ); + ctx->current_bundle->bundle = fd_pack_insert_bundle_init( ctx->pack, ctx->current_bundle->_txn, ctx->current_bundle->txn_cnt ); } ctx->cur_spot = ctx->current_bundle->bundle[ ctx->current_bundle->txn_received ]; ctx->current_bundle->min_blockhash_slot = fd_ulong_min( ctx->current_bundle->min_blockhash_slot, sig ); - /* TODO: handle commission stuff */ + } else { ctx->is_bundle = 0; @@ -772,6 +872,7 @@ during_frag( fd_pack_ctx_t * ctx, } } + /* After the transaction has been fully received, and we know we were not overrun while reading it, insert it into pack. */ @@ -797,6 +898,7 @@ after_frag( fd_pack_ctx_t * ctx, ctx->slot_end_ns = ctx->_slot_end_ns; fd_pack_set_block_limits( ctx->pack, ctx->slot_max_microblocks, ctx->slot_max_data ); fd_pack_pacing_update_consumed_cus( ctx->pacer, fd_pack_current_block_cost( ctx->pack ), now ); + break; } case IN_KIND_BANK: { @@ -819,7 +921,7 @@ after_frag( fd_pack_ctx_t * ctx, if( FD_UNLIKELY( ctx->current_bundle->txn_cnt==0UL ) ) return; if( FD_UNLIKELY( ++(ctx->current_bundle->txn_received)==ctx->current_bundle->txn_cnt ) ) { long insert_duration = -fd_tickcount(); - int result = fd_pack_insert_bundle_fini( ctx->pack, ctx->current_bundle->bundle, ctx->current_bundle->txn_cnt, ctx->current_bundle->min_blockhash_slot, 0 ); + int result = fd_pack_insert_bundle_fini( ctx->pack, ctx->current_bundle->bundle, ctx->current_bundle->txn_cnt, ctx->current_bundle->min_blockhash_slot, 0, ctx->blk_engine_cfg ); insert_duration += fd_tickcount(); ctx->insert_result[ result + FD_PACK_INSERT_RETVAL_OFF ]++; fd_histf_sample( ctx->insert_duration, (ulong)insert_duration ); @@ -844,6 +946,28 @@ after_frag( fd_pack_ctx_t * ctx, update_metric_state( ctx, now, FD_PACK_METRIC_STATE_TRANSACTIONS, fd_pack_avail_txn_cnt( ctx->pack )>0 ); } +static void +privileged_init( fd_topo_t * topo, + fd_topo_tile_t * tile ) { + if( FD_LIKELY( !tile->pack.bundle.enabled ) ) return; + + void * scratch = fd_topo_obj_laddr( topo, tile->tile_obj_id ); + + FD_SCRATCH_ALLOC_INIT( l, scratch ); + fd_pack_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_pack_ctx_t ), sizeof( fd_pack_ctx_t ) ); + + if( FD_UNLIKELY( !strcmp( tile->pack.bundle.identity_key_path, "" ) ) ) + FD_LOG_ERR(( "identity_key_path not set" )); + + const uchar * identity_key = fd_keyload_load( tile->pack.bundle.identity_key_path, /* pubkey only: */ 1 ); + fd_memcpy( ctx->crank->identity_pubkey->b, identity_key, 32UL ); + + if( FD_UNLIKELY( !fd_base58_decode_32( tile->pack.bundle.vote_account_path, ctx->crank->vote_pubkey->b ) ) ) { + const uchar * vote_key = fd_keyload_load( tile->pack.bundle.vote_account_path, /* pubkey only: */ 1 ); + fd_memcpy( ctx->crank->vote_pubkey->b, vote_key, 32UL ); + } +} + static void unprivileged_init( fd_topo_t * topo, fd_topo_tile_t * tile ) { @@ -860,7 +984,7 @@ unprivileged_init( fd_topo_t * topo, if( FD_UNLIKELY( tile->pack.max_pending_transactions >= USHORT_MAX-5UL ) ) FD_LOG_ERR(( "pack tile supports up to %lu pending transactions", USHORT_MAX-6UL )); - ulong pack_footprint = fd_pack_footprint( tile->pack.max_pending_transactions, 1, tile->pack.bank_tile_count, limits ); + ulong pack_footprint = fd_pack_footprint( tile->pack.max_pending_transactions, BUNDLE_META_SZ, tile->pack.bank_tile_count, limits ); FD_SCRATCH_ALLOC_INIT( l, scratch ); fd_pack_ctx_t * ctx = FD_SCRATCH_ALLOC_APPEND( l, alignof( fd_pack_ctx_t ), sizeof( fd_pack_ctx_t ) ); @@ -868,7 +992,7 @@ unprivileged_init( fd_topo_t * topo, if( FD_UNLIKELY( !rng ) ) FD_LOG_ERR(( "fd_rng_new failed" )); ctx->pack = fd_pack_join( fd_pack_new( FD_SCRATCH_ALLOC_APPEND( l, fd_pack_align(), pack_footprint ), - tile->pack.max_pending_transactions, 1, tile->pack.bank_tile_count, + tile->pack.max_pending_transactions, BUNDLE_META_SZ, tile->pack.bank_tile_count, limits, rng ) ); if( FD_UNLIKELY( !ctx->pack ) ) FD_LOG_ERR(( "fd_pack_new failed" )); @@ -881,6 +1005,7 @@ unprivileged_init( fd_topo_t * topo, else if( FD_LIKELY( !strcmp( link->name, "dedup_pack" ) ) ) ctx->in_kind[ i ] = IN_KIND_RESOLV; else if( FD_LIKELY( !strcmp( link->name, "poh_pack" ) ) ) ctx->in_kind[ i ] = IN_KIND_POH; else if( FD_LIKELY( !strcmp( link->name, "bank_poh" ) ) ) ctx->in_kind[ i ] = IN_KIND_BANK; + else if( FD_LIKELY( !strcmp( link->name, "sign_pack" ) ) ) ctx->in_kind[ i ] = IN_KIND_SIGN; else FD_LOG_ERR(( "pack tile has unexpected input link %lu %s", i, link->name )); } @@ -890,6 +1015,46 @@ unprivileged_init( fd_topo_t * topo, if( FD_UNLIKELY( out_cnt>FD_PACK_PACK_MAX_OUT ) ) FD_LOG_ERR(( "pack tile connects to too many banking tiles" )); if( FD_UNLIKELY( out_cnt!=tile->pack.bank_tile_count+1UL ) ) FD_LOG_ERR(( "pack tile connects to %lu banking tiles, but tile->pack.bank_tile_count is %lu", out_cnt-1UL, tile->pack.bank_tile_count )); + + ctx->crank->enabled = tile->pack.bundle.enabled; + if( FD_UNLIKELY( tile->pack.bundle.enabled ) ) { + if( FD_UNLIKELY( !fd_bundle_crank_gen_init( ctx->crank->gen, (fd_acct_addr_t const *)tile->pack.bundle.tip_distribution_program_addr, + (fd_acct_addr_t const *)tile->pack.bundle.tip_payment_program_addr, + (fd_acct_addr_t const *)ctx->crank->vote_pubkey->b, + (fd_acct_addr_t const *)tile->pack.bundle.tip_distribution_authority, tile->pack.bundle.commission_bps ) ) ) { + FD_LOG_ERR(( "constructing bundle generator failed" )); + } + + ulong sign_in_idx = fd_topo_find_tile_in_link ( topo, tile, "sign_pack", tile->kind_id ); + ulong sign_out_idx = fd_topo_find_tile_out_link( topo, tile, "pack_sign", tile->kind_id ); + FD_TEST( sign_in_idx!=ULONG_MAX ); + fd_topo_link_t * sign_in = &topo->links[ tile->in_link_id[ sign_in_idx ] ]; + fd_topo_link_t * sign_out = &topo->links[ tile->out_link_id[ sign_out_idx ] ]; + if( FD_UNLIKELY( !fd_keyguard_client_join( fd_keyguard_client_new( ctx->crank->keyguard_client, + sign_out->mcache, + sign_out->dcache, + sign_in->mcache, + sign_in->dcache ) ) ) ) { + FD_LOG_ERR(( "failed to construct keyguard" )); + } + /* Initialize enough of the prev config that it produces a + transaction */ + ctx->crank->prev_config->discriminator = 0x82ccfa1ee0aa0c9bUL; + ctx->crank->prev_config->tip_receiver->b[1] = 1; + ctx->crank->prev_config->block_builder->b[2] = 1; + + memset( ctx->crank->tip_receiver_owner, '\0', 32UL ); + memset( ctx->crank->recent_blockhash, '\0', 32UL ); + memset( ctx->crank->last_sig, '\0', 64UL ); + ctx->crank->ib_inserted = 0; + ctx->crank->epoch = 0UL; + ctx->crank->keyswitch = fd_keyswitch_join( fd_topo_obj_laddr( topo, tile->keyswitch_obj_id ) ); + FD_TEST( ctx->crank->keyswitch ); + } else { + memset( ctx->crank, '\0', sizeof(ctx->crank) ); + } + + #if FD_PACK_USE_EXTRA_STORAGE ctx->extra_txn_deq = extra_txn_deq_join( extra_txn_deq_new( FD_SCRATCH_ALLOC_APPEND( l, extra_txn_deq_align(), extra_txn_deq_footprint() ) ) ); @@ -916,6 +1081,7 @@ unprivileged_init( fd_topo_t * topo, ctx->insert_to_extra = 0; #endif ctx->use_consumed_cus = tile->pack.use_consumed_cus; + ctx->crank->enabled = tile->pack.bundle.enabled; ctx->wait_duration_ticks[ 0 ] = ULONG_MAX; for( ulong i=1UL; iplugin_out->chunk = fd_dcache_compact_next( ctx->plugin_out->chunk, sizeof(fd_plugin_msg_slot_end_t), ctx->plugin_out->chunk0, ctx->plugin_out->wmark ); } +extern int +fd_ext_bank_load_account( void const * bank, + int fixed_root, + uchar const * addr, + uchar * owner, + uchar * data, + ulong * data_sz ); + CALLED_FROM_RUST static void publish_became_leader( fd_poh_ctx_t * ctx, - ulong slot ) { + ulong slot, + ulong epoch ) { double tick_per_ns = fd_tempo_tick_per_ns( NULL ); fd_histf_sample( ctx->begin_leader_delay, (ulong)((double)(fd_log_wallclock()-ctx->reset_slot_start_ns)/tick_per_ns) ); @@ -933,6 +953,35 @@ publish_became_leader( fd_poh_ctx_t * ctx, ctx->reset_slot_start_ns = fd_log_wallclock() - (long)((double)(slot-ctx->reset_slot)*ctx->slot_duration_ns); } + fd_bundle_crank_tip_payment_config_t config[1] = { 0 }; + fd_acct_addr_t tip_receiver_owner[1] = { 0 }; + + if( FD_UNLIKELY( ctx->bundle.enabled ) ) { + long bundle_time = -fd_tickcount(); + fd_acct_addr_t tip_payment_config[1]; + fd_acct_addr_t tip_receiver[1]; + fd_bundle_crank_get_addresses( ctx->bundle.gen, epoch, tip_payment_config, tip_receiver ); + + fd_acct_addr_t _dummy[1]; + uchar dummy[1]; + + void const * bank = ctx->current_leader_bank; + + /* Calling rust from a C function that is CALLED_FROM_RUST risks + deadlock. In this case, I checked the load_account function and + ensured it never calls any C functions that acquire the lock. */ + ulong sz1 = sizeof(config), sz2 = 1UL; + int found1 = fd_ext_bank_load_account( bank, 0, tip_payment_config->b, _dummy->b, (uchar *)config, &sz1 ); + int found2 = fd_ext_bank_load_account( bank, 0, tip_receiver->b, tip_receiver_owner->b, dummy, &sz2 ); + /* The bundle crank code detects whether the accounts were found by + whether they have non-zero values (since found and uninitialized + should be treated the same), so we actually don't really care + about the value of found{1,2}. */ + (void)found1; (void)found2; + bundle_time += fd_tickcount(); + fd_histf_sample( ctx->bundle_init_delay, (ulong)bundle_time ); + } + long slot_start_ns = ctx->reset_slot_start_ns + (long)((double)(slot-ctx->reset_slot)*ctx->slot_duration_ns); /* No need to check flow control, there are always credits became when we @@ -948,6 +997,11 @@ publish_became_leader( fd_poh_ctx_t * ctx, leader->max_microblocks_in_slot = ctx->max_microblocks_per_slot; leader->ticks_per_slot = ctx->ticks_per_slot; leader->total_skipped_ticks = ctx->ticks_per_slot*(slot-ctx->reset_slot); + leader->epoch = epoch; + leader->bundle->config[0] = config[0]; + + memcpy( leader->bundle->last_blockhash, ctx->reset_hash, 32UL ); + memcpy( leader->bundle->tip_receiver_owner, tip_receiver_owner, 32UL ); if( FD_UNLIKELY( leader->ticks_per_slot+leader->total_skipped_ticks>=MAX_SKIPPED_TICKS ) ) FD_LOG_ERR(( "Too many skipped ticks %lu for slot %lu, chain must halt", leader->ticks_per_slot+leader->total_skipped_ticks, slot )); @@ -966,6 +1020,7 @@ publish_became_leader( fd_poh_ctx_t * ctx, CALLED_FROM_RUST void fd_ext_poh_begin_leader( void const * bank, ulong slot, + ulong epoch, ulong hashcnt_per_tick ) { fd_poh_ctx_t * ctx = fd_ext_poh_write_lock(); @@ -1026,7 +1081,7 @@ fd_ext_poh_begin_leader( void const * bank, FD_TEST( ctx->highwater_leader_slot==ULONG_MAX || slot>=ctx->highwater_leader_slot ); ctx->highwater_leader_slot = fd_ulong_max( fd_ulong_if( ctx->highwater_leader_slot==ULONG_MAX, 0UL, ctx->highwater_leader_slot ), slot ); - publish_became_leader( ctx, slot ); + publish_became_leader( ctx, slot, epoch ); FD_LOG_INFO(( "fd_ext_poh_begin_leader(slot=%lu, highwater_leader_slot=%lu, last_slot=%lu, last_hashcnt=%lu)", slot, ctx->highwater_leader_slot, ctx->last_slot, ctx->last_hashcnt )); fd_ext_poh_write_unlock(); @@ -1632,9 +1687,10 @@ during_housekeeping( fd_poh_ctx_t * ctx ) { static inline void metrics_write( fd_poh_ctx_t * ctx ) { - FD_MHIST_COPY( POH, BEGIN_LEADER_DELAY_SECONDS, ctx->begin_leader_delay ); - FD_MHIST_COPY( POH, FIRST_MICROBLOCK_DELAY_SECONDS, ctx->first_microblock_delay ); - FD_MHIST_COPY( POH, SLOT_DONE_DELAY_SECONDS, ctx->slot_done_delay ); + FD_MHIST_COPY( POH, BEGIN_LEADER_DELAY_SECONDS, ctx->begin_leader_delay ); + FD_MHIST_COPY( POH, FIRST_MICROBLOCK_DELAY_SECONDS, ctx->first_microblock_delay ); + FD_MHIST_COPY( POH, SLOT_DONE_DELAY_SECONDS, ctx->slot_done_delay ); + FD_MHIST_COPY( POH, BUNDLE_INITIALIZE_DELAY_SECONDS, ctx->bundle_init_delay ); } static int @@ -1931,6 +1987,13 @@ privileged_init( fd_topo_t * topo, const uchar * identity_key = fd_keyload_load( tile->poh.identity_key_path, /* pubkey only: */ 1 ); fd_memcpy( ctx->identity_key.uc, identity_key, 32UL ); + + if( FD_UNLIKELY( tile->poh.bundle.enabled ) ) { + if( FD_UNLIKELY( !fd_base58_decode_32( tile->poh.bundle.vote_account_path, ctx->bundle.vote_account.uc ) ) ) { + const uchar * vote_key = fd_keyload_load( tile->poh.bundle.vote_account_path, /* pubkey only: */ 1 ); + fd_memcpy( ctx->bundle.vote_account.uc, vote_key, 32UL ); + } + } } /* The Agave client needs to communicate to the shred tile what @@ -2073,6 +2136,16 @@ unprivileged_init( fd_topo_t * topo, ctx->max_active_descendant = 0UL; + if( FD_UNLIKELY( tile->poh.bundle.enabled ) ) { + ctx->bundle.enabled = 1; + NONNULL( fd_bundle_crank_gen_init( ctx->bundle.gen, (fd_acct_addr_t const *)tile->poh.bundle.tip_distribution_program_addr, + (fd_acct_addr_t const *)tile->poh.bundle.tip_payment_program_addr, + (fd_acct_addr_t const *)ctx->bundle.vote_account.uc, + (fd_acct_addr_t const *)ctx->bundle.vote_account.uc, 0UL ) ); /* last two arguments are properly bogus */ + } else { + ctx->bundle.enabled = 0; + } + ulong poh_shred_obj_id = fd_pod_query_ulong( topo->props, "poh_shred", ULONG_MAX ); FD_TEST( poh_shred_obj_id!=ULONG_MAX ); @@ -2126,6 +2199,9 @@ unprivileged_init( fd_topo_t * topo, fd_histf_join( fd_histf_new( ctx->slot_done_delay, FD_MHIST_SECONDS_MIN( POH, SLOT_DONE_DELAY_SECONDS ), FD_MHIST_SECONDS_MAX( POH, SLOT_DONE_DELAY_SECONDS ) ) ); + fd_histf_join( fd_histf_new( ctx->bundle_init_delay, FD_MHIST_SECONDS_MIN( POH, BUNDLE_INITIALIZE_DELAY_SECONDS ), + FD_MHIST_SECONDS_MAX( POH, BUNDLE_INITIALIZE_DELAY_SECONDS ) ) ); + for( ulong i=0UL; iin_cnt; i++ ) { fd_topo_link_t * link = &topo->links[ tile->in_link_id[ i ] ]; fd_topo_wksp_t * link_wksp = &topo->workspaces[ topo->objs[ link->dcache_obj_id ].wksp_id ]; diff --git a/src/app/fdctl/run/tiles/fd_sign.c b/src/app/fdctl/run/tiles/fd_sign.c index 67fa43f9d4..b041239f5a 100644 --- a/src/app/fdctl/run/tiles/fd_sign.c +++ b/src/app/fdctl/run/tiles/fd_sign.c @@ -292,6 +292,11 @@ unprivileged_init_sensitive( fd_topo_t * topo, FD_TEST( !strcmp( out_link->name, "sign_event" ) ); FD_TEST( in_link->mtu==32UL ); FD_TEST( out_link->mtu==64UL ); + } else if( !strcmp(in_link->name, "pack_sign" ) ) { + ctx->in_role[ i ] = FD_KEYGUARD_ROLE_BUNDLE_CRANK; + FD_TEST( !strcmp( out_link->name, "sign_pack" ) ); + FD_TEST( in_link->mtu==1232UL ); + FD_TEST( out_link->mtu==64UL ); } else { FD_LOG_CRIT(( "unexpected link %s", in_link->name )); } diff --git a/src/app/fdctl/run/topos/fd_frankendancer.c b/src/app/fdctl/run/topos/fd_frankendancer.c index 229f2d687a..8967cd587b 100644 --- a/src/app/fdctl/run/topos/fd_frankendancer.c +++ b/src/app/fdctl/run/topos/fd_frankendancer.c @@ -113,7 +113,7 @@ fd_topo_initialize( config_t * config ) { FOR(verify_tile_cnt) fd_topob_tile( topo, "verify", "verify", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); /**/ fd_topob_tile( topo, "dedup", "dedup", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); FOR(resolv_tile_cnt) fd_topob_tile( topo, "resolv", "resolv", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0 ); - /**/ fd_topob_tile( topo, "pack", "pack", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 0 ); + /**/ fd_topob_tile( topo, "pack", "pack", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, config->tiles.bundle.enabled ); FOR(bank_tile_cnt) fd_topob_tile( topo, "bank", "bank", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 0 ); /**/ fd_topob_tile( topo, "poh", "poh", "metric_in", tile_to_cpu[ topo->tile_cnt ], 1, 1 ); FOR(shred_tile_cnt) fd_topob_tile( topo, "shred", "shred", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); @@ -235,13 +235,17 @@ fd_topo_initialize( config_t * config ) { if( FD_UNLIKELY( config->tiles.bundle.enabled ) ) { fd_topob_wksp( topo, "bundle_verif" ); - fd_topob_wksp( topo, "bundle_sign" ); - fd_topob_wksp( topo, "sign_bundle" ); - fd_topob_wksp( topo, "bundle" ); + fd_topob_wksp( topo, "bundle_sign" ); + fd_topob_wksp( topo, "sign_bundle" ); + fd_topob_wksp( topo, "pack_sign" ); + fd_topob_wksp( topo, "sign_pack" ); + fd_topob_wksp( topo, "bundle" ); /**/ fd_topob_link( topo, "bundle_verif", "bundle_verif", config->tiles.verify.receive_buffer_size, FD_TPU_PARSED_MTU, 1UL ); /**/ fd_topob_link( topo, "bundle_sign", "bundle_sign", 128UL, 9UL, 1UL ); /**/ fd_topob_link( topo, "sign_bundle", "sign_bundle", 128UL, 64UL, 1UL ); + /**/ fd_topob_link( topo, "pack_sign", "pack_sign", 128UL, 1232UL, 1UL ); + /**/ fd_topob_link( topo, "sign_pack", "sign_pack", 128UL, 64UL, 1UL ); /**/ fd_topob_tile( topo, "bundle", "bundle", "metric_in", tile_to_cpu[ topo->tile_cnt ], 0, 1 ); @@ -253,6 +257,11 @@ fd_topo_initialize( config_t * config ) { /**/ fd_topob_tile_in( topo, "bundle", 0UL, "metric_in", "sign_bundle", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_bundle", 0UL ); + /**/ fd_topob_tile_in( topo, "sign", 0UL, "metric_in", "pack_sign", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_POLLED ); + /**/ fd_topob_tile_out( topo, "pack", 0UL, "pack_sign", 0UL ); + /**/ fd_topob_tile_in( topo, "pack", 0UL, "metric_in", "sign_pack", 0UL, FD_TOPOB_UNRELIABLE, FD_TOPOB_UNPOLLED ); + /**/ fd_topob_tile_out( topo, "sign", 0UL, "sign_pack", 0UL ); + if( plugins_enabled ) { fd_topob_wksp( topo, "bundle_plugi" ); /* bundle_plugi must be kind of deep, to prevent exhausting shared @@ -375,6 +384,20 @@ fd_topo_initialize( config_t * config ) { tile->pack.larger_shred_limits_per_block = config->development.bench.larger_shred_limits_per_block; tile->pack.use_consumed_cus = config->tiles.pack.use_consumed_cus; + if( FD_UNLIKELY( config->tiles.bundle.enabled ) ) { +#define PARSE_PUBKEY( _tile, f ) \ + if( FD_UNLIKELY( !fd_base58_decode_32( config->tiles.bundle.f, tile->_tile.bundle.f ) ) ) \ + FD_LOG_ERR(( "[tiles.bundle.enabled] set to true, but failed to parse [tiles.bundle."#f"] %s", config->tiles.bundle.f )); + tile->pack.bundle.enabled = 1; + PARSE_PUBKEY( pack, tip_distribution_program_addr ); + PARSE_PUBKEY( pack, tip_payment_program_addr ); + PARSE_PUBKEY( pack, tip_distribution_authority ); + tile->pack.bundle.commission_bps = config->tiles.bundle.commission_bps; + strncpy( tile->pack.bundle.identity_key_path, config->consensus.identity_path, sizeof(tile->pack.bundle.identity_key_path) ); + strncpy( tile->pack.bundle.vote_account_path, config->consensus.vote_account_path, sizeof(tile->pack.bundle.vote_account_path) ); + } else { + fd_memset( &tile->pack.bundle, '\0', sizeof(tile->pack.bundle) ); + } } else if( FD_UNLIKELY( !strcmp( tile->name, "bank" ) ) ) { } else if( FD_UNLIKELY( !strcmp( tile->name, "poh" ) ) ) { @@ -384,6 +407,16 @@ fd_topo_initialize( config_t * config ) { tile->poh.bank_cnt = config->layout.bank_tile_count; tile->poh.lagged_consecutive_leader_start = config->tiles.poh.lagged_consecutive_leader_start; + if( FD_UNLIKELY( config->tiles.bundle.enabled ) ) { + tile->poh.bundle.enabled = 1; + PARSE_PUBKEY( poh, tip_distribution_program_addr ); + PARSE_PUBKEY( poh, tip_payment_program_addr ); + strncpy( tile->poh.bundle.vote_account_path, config->consensus.vote_account_path, sizeof(tile->poh.bundle.vote_account_path) ); +#undef PARSE_PUBKEY + } else { + fd_memset( &tile->poh.bundle, '\0', sizeof(tile->poh.bundle) ); + } + } else if( FD_UNLIKELY( !strcmp( tile->name, "shred" ) ) ) { fd_memcpy( tile->shred.src_mac_addr, config->tiles.net.mac_addr, 6 ); strncpy( tile->shred.identity_key_path, config->consensus.identity_path, sizeof(tile->shred.identity_key_path) ); diff --git a/src/app/fddev/dev.c b/src/app/fddev/dev.c index 1a121a8ad5..955fb3b653 100644 --- a/src/app/fddev/dev.c +++ b/src/app/fddev/dev.c @@ -117,12 +117,23 @@ update_config_for_dev( config_t * const config ) { } } - if( FD_LIKELY( !strcmp( config->consensus.vote_account_path, "" ) ) ) + if( FD_LIKELY( !strcmp( config->consensus.vote_account_path, "" ) ) ) { FD_TEST( fd_cstr_printf_check( config->consensus.vote_account_path, sizeof( config->consensus.vote_account_path ), NULL, "%s/vote-account.json", config->scratch_directory ) ); + /* If using bundles, pack and poh need the vote account too */ + if( FD_UNLIKELY( config->tiles.bundle.enabled ) ) { + ulong pack_id = fd_topo_find_tile( &config->topo, "pack", 0 ); + fd_topo_tile_t * pack_topo = &config->topo.tiles[ pack_id ]; + memcpy( pack_topo->pack.bundle.vote_account_path, config->consensus.vote_account_path, sizeof(pack_topo->pack.bundle.vote_account_path) ); + + ulong poh_id = fd_topo_find_tile( &config->topo, "poh", 0 ); + fd_topo_tile_t * poh_topo = &config->topo.tiles[ poh_id ]; + memcpy( poh_topo->poh.bundle.vote_account_path, config->consensus.vote_account_path, sizeof(poh_topo->poh.bundle.vote_account_path) ); + } + } ulong gui_idx = fd_topo_find_tile( &config->topo, "gui", 0UL ); if( FD_LIKELY( gui_idx!=ULONG_MAX ) ) { diff --git a/src/disco/keyguard/fd_keyguard.h b/src/disco/keyguard/fd_keyguard.h index cdb53be768..5c9f2d1bc4 100644 --- a/src/disco/keyguard/fd_keyguard.h +++ b/src/disco/keyguard/fd_keyguard.h @@ -16,13 +16,14 @@ FD_PROTOTYPES_BEGIN /* Role definitions ***************************************************/ -#define FD_KEYGUARD_ROLE_VOTER (0) /* vote transaction sender */ -#define FD_KEYGUARD_ROLE_GOSSIP (1) /* gossip participant */ -#define FD_KEYGUARD_ROLE_LEADER (2) /* block producer (shreds) */ -#define FD_KEYGUARD_ROLE_REPAIR (4) /* Repair tile */ -#define FD_KEYGUARD_ROLE_BUNDLE (5) /* Bundle tile */ -#define FD_KEYGUARD_ROLE_EVENT (6) /* Event tile */ -#define FD_KEYGUARD_ROLE_CNT (7) /* number of known roles */ +#define FD_KEYGUARD_ROLE_VOTER (0) /* vote transaction sender */ +#define FD_KEYGUARD_ROLE_GOSSIP (1) /* gossip participant */ +#define FD_KEYGUARD_ROLE_LEADER (2) /* block producer (shreds) */ +#define FD_KEYGUARD_ROLE_REPAIR (4) /* Repair tile */ +#define FD_KEYGUARD_ROLE_BUNDLE (5) /* Bundle tile */ +#define FD_KEYGUARD_ROLE_EVENT (6) /* Event tile */ +#define FD_KEYGUARD_ROLE_BUNDLE_CRANK (7) /* Sign cranking transactions for bundle tips */ +#define FD_KEYGUARD_ROLE_CNT (8) /* number of known roles */ /* Payload types ******************************************************/ @@ -44,7 +45,7 @@ FD_PROTOTYPES_BEGIN #define FD_KEYGUARD_PAYLOAD_REPAIR (1UL< Delay between when we become leader in a slot and when we finish the slot. + + Delay in starting the slot caused by loading the information needed to generate the bundle crank transactions + diff --git a/src/disco/pack/fd_pack.c b/src/disco/pack/fd_pack.c index f1faf493ff..45d5ae4067 100644 --- a/src/disco/pack/fd_pack.c +++ b/src/disco/pack/fd_pack.c @@ -409,8 +409,8 @@ typedef struct fd_pack_smallest fd_pack_smallest_t; penalty_map is the fd_map_dynamic that maps accounts to their respective penalty treaps. */ struct fd_pack_penalty_treap { - fd_acct_addr_t key; - treap_t penalty_treap[1]; + fd_acct_addr_t key; + treap_t penalty_treap[1]; }; typedef struct fd_pack_penalty_treap fd_pack_penalty_treap_t; @@ -448,7 +448,7 @@ typedef struct fd_pack_penalty_treap fd_pack_penalty_treap_t; /* Finally, we can now declare the main pack data structure */ struct fd_pack_private { ulong pack_depth; - int enable_bundles; /* if 0, bundles are disabled */ + ulong bundle_meta_sz; /* if 0, bundles are disabled */ ulong bank_tile_cnt; fd_pack_limits_t lim[1]; @@ -504,13 +504,6 @@ struct fd_pack_private { transitions between them. */ int initializer_bundle_state; - /* skip_first_bundle: when in the [Pending], [Failed], or - [Ready] states, if skip_first_bundle is 1, then the first bundle is - the most recently scheduled initialization bundle and should be - skipped when scheduling. Contents are not specified when in the - [Not Initialized] state. */ - int skip_first_bundle; - /* pending_bundle_cnt: the number of bundles in pending_bundles. */ ulong pending_bundle_cnt; @@ -632,6 +625,12 @@ struct fd_pack_private { /* chdkup: scratch memory chkdup needs for its internal processing */ fd_chkdup_t chkdup[ 1 ]; + + /* bundle_meta: an array, parallel to the pool, with each element + having size bundle_meta_sz. I.e. if pool[i] has an associated + bundle meta, it's located at bundle_meta[j] for j in + [i*bundle_meta_sz, (i+1)*bundle_meta_sz). */ + void * bundle_meta; }; typedef struct fd_pack_private fd_pack_t; @@ -644,12 +643,13 @@ static inline void insert_bundle_impl( fd_pack_t * pack, ulong bundle_idx, ulong FD_FN_PURE ulong fd_pack_footprint( ulong pack_depth, - int enable_bundles, + ulong bundle_meta_sz, ulong bank_tile_cnt, fd_pack_limits_t const * limits ) { if( FD_UNLIKELY( (bank_tile_cnt==0) | (bank_tile_cnt>FD_PACK_MAX_BANK_TILES) ) ) return 0UL; if( FD_UNLIKELY( pack_depth<4UL ) ) return 0UL; + int enable_bundles = !!bundle_meta_sz; ulong l; ulong extra_depth = fd_ulong_if( enable_bundles, 1UL+FD_PACK_MAX_TXN_PER_BUNDLE, 1UL ); /* space for use between init and fini */ ulong max_acct_in_treap = pack_depth * FD_TXN_ACCT_ADDR_MAX; @@ -684,17 +684,19 @@ fd_pack_footprint( ulong pack_depth, l = FD_LAYOUT_APPEND( l, 32UL, sizeof(fd_pack_addr_use_t)*max_acct_in_flight ); /* use_by_bank */ l = FD_LAYOUT_APPEND( l, 32UL, sizeof(ulong)*max_txn_in_flight ); /* use_by_bank_txn*/ l = FD_LAYOUT_APPEND( l, bitset_map_align(), bitset_map_footprint( lg_acct_in_trp ) ); /* acct_to_bitset */ + l = FD_LAYOUT_APPEND( l, 64UL, (pack_depth+extra_depth)*bundle_meta_sz ); /* bundle_meta */ return FD_LAYOUT_FINI( l, FD_PACK_ALIGN ); } void * fd_pack_new( void * mem, ulong pack_depth, - int enable_bundles, + ulong bundle_meta_sz, ulong bank_tile_cnt, fd_pack_limits_t const * limits, fd_rng_t * rng ) { + int enable_bundles = !!bundle_meta_sz; ulong extra_depth = fd_ulong_if( enable_bundles, 1UL+FD_PACK_MAX_TXN_PER_BUNDLE, 1UL ); ulong max_acct_in_treap = pack_depth * FD_TXN_ACCT_ADDR_MAX; ulong max_txn_per_mblk = fd_ulong_max( limits->max_txn_per_microblock, @@ -730,9 +732,10 @@ fd_pack_new( void * mem, void * _use_by_bank = FD_SCRATCH_ALLOC_APPEND( l, 32UL, sizeof(fd_pack_addr_use_t)*max_acct_in_flight ); void * _use_by_txn = FD_SCRATCH_ALLOC_APPEND( l, 32UL, sizeof(ulong)*max_txn_in_flight ); void * _acct_bitset = FD_SCRATCH_ALLOC_APPEND( l, bitset_map_align(), bitset_map_footprint( lg_acct_in_trp ) ); + void * bundle_meta = FD_SCRATCH_ALLOC_APPEND( l, 64UL, (pack_depth+extra_depth)*bundle_meta_sz ); pack->pack_depth = pack_depth; - pack->enable_bundles = enable_bundles; + pack->bundle_meta_sz = bundle_meta_sz; pack->bank_tile_cnt = bank_tile_cnt; pack->lim[0] = *limits; pack->pending_txn_cnt = 0UL; @@ -816,6 +819,8 @@ fd_pack_new( void * mem, fd_chkdup_new( pack->chkdup, rng ); + pack->bundle_meta = bundle_meta; + return mem; } @@ -824,11 +829,12 @@ fd_pack_join( void * mem ) { FD_SCRATCH_ALLOC_INIT( l, mem ); fd_pack_t * pack = FD_SCRATCH_ALLOC_APPEND( l, FD_PACK_ALIGN, sizeof(fd_pack_t) ); + int enable_bundles = !!pack->bundle_meta_sz; ulong pack_depth = pack->pack_depth; - ulong extra_depth = fd_ulong_if( pack->enable_bundles, 1UL+FD_PACK_MAX_TXN_PER_BUNDLE, 1UL ); + ulong extra_depth = fd_ulong_if( enable_bundles, 1UL+FD_PACK_MAX_TXN_PER_BUNDLE, 1UL ); ulong bank_tile_cnt = pack->bank_tile_cnt; ulong max_txn_per_microblock = fd_ulong_max( pack->lim->max_txn_per_microblock, - fd_ulong_if( pack->enable_bundles, FD_PACK_MAX_TXN_PER_BUNDLE, 0UL ) ); + fd_ulong_if( enable_bundles, FD_PACK_MAX_TXN_PER_BUNDLE, 0UL ) ); ulong max_acct_in_treap = pack_depth * FD_TXN_ACCT_ADDR_MAX; ulong max_acct_in_flight = bank_tile_cnt * (FD_TXN_ACCT_ADDR_MAX * max_txn_per_microblock + 1UL); @@ -836,7 +842,7 @@ fd_pack_join( void * mem ) { ulong max_w_per_block = fd_ulong_min( pack->lim->max_cost_per_block / FD_PACK_COST_PER_WRITABLE_ACCT, max_txn_per_microblock * pack->lim->max_microblocks_per_block * FD_TXN_ACCT_ADDR_MAX ); ulong written_list_max = fd_ulong_min( max_w_per_block>>1, DEFAULT_WRITTEN_LIST_MAX ); - ulong bundle_temp_accts = fd_ulong_if( pack->enable_bundles, FD_PACK_MAX_TXN_PER_BUNDLE*FD_TXN_ACCT_ADDR_MAX, 1UL ); + ulong bundle_temp_accts = fd_ulong_if( enable_bundles, FD_PACK_MAX_TXN_PER_BUNDLE*FD_TXN_ACCT_ADDR_MAX, 1UL ); int lg_uses_tbl_sz = fd_ulong_find_msb( fd_ulong_pow2_up( 2UL*max_acct_in_flight ) ); int lg_max_writers = fd_ulong_find_msb( fd_ulong_pow2_up( 2UL*max_w_per_block ) ); @@ -857,6 +863,7 @@ fd_pack_join( void * mem ) { /* */ FD_SCRATCH_ALLOC_APPEND( l, 32UL, sizeof(fd_pack_addr_use_t)*max_acct_in_flight ); /* */ FD_SCRATCH_ALLOC_APPEND( l, 32UL, sizeof(ulong)*max_txn_in_flight ); pack->acct_to_bitset= bitset_map_join( FD_SCRATCH_ALLOC_APPEND( l, bitset_map_align(), bitset_map_footprint( lg_acct_in_trp ) ) ); + /* */ FD_SCRATCH_ALLOC_APPEND( l, 64UL, (pack_depth+extra_depth)*pack->bundle_meta_sz ); FD_MGAUGE_SET( PACK, PENDING_TRANSACTIONS_HEAP_SIZE, pack->pack_depth ); return pack; @@ -1202,7 +1209,7 @@ fd_pack_insert_txn_fini( fd_pack_t * pack, ord->expires_at = expires_at; int is_vote = est_result==1; - int validation_result = validate_transaction( pack, ord, txn, accts, alt_adj, pack->enable_bundles ); + int validation_result = validate_transaction( pack, ord, txn, accts, alt_adj, !!pack->bundle_meta_sz ); if( FD_UNLIKELY( validation_result ) ) { trp_pool_ele_release( pack->pool, ord ); return validation_result; @@ -1296,12 +1303,18 @@ fd_pack_insert_bundle_cancel( fd_pack_t * pack, for( ulong i=0UL; ipool, (fd_pack_ord_txn_t*)bundle[ txn_cnt-1UL-i ] ); } +/* Explained below */ +#define BUNDLE_L_PRIME 37896771UL +#define BUNDLE_N 312671UL +#define RC_TO_REL_BUNDLE_IDX( r, c ) (BUNDLE_N - ((ulong)(r) * 1UL<<32)/((ulong)(c) * BUNDLE_L_PRIME)) + int fd_pack_insert_bundle_fini( fd_pack_t * pack, fd_txn_e_t * const * bundle, ulong txn_cnt, ulong expires_at, - int initializer_bundle ) { + int initializer_bundle, + void const * bundle_meta ) { int err = 0; @@ -1312,9 +1325,9 @@ fd_pack_insert_bundle_fini( fd_pack_t * pack, bundles are coming in a pre-prioritized order, so it doesn't make sense to drop an earlier bundle for this one. That means that really, the best thing to do is drop this one. */ - if( FD_UNLIKELY( pending_b_txn_cnt+txn_cnt>pack->pack_depth/2UL ) ) err = FD_PACK_INSERT_REJECT_PRIORITY; + if( FD_UNLIKELY( (!initializer_bundle)&(pending_b_txn_cnt+txn_cnt>pack->pack_depth/2UL) ) ) err = FD_PACK_INSERT_REJECT_PRIORITY; - if( FD_UNLIKELY( expires_atexpire_before ) ) err = FD_PACK_INSERT_REJECT_EXPIRED; + if( FD_UNLIKELY( expires_atexpire_before ) ) err = FD_PACK_INSERT_REJECT_EXPIRED; for( ulong i=0UL; (itxnp->flags |= FD_TXN_P_FLAGS_BUNDLE; + bundle[ i ]->txnp->flags &= ~FD_TXN_P_FLAGS_INITIALIZER_BUNDLE; bundle[ i ]->txnp->flags |= fd_uint_if( initializer_bundle, FD_TXN_P_FLAGS_INITIALIZER_BUNDLE, 0 ); ord->expires_at = expires_at; - int validation_result = validate_transaction( pack, ord, txn, accts, alt_adj, 1 ); + int validation_result = validate_transaction( pack, ord, txn, accts, alt_adj, !initializer_bundle ); if( FD_UNLIKELY( validation_result ) ) { err = validation_result; break; } } @@ -1342,6 +1356,17 @@ fd_pack_insert_bundle_fini( fd_pack_t * pack, return err; } + if( FD_UNLIKELY( initializer_bundle && pending_b_txn_cnt>0UL ) ) { + treap_rev_iter_t _cur=treap_rev_iter_init( pack->pending_bundles, pack->pool ); + FD_TEST( !treap_rev_iter_done( _cur ) ); + fd_pack_ord_txn_t * cur = treap_rev_iter_ele( _cur, pack->pool ); + int is_ib = !!(cur->txn->flags & FD_TXN_P_FLAGS_INITIALIZER_BUNDLE); + + /* Delete the previous IB if there is one */ + if( FD_UNLIKELY( is_ib && 0UL==RC_TO_REL_BUNDLE_IDX( cur->rewards, cur->compute_est ) ) ) + delete_transaction( pack, fd_txn_get_signatures( TXN(cur->txn), cur->txn->payload ), 1, 0 ); + } + int replaces = 0; while( FD_UNLIKELY( pack->pending_txn_cnt+txn_cnt > pack->pack_depth ) ) { if( FD_UNLIKELY( !delete_worst( pack, FLT_MAX, 0 ) ) ) { @@ -1352,8 +1377,11 @@ fd_pack_insert_bundle_fini( fd_pack_t * pack, } if( FD_UNLIKELY( !pending_b_txn_cnt ) ) { - pack->relative_bundle_idx = 0UL; - pack->skip_first_bundle = 0; + pack->relative_bundle_idx = 1UL; + } + + if( FD_LIKELY( bundle_meta ) ) { + memcpy( (uchar *)pack->bundle_meta + (ulong)((fd_pack_ord_txn_t *)bundle[0]-pack->pool)*pack->bundle_meta_sz, bundle_meta, pack->bundle_meta_sz ); } /* We put bundles in a treap just like all the other transactions, but @@ -1463,11 +1491,13 @@ fd_pack_insert_bundle_fini( fd_pack_t * pack, if( FD_UNLIKELY( pack->relative_bundle_idx>BUNDLE_N ) ) { FD_LOG_WARNING(( "Too many bundles inserted without allowing pending bundles to go empty. " "Ordering of bundles may be incorrect." )); - pack->relative_bundle_idx = 0UL; - pack->skip_first_bundle = 0; + pack->relative_bundle_idx = 1UL; } - insert_bundle_impl( pack, pack->relative_bundle_idx, txn_cnt, (fd_pack_ord_txn_t * *)bundle, expires_at ); - pack->relative_bundle_idx++; + ulong bundle_idx = fd_ulong_if( initializer_bundle, 0UL, pack->relative_bundle_idx ); + insert_bundle_impl( pack, bundle_idx, txn_cnt, (fd_pack_ord_txn_t * *)bundle, expires_at ); + /* if IB this is max( 1, x ), which is x. Otherwise, this is max(x, + x+1) which is x++ */ + pack->relative_bundle_idx = fd_ulong_max( bundle_idx+1UL, pack->relative_bundle_idx ); return replaces ? FD_PACK_INSERT_ACCEPT_NONVOTE_REPLACE : FD_PACK_INSERT_ACCEPT_NONVOTE_ADD; @@ -1507,65 +1537,19 @@ insert_bundle_impl( fd_pack_t * pack, } -#define RC_TO_REL_BUNDLE_IDX( r, c ) (BUNDLE_N - ((ulong)(r) * 1UL<<32)/((ulong)(c) * BUNDLE_L_PRIME)) - -void -fd_pack_rewrite_initializer_bundles( fd_pack_t * pack, - fd_pack_ib_rewriter_fn_t rewriter, - void * ctx ) { - fd_txn_e_t * bundle[ FD_PACK_MAX_TXN_PER_BUNDLE ]; - ulong i = 0UL; - ulong cur_bundle = 0UL; - ulong expires_at[1] = { 0UL }; - - /* Stash a copy of skip_first_bundle because delete will clear it */ - int skip_first_bundle = pack->skip_first_bundle; +void const * +fd_pack_peek_bundle_meta( fd_pack_t const * pack ) { + int ib_state = pack->initializer_bundle_state; + if( FD_UNLIKELY( (ib_state==FD_PACK_IB_STATE_PENDING) | (ib_state==FD_PACK_IB_STATE_FAILED) ) ) return NULL; - treap_rev_iter_t _next = treap_idx_null(); - for( treap_rev_iter_t _cur=treap_rev_iter_init( pack->pending_bundles, pack->pool ); !treap_rev_iter_done( _cur ); _cur=_next ) { - _next = treap_rev_iter_next( _cur, pack->pool ); + treap_rev_iter_t _cur=treap_rev_iter_init( pack->pending_bundles, pack->pool ); + if( FD_UNLIKELY( treap_rev_iter_done( _cur ) ) ) return NULL; /* empty */ - fd_pack_ord_txn_t * cur = treap_rev_iter_ele( _cur, pack->pool ); - int is_ib = !!(cur->txn->flags & FD_TXN_P_FLAGS_INITIALIZER_BUNDLE); + fd_pack_ord_txn_t * cur = treap_rev_iter_ele( _cur, pack->pool ); + int is_ib = !!(cur->txn->flags & FD_TXN_P_FLAGS_INITIALIZER_BUNDLE); + if( FD_UNLIKELY( is_ib ) ) return NULL; - if( FD_UNLIKELY( is_ib ) ) { - cur_bundle = RC_TO_REL_BUNDLE_IDX( cur->rewards, cur->compute_est ); - bundle[ i++ ] = cur->txn_e; - } - - /* The only bundle boundary we care about is the end of an - initializer bundle, so there's no need to put any effort into - finding the boundaries between non-initializer bundles. */ - if( FD_LIKELY( i==0UL ) ) continue; - - int last_in_bundle = 0; - if( FD_UNLIKELY ( treap_rev_iter_done( _next ) ) ) last_in_bundle = 1; - else { - fd_pack_ord_txn_t * next = treap_rev_iter_ele( _next, pack->pool ); - last_in_bundle = (cur_bundle != RC_TO_REL_BUNDLE_IDX( next->rewards, next->compute_est )); - } - - if( FD_UNLIKELY( last_in_bundle ) ) { - /* We're going to carefully delete the bundle, call rewrite, then - re-add them brand new with the same bundle idx so that they end - up in the same spot. Assuming rewriter obeys the contract in - the documentation, nothing can touch these transactions between - when they're deleted and we call the rewriter, so this is safe. - We need to make sure they don't remain in the pool's free list - though. */ - for( ulong j=0UL; jtxnp), bundle[j]->txnp->payload ), 0, 0 ); - FD_TEST( trp_pool_ele_acquire( pack->pool )->txn_e==bundle[j] ); - } - - rewriter( bundle, i, expires_at, ctx ); - - insert_bundle_impl( pack, cur_bundle, i, (fd_pack_ord_txn_t **)fd_type_pun( bundle ), *expires_at ); - - i = 0; - } - } - pack->skip_first_bundle = skip_first_bundle; + return (void const *)((uchar const *)pack->bundle_meta + (ulong)_cur * pack->bundle_meta_sz); } void @@ -2059,24 +2043,12 @@ fd_pack_try_schedule_bundle( fd_pack_t * pack, fd_pack_ord_txn_t * pool = pack->pool; treap_t * bundles = pack->pending_bundles; - int skip_first, require_ib; - if( FD_UNLIKELY( state==FD_PACK_IB_STATE_NOT_INITIALIZED ) ) { skip_first = 0; require_ib = 1; } - if( FD_LIKELY ( state==FD_PACK_IB_STATE_READY ) ) { skip_first = pack->skip_first_bundle; require_ib = 0; } + int require_ib; + if( FD_UNLIKELY( state==FD_PACK_IB_STATE_NOT_INITIALIZED ) ) { require_ib = 1; } + if( FD_LIKELY ( state==FD_PACK_IB_STATE_READY ) ) { require_ib = 0; } treap_rev_iter_t _cur = treap_rev_iter_init( bundles, pool ); ulong bundle_idx = ULONG_MAX; - while( skip_first & !treap_rev_iter_done( _cur ) ) { - fd_pack_ord_txn_t * cur = treap_rev_iter_ele( _cur, pool ); - ulong this_bundle_idx = RC_TO_REL_BUNDLE_IDX( cur->rewards, cur->compute_est ); - int matches = (bundle_idx==ULONG_MAX) | (this_bundle_idx==bundle_idx); - bundle_idx = this_bundle_idx; - - /* In the common case of single-transaction IBs, this is false the - first time and true the second time through, so hard to say - LIKELY/UNLIKELY */ - if( !matches ) break; - _cur = treap_rev_iter_next( _cur, pool ); - } if( FD_UNLIKELY( treap_rev_iter_done( _cur ) ) ) return TRY_BUNDLE_NO_READY_BUNDLES; @@ -2086,14 +2058,6 @@ fd_pack_try_schedule_bundle( fd_pack_t * pack, bundle_idx = RC_TO_REL_BUNDLE_IDX( txn0->rewards, txn0->compute_est ); if( FD_UNLIKELY( require_ib & !is_ib ) ) return TRY_BUNDLE_NO_READY_BUNDLES; - if( FD_UNLIKELY( skip_first & is_ib ) ) { - /* This is a new initializer bundle, so the one we skipped is now - superseded. Note: This also clears the skip_first_bundle bit */ - fd_pack_ord_txn_t * ib0 = treap_rev_iter_ele( treap_rev_iter_init( bundles, pool ), pool ); - fd_ed25519_sig_t const * sig0 = fd_txn_get_signatures( TXN(ib0->txn), ib0->txn->payload ); - - delete_transaction( pack, sig0, 1, 0 ); - } /* At this point, we have our candidate bundle, so we'll schedule it if we can. If we can't, we won't schedule anything. */ @@ -2330,7 +2294,6 @@ fd_pack_try_schedule_bundle( fd_pack_t * pack, if( FD_UNLIKELY( is_ib ) ) { pack->initializer_bundle_state = FD_PACK_IB_STATE_PENDING; - pack->skip_first_bundle = 1; } return retval; } @@ -2530,7 +2493,6 @@ fd_pack_end_block( fd_pack_t * pack ) { pack->outstanding_microblock_mask = 0UL; pack->initializer_bundle_state = FD_PACK_IB_STATE_NOT_INITIALIZED; - pack->skip_first_bundle = 0; acct_uses_clear( pack->acct_in_use ); @@ -2693,13 +2655,6 @@ delete_transaction( fd_pack_t * pack, } } - /* Are we deleting the first bundle that we previously wanted to skip? - If so, we need to clear the skip_first_bundle flag? */ - if( FD_UNLIKELY( ((root==pack->pending_bundles) & pack->skip_first_bundle) && - (containing == treap_rev_iter_ele_const( treap_rev_iter_init( pack->pending_bundles, pack->pool ), pack->pool ) ) ) ) { - pack->skip_first_bundle = 0; - } - if( FD_UNLIKELY( delete_full_bundle & (root==pack->pending_bundles) ) ) { /* When we delete, the sturcture of the treap may move around, but pointers to inside the pool will remain valid */ diff --git a/src/disco/pack/fd_pack.h b/src/disco/pack/fd_pack.h index 48e2ffcca5..6dfada179e 100644 --- a/src/disco/pack/fd_pack.h +++ b/src/disco/pack/fd_pack.h @@ -125,8 +125,15 @@ typedef struct fd_pack_private fd_pack_t; pack_depth sets the maximum number of pending transactions that pack stores and may eventually schedule. pack_depth must be at least 4. - If enable_bundles is non-zero, then the bundle-related functions on + If bundle_meta_sz is non-zero, then the bundle-related functions on this pack object can be used, and it can schedule bundles. + Additionally, if bundle_meta_sz is non-zero, then a region of size + bundle_meta_sz bytes (with no additional alignment) will be reserved + for each bundle. + + Note: if you'd like to use bundles, but don't require metadata for + the bundles, simply use a small positive value (e.g. 1), always pass + NULL in insert_bundle_fini, and never call fd_pack_peek_bundle_meta. bank_tile_cnt sets the number of bank tiles to which this pack object can schedule transactions. bank_tile_cnt must be in [1, @@ -139,7 +146,7 @@ FD_FN_CONST static inline ulong fd_pack_align ( void ) { return FD_PACK_AL FD_FN_PURE ulong fd_pack_footprint( ulong pack_depth, - int enable_bundles, + ulong bundle_meta_sz, ulong bank_tile_cnt, fd_pack_limits_t const * limits ); @@ -147,7 +154,7 @@ fd_pack_footprint( ulong pack_depth, /* fd_pack_new formats a region of memory to be suitable for use as a pack object. mem is a non-NULL pointer to a region of memory in the local address space with the required alignment and footprint. - pack_depth, enable_bundles, bank_tile_cnt, and limits are as above. + pack_depth, bundle_meta_sz, bank_tile_cnt, and limits are as above. rng is a local join to a random number generator used to perturb estimates. @@ -156,7 +163,7 @@ fd_pack_footprint( ulong pack_depth, will not be joined to the pack object when this function returns. */ void * fd_pack_new( void * mem, ulong pack_depth, - int enable_bundles, + ulong bundle_meta_sz, ulong bank_tile_cnt, fd_pack_limits_t const * limits, fd_rng_t * rng ); @@ -317,9 +324,8 @@ fd_txn_e_t * fd_pack_insert_txn_init ( fd_pack_t * pack int fd_pack_insert_txn_fini ( fd_pack_t * pack, fd_txn_e_t * txn, ulong expires_at ); void fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_e_t * txn ); - /* fd_pack_insert_bundle_{init,fini,cancel} are parallel to the - similarly named fd_pack_insert_tnx functions but can be used to + similarly named fd_pack_insert_txn functions but can be used to insert a bundle instead of a transaction. fd_pack_insert_bundle_init populates and returns bundle. @@ -353,13 +359,29 @@ void fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_e_t * txn the bundle have the same expires_at value, since if one expires, the whole bundle becomes invalid. - If initializer_bundle is non-zero, the transactions in the bundle will - not be checked against the bundle blacklist; otherwise, the check will be - performed as normal. See the section below on initializer bundles - for more details. Other than the blacklist check, transactions in a - bundle are subject to the same checks as other transactions. If any - transaction in the bundle fails validation, the whole bundle will be - rejected. + If initializer_bundle is non-zero, this bundle will be inserted at + the front of the bundle queue so that it is the next bundle + scheduled. Otherwise, the bundle will be inserted at the back of the + bundle queue, and will be scheduled in FIFO order with the rest of + the bundles. If an initializer bundle is already present in pack's + pending transactions, that bundle will be deleted. Additionally, if + initializer_bundle is non-zero, the transactions in the bundle will + not be checked against the bundle blacklist; otherwise, the check + will be performed as normal. See the section below on initializer + bundles for more details. + + Other than the blacklist check, transactions in a bundle are subject + to the same checks as other transactions. If any transaction in the + bundle fails validation, the whole bundle will be rejected. + + _fini also accepts bundle_meta, an optional opaque pointer to a + region of memory of size bundle_meta_sz (as provided in pack_new). + If bundle_meta is non-NULL, the contents of the memory will be copied + to a metadata region associated with this bundle and can be retrieved + later with fd_pack_peek_bundle_meta. The contents of bundle_meta is + not retrievable if initializer_bundle is non-zero, so you may wish to + just pass NULL in that case. This funtion does not retain any + interest in the contents of bundle_meta after it returns. txn_cnt must be in [1, MAX_TXN_PER_BUNDLE]. A txn_cnt of 1 inserts a single-transaction bundle which is transaction with extremely high @@ -373,12 +395,14 @@ void fd_pack_insert_txn_cancel( fd_pack_t * pack, fd_txn_e_t * txn returns is unspecified. These functions must not be called if the pack object was initialized - with enable_bundles==0. */ + with bundle_meta_sz==0. */ fd_txn_e_t * const * fd_pack_insert_bundle_init ( fd_pack_t * pack, fd_txn_e_t * * bundle, ulong txn_cnt ); -int fd_pack_insert_bundle_fini ( fd_pack_t * pack, fd_txn_e_t * const * bundle, ulong txn_cnt, ulong expires_at, int initializer_bundle ); +int fd_pack_insert_bundle_fini ( fd_pack_t * pack, fd_txn_e_t * const * bundle, ulong txn_cnt, + ulong expires_at, int initializer_bundle, void const * bundle_meta ); void fd_pack_insert_bundle_cancel( fd_pack_t * pack, fd_txn_e_t * const * bundle, ulong txn_cnt ); + /* =========== More details about initializer bundles =============== Initializer bundles are a special type of bundle with special support from the pack object to facilitate preparing on-chain state for the @@ -406,85 +430,63 @@ void fd_pack_insert_bundle_cancel( fd_pack_t * pack, fd_txn_e_t When attempting to schedule a bundle the pack object checks the state, and employs the following rules: - * [Not Initialized]: If the top bundle is an IB, schedule it without - removing it, store that it is the most recently executed IB, and - transition to [Pending]. Otherwise, do not schedule a bundle. + * [Not Initialized]: If the top bundle is an IB, schedule it, + removing it like normal, then transition to [Pending]. Otherwise, + do not schedule a bundle. * [Pending]: Do not schedule a bundle. * [Failed]: Do not schedule a bundle - * [Ready]: Ignore the top bundle if it is the most recently executed - IB, and attempt to schedule the next bundle. Otherwise attempt to - schedule the top bundle. If scheduling an IB, delete any prior IBs - (at most one in the top bundle), do not remove the scheduled IB, - store that it is the most recently executed IB, and transition to - [Pending]. + * [Ready]: Attempt to schedule the next bundle. If scheduling an IB, + transition to [Pending]. As described in the state machine, ending the block (via fd_pack_end_block) transitions to [Not Initialized], and calls to fd_pack_rebate_cus control the transition out of [Pending]. - The policy of scheduling the top IB without removing it and then - normally ignoring it is a bit curious, but is important. It ensures - that the most recently executed IB is also scheduled at the beginning - of each slot. This ensures that everything is initialized properly - (e.g. all tips go to the right spot) even if a block builds off a - different fork than the previous block. For the purposes of - transitioning from [Pending] to [Ready], AlreadyProcessed is treated - as success, which means that the fee payer will not be charged again - in the common case that the block builds off the previous block. The - rules are written in such a way that if the only IB is deleted for - any reason, bundles can continue to be scheduled until the end of the - slot, but not after. This means the caller should periodically - insert a new IB at least prior to the recent blockhash of the - previous IB expiring, which is only really a concern if remaining - leader for hundreds of slots in a row. - - Often, the contents of the IB may depend on the slot or epoch in - which it is executed. It would be simplest to just expire these - transactions and re-insert them, but when using multiple block - engines, the ordering of initializer bundles with respect to other - bundles is important, and it would be difficult to expose an - interface that would allow the caller to re-insert them in the - appropriate order. Instead, pack exposes - fd_pack_rewrite_initializer_bundles, which can be used to update - them. + This design supports a typical block engine system where some state + may need to be initialized at the start of the slot and some state + may need to change between runs of transactions (e.g. 5 transactions + from block builder A followed by 5 transactions from block builder + B). This can be done by inserting an initializer bundle whenever the + top non-initializer bundle's metadata state (retrievable with + fd_pack_peek_bundle_meta) doesn't match the current on-chain state. + Since the initializer bundle will execute before the bundle that was + previously the top one, by the time the non-initializer bundle + executes, the on-chain state will be correctly configured. In this + scheme, in the rare case that an initializer bundle was inserted but + never executed, it should be deleted at the end of the slot. + + If at the start of the slot, it is determined that the on-chain state + is in good shape, the state machine can transition directly to + [Ready] by calling fd_pack_set_initializer_bundles_ready. + + Initializer bundles are not exempt from expiration, but it should not + be a problem if they are always inserted with the most recent + blockhash and deleted at the end of the slot. Additionally, a bundle marked as an IB is exempted from the bundle account blacklist checks. For this reason, it's important that IB be generated by trusted code with minimal or sanitized attacker-controlled input. */ -/* fd_pack_ib_rewriter_fn_t: Used by rewrite_initializer_bundles below. - This function is invoked for each pending initializer bundle, with - bundle[0], ... bundle[txn_cnt-1] pointing to the transactions in the - initializer bundle. expires_at points to the current expiration - value for all transactions in the bundle, which can be modified by - changing *expires_at. ctx is an opaque pointer. */ - typedef void (* fd_pack_ib_rewriter_fn_t)( fd_txn_e_t * const * bundle, - ulong txn_cnt, - ulong * expires_at, - void * ctx ); - -/* fd_pack_rewrite_initializer_bundles: calls the function provided as - `rewriter` on each pending initializer bundle (see long comment above - for more details) consisting of transactions bundle[0], ... - bundle[txn_cnt-1]. The function can modify the transactions in the - bundle, but they all must remain valid transactions, and the number - of compute units each one consumes must not change. Additionally, - the number of transactions in the bundle must not change. Of course, - new address lookup tables will not be re-expanded. In particular, - it's safe to switch the accounts from one PDA to another, to update - data in instruction payloads, to update the recent blockhash, and to - update the signature(s), which of course must be done if any other - changes are made. - The rewriter function will be called with the provided ctx pointer as - the ctx argument, but it's not otherwise examined, which means - ctx==NULL is okay. The rewriter function must not call any functions - on this pack object. Calling this function does not change the state - for the initializer bundle state machine. If enable_bundles==0 or - there are no pending initializer bundles, this function is a no-op. */ -void fd_pack_rewrite_initializer_bundles( fd_pack_t * pack, - fd_pack_ib_rewriter_fn_t rewriter, - void * ctx ); + +/* fd_pack_peek_bundle_meta returns a constant pointer to the bundle + metadata associated with the bundle currently in line to be scheduled + next, or NULL in any of the following cases: + * There are no bundles + * The bundle currently in line to be scheduled next is an IB + * The bundle state is currently [Pending] or [Failed]. + + The lifetime of the returned pointer is until the next pack insert, + schedule, delete, or expire call. The size of the region pointed to + by the returned pointer is bundle_meta_sz. If this bundle was + inserted with bundle_meta==NULL, then the contents of the region + pointed to by the returned pointer are arbitrary, but it will be safe + to read. + + Pack doesn't do anything special to ensure the returned pointer + points to memory with any particular alignment. It will naturally + have an alignment of at least GCD( 64, bundle_meta_sz ). */ +void const * fd_pack_peek_bundle_meta( fd_pack_t const * pack ); /* fd_pack_set_initializer_bundles_ready sets the IB state machine state (see long initializer bundle comment above) to the [Ready] state. diff --git a/src/disco/plugin/Local.mk b/src/disco/plugin/Local.mk new file mode 100644 index 0000000000..3d499dc7dc --- /dev/null +++ b/src/disco/plugin/Local.mk @@ -0,0 +1,5 @@ +$(call add-hdrs,fd_bundle_crank.h) +$(call add-objs,fd_bundle_crank,fd_disco,fd_flamenco) + +$(call make-unit-test,test_bundle_crank,test_bundle_crank,fd_disco fd_flamenco fd_ballet fd_util) +$(call run-unit-test,test_bundle_crank,) diff --git a/src/disco/plugin/fd_bundle_crank.c b/src/disco/plugin/fd_bundle_crank.c new file mode 100644 index 0000000000..0986e77eee --- /dev/null +++ b/src/disco/plugin/fd_bundle_crank.c @@ -0,0 +1,395 @@ +#include "fd_bundle_crank.h" +#include "../../flamenco/runtime/fd_pubkey_utils.h" + +FD_STATIC_ASSERT( sizeof(fd_bundle_crank_tip_payment_config_t)==89UL, config_struct ); + +static const fd_bundle_crank_3_t fd_bundle_crank_3_base[1] = {{ + + .sig_cnt = 1, + ._sig_cnt = 1, + .ro_signed_cnt = 0, + .ro_unsigned_cnt = 5, + .acct_addr_cnt = 20, + + .system_program = { SYS_PROG_ID }, + .compute_budget_program = { COMPUTE_BUDGET_PROG_ID }, + + .instr_cnt = 4, + .compute_budget_instruction = { + .prog_id = 15, + .acct_cnt = 0, + .data_sz = 5, + .set_cu_limit = 2, + .cus = 100000U + }, + + .init_tip_distribution_acct = { + .prog_id = 19, + .acct_cnt = 5, + .acct_idx = { 9, 13, 17, 0, 18 }, + .data_sz = 43, + .ix_discriminator = { FD_BUNDLE_CRANK_DISC_INIT_TIP_DISTR }, + }, + + .change_tip_receiver = { + .prog_id = 16, + .acct_cnt = 13, + .acct_idx = { 10, 11, 13, 12, 1, 2, 3, 4, 5, 6, 7, 8, 0 }, + .data_sz = 8, + .ix_discriminator = { FD_BUNDLE_CRANK_DISC_CHANGE_TIP_RCV } + }, + + .change_block_builder = { + .prog_id = 16, + .acct_cnt = 13, + .acct_idx = { 10, 13, 12, 14, 1, 2, 3, 4, 5, 6, 7, 8, 0}, + .data_sz = 16, + .ix_discriminator = { FD_BUNDLE_CRANK_DISC_CHANGE_BLK_BLD }, + }, + + /* Account addresses that depend on the network: */ + .tip_payment_accounts = {{ 0 }}, + .tip_distribution_program_config = { 0 }, + .tip_payment_program_config = { 0 }, + .tip_distribution_program = { 0 }, + .tip_payment_program = { 0 }, + + /* Fields that depend on the validator configuration: */ + .authorized_voter = { 0 }, + .validator_vote_account = { 0 }, + .init_tip_distribution_acct.merkle_root_upload_authority = { 0 }, + .init_tip_distribution_acct.commission_bps = 0, + .init_tip_distribution_acct.bump = 0, + + /* Fields that vary each time: */ + .old_tip_receiver = { 0 }, + .old_block_builder = { 0 }, + .new_tip_receiver = { 0 }, + .new_block_builder = { 0 }, + .change_block_builder.block_builder_commission_pct = 0UL +}}; + +static const fd_bundle_crank_2_t fd_bundle_crank_2_base[1] = {{ + + .sig_cnt = 1, + ._sig_cnt = 1, + .ro_signed_cnt = 0, + .ro_unsigned_cnt = 2, + .acct_addr_cnt = 17, + + .compute_budget_program = { COMPUTE_BUDGET_PROG_ID }, + + .instr_cnt = 3, + .compute_budget_instruction = { + .prog_id = 15, + .acct_cnt = 0, + .data_sz = 5, + .set_cu_limit = 2, + .cus = 80000U + }, + + .change_tip_receiver = { + .prog_id = 16, + .acct_cnt = 13, + .acct_idx = { 10, 11, 13, 12, 1, 2, 3, 4, 5, 6, 7, 8, 0 }, + .data_sz = 8, + .ix_discriminator = { FD_BUNDLE_CRANK_DISC_CHANGE_TIP_RCV } + }, + + .change_block_builder = { + .prog_id = 16, + .acct_cnt = 13, + .acct_idx = { 10, 13, 12, 14, 1, 2, 3, 4, 5, 6, 7, 8, 0}, + .data_sz = 16, + .ix_discriminator = { FD_BUNDLE_CRANK_DISC_CHANGE_BLK_BLD }, + }, + + /* Account addresses that depend on the network: */ + .tip_payment_accounts = {{ 0 }}, + .tip_distribution_program_config = { 0 }, + .tip_payment_program_config = { 0 }, + .tip_payment_program = { 0 }, + + /* Fields that depend on the validator configuration: */ + .authorized_voter = { 0 }, + + /* Fields that vary each time: */ + .old_tip_receiver = { 0 }, + .old_block_builder = { 0 }, + .new_tip_receiver = { 0 }, + .new_block_builder = { 0 }, + .change_block_builder.block_builder_commission_pct = 0UL +}}; + +static const fd_acct_addr_t null_addr = { 0 }; + +#define MAP_NAME pidx_map +#define MAP_T fd_bundle_crank_gen_pidx_t +#define MAP_KEY_T fd_acct_addr_t +#define MAP_MEMOIZE 0 +#define MAP_QUERY_OPT 2 /* low hit rate */ +#define MAP_LG_SLOT_CNT 5 /* 18 entries, space for 32 */ +#define MAP_KEY_NULL null_addr +#if FD_HAS_AVX +# define MAP_KEY_INVAL(k) _mm256_testz_si256( wb_ldu( (k).b ), wb_ldu( (k).b ) ) +#else +# define MAP_KEY_INVAL(k) MAP_KEY_EQUAL(k, null_addr) +#endif +#define MAP_KEY_EQUAL(k0,k1) (!memcmp((k0).b,(k1).b, FD_TXN_ACCT_ADDR_SZ)) +#define MAP_KEY_EQUAL_IS_SLOW 1 +#define MAP_MEMOIZE 0 +#define MAP_KEY_HASH(key) ((uint)fd_ulong_hash( fd_ulong_load_8( (key).b ) )) + +#include "../../util/tmpl/fd_map.c" + + +#define EXPAND_ARR8(arr, i) arr[(i)], arr[(i)+1], arr[(i)+2], arr[(i)+3], arr[(i)+4], arr[(i)+5], arr[(i)+6], arr[(i)+7], +#define EXPAND_ARR32(arr, i) EXPAND_ARR8(arr, (i)) EXPAND_ARR8(arr, (i)+8) EXPAND_ARR8(arr, (i)+16) EXPAND_ARR8(arr, (i)+24) + + + +fd_bundle_crank_gen_t * +fd_bundle_crank_gen_init( void * mem, + fd_acct_addr_t const * tip_distribution_program_addr, + fd_acct_addr_t const * tip_payment_program_addr, + fd_acct_addr_t const * validator_vote_acct_addr, + fd_acct_addr_t const * merkle_root_authority_addr, + ulong commission_bps ) { + fd_bundle_crank_gen_t * g = (fd_bundle_crank_gen_t *)mem; + memcpy( g->crank3, fd_bundle_crank_3_base, sizeof(fd_bundle_crank_3_base) ); + memcpy( g->crank2, fd_bundle_crank_2_base, sizeof(fd_bundle_crank_2_base) ); + + g->crank3->init_tip_distribution_acct.commission_bps = (ushort)commission_bps; + memcpy( g->crank3->tip_distribution_program, tip_distribution_program_addr, 32UL ); + memcpy( g->crank3->tip_payment_program, tip_payment_program_addr, 32UL ); + memcpy( g->crank3->validator_vote_account, validator_vote_acct_addr, 32UL ); + memcpy( g->crank3->init_tip_distribution_acct.merkle_root_upload_authority, merkle_root_authority_addr, 32UL ); + + uint cerr[1]; + do { + char seed[13] = "TIP_ACCOUNT_0"; /* Not NUL terminated */ + uchar * seed_ptr[1] = { (uchar *)seed }; + ulong seed_len = 13; + for( ulong i=0UL; i<8UL; i++ ) { + seed[12] = (char)((ulong)'0' + i); + uchar out_bump[1]; + FD_TEST( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( (fd_pubkey_t const *)tip_payment_program_addr, + 1UL, seed_ptr, &seed_len, + (fd_pubkey_t *)g->crank3->tip_payment_accounts[i], out_bump, cerr ) ); + } + } while( 0 ); + + do { + char seed[14] = "CONFIG_ACCOUNT"; /* Not NUL terminated */ + ulong seed_len = 14; + uchar out_bump[1]; + uchar * seed_ptr[1] = { (uchar *)seed }; + FD_TEST( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( (fd_pubkey_t const *)tip_payment_program_addr, + 1UL, seed_ptr, &seed_len, + (fd_pubkey_t *)g->crank3->tip_payment_program_config, out_bump, cerr ) ); + /* Same seed used for tip distribution config account too */ + FD_TEST( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( (fd_pubkey_t const *)tip_distribution_program_addr, + 1UL, seed_ptr, &seed_len, + (fd_pubkey_t *)g->crank3->tip_distribution_program_config, out_bump, cerr ) ); + } while( 0 ); + + /* Populate crank2 from crank3 */ + memcpy( g->crank2->tip_payment_accounts, g->crank3->tip_payment_accounts, 8UL*32UL ); + memcpy( g->crank2->tip_distribution_program_config, g->crank3->tip_distribution_program_config, 32UL ); + memcpy( g->crank2->tip_payment_program_config, g->crank3->tip_payment_program_config, 32UL ); + memcpy( g->crank2->tip_payment_program, g->crank3->tip_payment_program, 32UL ); + + FD_TEST( sizeof(g->txn3)==fd_txn_parse( (uchar const *)g->crank3, sizeof(g->crank3), g->txn3, NULL ) ); + FD_TEST( sizeof(g->txn2)==fd_txn_parse( (uchar const *)g->crank2, sizeof(g->crank2), g->txn2, NULL ) ); + + pidx_map_new( g->map ); + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[0], 0 ) }} )->idx= 1UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[1], 0 ) }} )->idx= 2UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[2], 0 ) }} )->idx= 3UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[3], 0 ) }} )->idx= 4UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[4], 0 ) }} )->idx= 5UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[5], 0 ) }} )->idx= 6UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[6], 0 ) }} )->idx= 7UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_accounts[7], 0 ) }} )->idx= 8UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_distribution_program_config, 0 ) }} )->idx= 9UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_program_config, 0 ) }} )->idx=10UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->compute_budget_program, 0 ) }} )->idx=15UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_payment_program, 0 ) }} )->idx=16UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->validator_vote_account, 0 ) }} )->idx=17UL; + pidx_map_insert( g->map, (fd_acct_addr_t){{ EXPAND_ARR32( g->crank3->tip_distribution_program, 0 ) }} )->idx=19UL; + + g->configured_epoch = ULONG_MAX; + return g; +} + + +static inline void +fd_bundle_crank_update_epoch( fd_bundle_crank_gen_t * g, + ulong epoch ) { + struct __attribute__((packed)) { + char tip_distr_acct[24]; + uchar vote_pubkey [32]; + ulong epoch; + } seeds[1] = {{ + .tip_distr_acct = "TIP_DISTRIBUTION_ACCOUNT", + .vote_pubkey = { EXPAND_ARR32( g->crank3->validator_vote_account, 0 ) }, + .epoch = epoch + }}; + FD_STATIC_ASSERT( sizeof(seeds)==24UL+32UL+8UL, seed_struct ); + ulong seed_len = sizeof(seeds); + uint custom_err[1]; + + uchar * _seeds[1] = { (uchar *)seeds }; + + FD_TEST( FD_PUBKEY_SUCCESS==fd_pubkey_find_program_address( (fd_pubkey_t const *)g->crank3->tip_distribution_program, + 1UL, _seeds, &seed_len, + (fd_pubkey_t *)g->crank3->new_tip_receiver, + &(g->crank3->init_tip_distribution_acct.bump), custom_err ) ); + memcpy( g->crank2->new_tip_receiver, g->crank3->new_tip_receiver, 32UL ); + g->configured_epoch = epoch; + +} + +void +fd_bundle_crank_get_addresses( fd_bundle_crank_gen_t * gen, + ulong epoch, + fd_acct_addr_t * out_tip_payment_config, + fd_acct_addr_t * out_tip_receiver ) { + if( FD_UNLIKELY( epoch!=gen->configured_epoch ) ) fd_bundle_crank_update_epoch( gen, epoch ); + memcpy( out_tip_payment_config, gen->crank3->tip_payment_program_config, 32UL ); + memcpy( out_tip_receiver, gen->crank3->new_tip_receiver, 32UL ); +} + +ulong +fd_bundle_crank_generate( fd_bundle_crank_gen_t * gen, + fd_bundle_crank_tip_payment_config_t const * old_tip_payment_config, + fd_acct_addr_t const * new_block_builder, + fd_acct_addr_t const * identity, + fd_acct_addr_t const * tip_receiver_owner, + ulong epoch, + ulong block_builder_commission, + uchar * out_payload, + fd_txn_t * out_txn ) { + + if( FD_UNLIKELY( epoch!=gen->configured_epoch ) ) fd_bundle_crank_update_epoch( gen, epoch ); + + if( FD_UNLIKELY( old_tip_payment_config->discriminator != 0x82ccfa1ee0aa0c9bUL ) ) { + FD_LOG_WARNING(( "Found unexpected tip payment config account discriminator %lx. Refusing to crank bundles.", + old_tip_payment_config->discriminator )); + return ULONG_MAX; + } + + int swap3 = !fd_memeq( tip_receiver_owner, gen->crank3->tip_distribution_program, sizeof(fd_acct_addr_t) ); + + if( FD_LIKELY( fd_memeq( old_tip_payment_config->tip_receiver, gen->crank3->new_tip_receiver, 32UL ) && + fd_memeq( old_tip_payment_config->block_builder, new_block_builder, 32UL ) && + !swap3 && + old_tip_payment_config->commission_pct==block_builder_commission ) ) { + /* Everything configured properly! */ + return 0UL; + } + + + if( FD_UNLIKELY( swap3 ) ) { + memcpy( gen->crank3->authorized_voter, identity, 32UL ); + memcpy( gen->crank3->new_block_builder, new_block_builder, 32UL ); + memcpy( gen->crank3->old_tip_receiver, old_tip_payment_config->tip_receiver, 32UL ); + memcpy( gen->crank3->old_block_builder, old_tip_payment_config->block_builder, 32UL ); + gen->crank3->change_block_builder.block_builder_commission_pct = block_builder_commission; + } else { + memcpy( gen->crank2->authorized_voter, identity, 32UL ); + memcpy( gen->crank2->new_block_builder, new_block_builder, 32UL ); + memcpy( gen->crank2->old_tip_receiver, old_tip_payment_config->tip_receiver, 32UL ); + memcpy( gen->crank2->old_block_builder, old_tip_payment_config->block_builder, 32UL ); + gen->crank2->change_block_builder.block_builder_commission_pct = block_builder_commission; + } + + /* If it weren't for the fact that the old tip payment config is + essentially attacker-controlled, we'd be golden. However, someone + trying to grief us can e.g. set the old block builder to the tip + payment program, and if we're not careful, we'll create a + transaction with a duplicate account. We trust identity, new + tip_receiver, and new_block_builder well enough though. Note that + it's not possible for either attacker-controlled address to be the + system program, because the account must be writable, and write + locks to the system program get demoted. */ + fd_bundle_crank_gen_pidx_t * identity_pidx = pidx_map_insert( gen->map, *(fd_acct_addr_t *)identity ); + if( FD_UNLIKELY( !identity_pidx ) ) { + FD_LOG_WARNING(( "Indentity was already in map. Refusing to crank bundles." )); + return ULONG_MAX; + } + identity_pidx->idx = 0UL; + + fd_bundle_crank_gen_pidx_t * new_tr_pidx = pidx_map_insert( gen->map, *(fd_acct_addr_t *)gen->crank3->new_tip_receiver ); + if( FD_UNLIKELY( !new_tr_pidx ) ) { + pidx_map_remove( gen->map, identity_pidx ); + FD_LOG_WARNING(( "New block builder was already in map. Refusing to crank bundles." )); + return ULONG_MAX; + } + new_tr_pidx->idx = 13UL; + + fd_bundle_crank_gen_pidx_t * new_bb_pidx = pidx_map_insert( gen->map, *(fd_acct_addr_t *)new_block_builder ); + if( FD_UNLIKELY( !new_bb_pidx ) ) { + pidx_map_remove( gen->map, new_tr_pidx ); + pidx_map_remove( gen->map, identity_pidx ); + FD_LOG_WARNING(( "New block builder was already in map. Refusing to crank bundles." )); + return ULONG_MAX; + } + new_bb_pidx->idx = 14UL; + + int inserted1 = 0; + int inserted2 = 0; + fd_bundle_crank_gen_pidx_t dummy1[1] = {{ .idx = 11UL }}; + fd_bundle_crank_gen_pidx_t dummy2[1] = {{ .idx = 12UL }}; + + fd_bundle_crank_gen_pidx_t * old_tr_pidx = pidx_map_query( gen->map, *old_tip_payment_config->tip_receiver, NULL ); + if( FD_LIKELY( NULL==old_tr_pidx ) ) { + old_tr_pidx = pidx_map_insert( gen->map, *old_tip_payment_config->tip_receiver ); + old_tr_pidx->idx = 11UL; + inserted1 = 1; + } else if( FD_UNLIKELY( !swap3 && old_tr_pidx->idx>16UL ) ) { + old_tr_pidx = dummy1; + } else { + /* perturb the old tip receiver pubkey so that it's not a duplicate, + then don't use it. */ + gen->crank3->old_tip_receiver[0]++; + gen->crank2->old_tip_receiver[0]++; + } + + fd_bundle_crank_gen_pidx_t * old_bb_pidx = pidx_map_query( gen->map, *old_tip_payment_config->block_builder, NULL ); + if( FD_UNLIKELY( NULL==old_bb_pidx ) ) { + old_bb_pidx = pidx_map_insert( gen->map, *old_tip_payment_config->block_builder ); + old_bb_pidx->idx = 12UL; + inserted2 = 1; + } else if( FD_UNLIKELY( !swap3 && old_bb_pidx->idx>16UL ) ) { + old_bb_pidx = dummy2; + } else { + /* perturb, but do it differently so it can't conflict with the + old tip receiver if they're both the same. */ + gen->crank3->old_block_builder[0]--; + gen->crank2->old_block_builder[0]--; + } + + gen->crank3->change_tip_receiver.acct_idx [1] = (uchar)(old_tr_pidx->idx); + gen->crank2->change_tip_receiver.acct_idx [1] = (uchar)(old_tr_pidx->idx); + gen->crank3->change_tip_receiver.acct_idx [3] = (uchar)(old_bb_pidx->idx); + gen->crank3->change_block_builder.acct_idx[2] = (uchar)(old_bb_pidx->idx); + gen->crank2->change_tip_receiver.acct_idx [3] = (uchar)(old_bb_pidx->idx); + gen->crank2->change_block_builder.acct_idx[2] = (uchar)(old_bb_pidx->idx); + + if( FD_UNLIKELY( inserted2 ) ) pidx_map_remove( gen->map, old_bb_pidx ); + if( FD_LIKELY ( inserted1 ) ) pidx_map_remove( gen->map, old_tr_pidx ); + pidx_map_remove( gen->map, new_bb_pidx ); + pidx_map_remove( gen->map, new_tr_pidx ); + pidx_map_remove( gen->map, identity_pidx ); + + if( FD_UNLIKELY( swap3 ) ) { + fd_memcpy( out_payload, gen->crank3, sizeof(gen->crank3) ); + fd_memcpy( out_txn, gen->txn3, sizeof(gen->txn3) ); + return sizeof(gen->crank3); + } else { + fd_memcpy( out_payload, gen->crank2, sizeof(gen->crank2) ); + fd_memcpy( out_txn, gen->txn2, sizeof(gen->txn2) ); + return sizeof(gen->crank2); + } +} diff --git a/src/disco/plugin/fd_bundle_crank.h b/src/disco/plugin/fd_bundle_crank.h new file mode 100644 index 0000000000..bccfcecd78 --- /dev/null +++ b/src/disco/plugin/fd_bundle_crank.h @@ -0,0 +1,303 @@ +#ifndef HEADER_fd_src_disco_plugin_fd_bundle_crank_h +#define HEADER_fd_src_disco_plugin_fd_bundle_crank_h + +#include "../fd_disco_base.h" +#include "../../ballet/txn/fd_txn.h" +#include "../../flamenco/runtime/fd_system_ids_pp.h" +#include "fd_bundle_crank_constants.h" + + +/* This header defines the crank transactions and some helper functions + for working with them. There are two types of crank transactions: + one that creates the tip distribution account in addition to updating + it and one that only updates it. The tip distribution account only + needs to be created once per epoch, but it needs to be updated pretty + much each time the leader changes. */ + + +/* Forward declare this struct. It's defined at the bottom of this + header, and it's fixed size, so you can declare one easily, but + putting it up here clutters the file. */ +struct fd_bundle_crank_gen_private; +typedef struct fd_bundle_crank_gen_private fd_bundle_crank_gen_t; + + +/* fd_bundle_crank_gen_init initializes a bundle crank generator so that + it can produce bundle crank transactions. This does some + precomputation that is somewhat expensive (a few SHA256s), so it's + better not to have to do it each slot. mem points to a region of + memory with suitable alignment and footprint for a + fd_bundle_crank_gen_t. fd_bundle_crank_gen_t x[1] is the easiest way + to get that. + + tip_distribution_program_addr and tip_payment_program_addr are + non-NULL pointers to the respective account address of the + appropriately configured tip distribution program and tip payment + program. This code does not support the one-time initialization that + must be done on those programs. + + validator_vote_acct_addr is a non-NULL pointer to the pubkey for this + validator's vote account. + + merkle_root_authority_addr is a non-NULL pointer to a pubkey that + will be delegated authority to distribute tips according to the + Merkle root. commission_bps is the validator's tip commission, + stored in basis points. Must be in [0, 10,000]. The value of + merkle_root_authority and commission_bps are only used in + fd_bundle_crank_generate, which means that if that function will not + be called, it's okay to pass bogus data here; even in this case, + however, merkle_root_authority_addr must point to a valid 32-byte + region. + + Returns mem, which is properly initialized for use in + fd_bundle_crank_generate. */ +fd_bundle_crank_gen_t * +fd_bundle_crank_gen_init( void * mem, + fd_acct_addr_t const * tip_distribution_program_addr, + fd_acct_addr_t const * tip_payment_program_addr, + fd_acct_addr_t const * validator_vote_acct_addr, + fd_acct_addr_t const * merkle_root_authority_addr, + ulong commission_bps ); + + +/* fd_bundle_crank_get_addresses returns the account addresses that need + to be queried at the start of each slot. gen must be a valid + initialized bundle crank generator. epoch is the epoch number of the + slot in which the initialization transactions will be submitted. + out_tip_payment_config and out_tip_receiver point to regions of + memory that will be populated with the tip payment config account + address and this validator's tip receiver account address for the + provided epoch. */ +void +fd_bundle_crank_get_addresses( fd_bundle_crank_gen_t * gen, + ulong epoch, + fd_acct_addr_t * out_tip_payment_config, + fd_acct_addr_t * out_tip_receiver ); + + +/* fd_bundle_crank_tip_payment_config is the layout of the tip payment + configuration account on chain. */ +struct __attribute__((packed)) fd_bundle_crank_tip_payment_config { + ulong discriminator; /* == 0x82ccfa1ee0aa0c9b */ + fd_acct_addr_t tip_receiver[1]; + fd_acct_addr_t block_builder[1]; + ulong commission_pct; + uchar bumps[9]; +}; +typedef struct fd_bundle_crank_tip_payment_config fd_bundle_crank_tip_payment_config_t; + + +/* fd_bundle_crank_generate produces the necessary bundle crank + transactions. gen must be a valid initialized bundle crank generator. + old_tip_payment_config must point to a copy of the contents of the + tip payment configuration account (use fd_bundle_crank_get_addresses + to get the account address of the tip payment configuration account). + Since the previous leader can modify the contents of this account + almost arbitrarily, this is treated as mostly untrusted input. + new_block_builder points to the desired block builder, which + typically comes from the bundle metadata. identity points to the + current validator's identity pubkey, which must also be an authorized + voter for the vote account used in _init(); this account will be used + as the signer for this transaction. tip_receiver_owner points to the + pubkey of the on-chain account owner of the tip_receiver account + (also normally retrieved with get_addresses). By convention, the + owner for accounts that don't exist is the system program (111..1). + The only other acceptable answer is the tip distribution program + (since it's a PDA of the tip distribution acct, no other program can + create it). epoch is the epoch number of the slot in which the + transaction will be submitted. block_builder_commission is the block + builder's commission (in percentage points, not basis points like the + validator's tip commission!). This commission also typically comes + from the bundle's metadata. out_payload must point to the first byte + of a memory region of at least sizeof(fd_bundle_crank_3_t) bytes, but + probably should be FD_TXN_MTU bytes. out_txn must point to the first + byte of a memory region of at least FD_TXN_MAX_SZ bytes. + + This function determines what cranks are necessary. If any, it + writes the transaction payload to the region pointed to by + out_payload and the corresponding fd_txn_t to the region pointed to + by out_txn. If no cranks are neccessary, the contents of these + regions are unmodified. + + If cranks are necessary, returns the size of the transaction payload + written to out_payload. Otherwise, returns 0. The size of the + fd_txn_t written to out_txn can be determined using other means. If + some expected invariant is violated, returns ULONG_MAX and logs a + warning. This should be impossible in a production network, and it + will prevent the validator from using bundles. + + The transactions written to out_payload are fully populated except + for the blockhash and signature fields. Space is reserved for them + in the proper place, but the fields are filled with 0s. These two + fields must be populated before the transaction can be submitted. */ +ulong +fd_bundle_crank_generate( fd_bundle_crank_gen_t * gen, + fd_bundle_crank_tip_payment_config_t const * old_tip_payment_config, + fd_acct_addr_t const * new_block_builder, + fd_acct_addr_t const * identity, + fd_acct_addr_t const * tip_receiver_owner, + ulong epoch, + ulong block_builder_commission, + uchar * out_payload, + fd_txn_t * out_txn ); + + +/* These are fixed sized transactions, so sizeof() gives you the proper + size of the payload. */ +typedef struct fd_bundle_crank_3 fd_bundle_crank_3_t; /* init and update */ +typedef struct fd_bundle_crank_2 fd_bundle_crank_2_t; /* update only */ + + + +struct __attribute__((packed)) fd_bundle_crank_3 { + uchar sig_cnt; /* = 1 */ + uchar signature[64]; + uchar _sig_cnt; /* = 1 */ + uchar ro_signed_cnt; /* = 0 */ + uchar ro_unsigned_cnt; /* = 5 */ + uchar acct_addr_cnt; /* = 20 */ + + /* Writable signers */ + /* 0 */ uchar authorized_voter[32]; + /* Readonly signers (none) */ + /* Writable non-signers */ + /* 1-8 */ uchar tip_payment_accounts[8][32]; + /* 9 */ uchar tip_distribution_program_config[32]; + /* 10 */ uchar tip_payment_program_config[32]; + /* 11 */ uchar old_tip_receiver[32]; + /* 12 */ uchar old_block_builder[32]; + /* 13 */ uchar new_tip_receiver[32]; + /* 14 */ uchar new_block_builder[32]; + + /* Readonly non-signers */ + /* 15 */ uchar compute_budget_program[32]; + /* 16 */ uchar tip_payment_program[32]; + /* 17 */ uchar validator_vote_account[32]; + /* 18 */ uchar system_program[32]; + /* 19 */ uchar tip_distribution_program[32]; + + uchar recent_blockhash[32]; + uchar instr_cnt; /* = 4 */ + + /* Compute budget instruction */ + struct __attribute__((packed)) { + uchar prog_id; /* = 15 */ + uchar acct_cnt; /* = 0 */ + uchar data_sz; /* = 5 */ + uchar set_cu_limit; /* = 2 */ + uint cus; /* = 150+39195 + 38260 + 13696 + 150*(255-bump) approx 100k */ + } compute_budget_instruction; + + /* Initialize Tip Distribution Account */ + struct __attribute__((packed)) { + uchar prog_id; /* = 19 */ + uchar acct_cnt; /* = 5 */ + uchar acct_idx[5]; /* = { 9, 13, 17, 0, 18} */ + uchar data_sz; /* = 43 */ + uchar ix_discriminator[8]; /* = {78 bf 19 b6 6f 31 b3 37} */ + uchar merkle_root_upload_authority[32]; + ushort commission_bps; + uchar bump; + } init_tip_distribution_acct; + + /* Change Tip Receiver */ + struct __attribute__((packed)) { + uchar prog_id; /* = 16 */ + uchar acct_cnt; /* = 13 */ + uchar acct_idx[13]; /* = { 10, 11, 13, 12, 1, 2, 3, 4, 5, 6, 7, 8, 0} */ + uchar data_sz; /* = 8 */ + uchar ix_discriminator[8]; /* = {456316470be7568f} */ + } change_tip_receiver; + + /* Change Block Builder */ + struct __attribute__((packed)) { + uchar prog_id; /* = 16 */ + uchar acct_cnt; /* = 13 */ + uchar acct_idx[13]; /* = { 10, 13, 12, 14, 1, 2, 3, 4, 5, 6, 7, 8, 0} */ + uchar data_sz; /* = 16 */ + uchar ix_discriminator[8]; /* = {86 50 26 89 a5 15 72 7b} */ + ulong block_builder_commission_pct; + } change_block_builder; +}; + +struct __attribute__((packed)) fd_bundle_crank_2 { + uchar sig_cnt; /* = 1 */ + uchar signature[64]; + uchar _sig_cnt; /* = 1 */ + uchar ro_signed_cnt; /* = 0 */ + uchar ro_unsigned_cnt; /* = 2 */ + uchar acct_addr_cnt; /* = 17 */ + + /* Writable signers */ + /* 0 */ uchar authorized_voter[32]; + /* Readonly signers (none) */ + /* Writable non-signers */ + /* 1-8 */ uchar tip_payment_accounts[8][32]; + /* 9 */ uchar tip_distribution_program_config[32]; + /* 10 */ uchar tip_payment_program_config[32]; + /* 11 */ uchar old_tip_receiver[32]; + /* 12 */ uchar old_block_builder[32]; + /* 13 */ uchar new_tip_receiver[32]; + /* 14 */ uchar new_block_builder[32]; + + /* Readonly non-signers */ + /* 15 */ uchar compute_budget_program[32]; + /* 16 */ uchar tip_payment_program[32]; + + uchar recent_blockhash[32]; + uchar instr_cnt; /* = 3 */ + + /* Compute budget instruction */ + struct __attribute__((packed)) { + uchar prog_id; /* = 15 */ + uchar acct_cnt; /* = 0 */ + uchar data_sz; /* = 5 */ + uchar set_cu_limit; /* = 2 */ + uint cus; /* = 150+39195 + 38260 = 77,605 */ + } compute_budget_instruction; + + /* Change Tip Receiver */ + struct __attribute__((packed)) { + uchar prog_id; /* = 16 */ + uchar acct_cnt; /* = 13 */ + uchar acct_idx[13]; /* = { 10, 11, 13, 12, 1, 2, 3, 4, 5, 6, 7, 8, 0} */ + uchar data_sz; /* = 8 */ + uchar ix_discriminator[8]; /* = {456316470be7568f} */ + } change_tip_receiver; + + /* Change Block Builder */ + struct __attribute__((packed)) { + uchar prog_id; /* = 16 */ + uchar acct_cnt; /* = 13 */ + uchar acct_idx[13]; /* = { 10, 13, 12, 14, 1, 2, 3, 4, 5, 6, 7, 8, 0} */ + uchar data_sz; /* = 16 */ + uchar ix_discriminator[8]; /* = {86 50 26 89 a5 15 72 7b} */ + ulong block_builder_commission_pct; + } change_block_builder; + +}; + + +/* This is only here so that the bundle_crank_gen struct can be declared + here statically sized. */ +typedef struct { + fd_acct_addr_t key; + ulong idx; +} fd_bundle_crank_gen_pidx_t; + + + + +struct fd_bundle_crank_gen_private { + fd_bundle_crank_3_t crank3[1]; + fd_bundle_crank_2_t crank2[1]; + + uchar txn3[ sizeof(fd_txn_t)+4UL*sizeof(fd_txn_instr_t) ] __attribute__(( aligned( alignof(fd_txn_t) ) )); + uchar txn2[ sizeof(fd_txn_t)+3UL*sizeof(fd_txn_instr_t) ] __attribute__(( aligned( alignof(fd_txn_t) ) )); + ulong configured_epoch; + + fd_bundle_crank_gen_pidx_t map[32]; +}; +typedef struct fd_bundle_crank_gen_private fd_bundle_crank_gen_t; + +#endif /* HEADER_fd_src_disco_plugin_fd_bundle_crank_h */ diff --git a/src/disco/plugin/fd_bundle_crank_constants.h b/src/disco/plugin/fd_bundle_crank_constants.h new file mode 100644 index 0000000000..78958ac3a3 --- /dev/null +++ b/src/disco/plugin/fd_bundle_crank_constants.h @@ -0,0 +1,17 @@ +/* This just contains the #defines that are useful for the keyguard + tile. This is in line with our policy of ruthelessly minimizing the + attack surface of the keyguard tile. No need for an include guard + because it's just #defines. */ +#define FD_BUNDLE_CRANK_2_SZ 710UL +#define FD_BUNDLE_CRANK_3_SZ 857UL + +#define FD_BUNDLE_CRANK_2_IX1_DISC_OFF 670UL +#define FD_BUNDLE_CRANK_2_IX2_DISC_OFF 694UL + +#define FD_BUNDLE_CRANK_3_IX1_DISC_OFF 758UL +#define FD_BUNDLE_CRANK_3_IX2_DISC_OFF 817UL +#define FD_BUNDLE_CRANK_3_IX3_DISC_OFF 841UL + +#define FD_BUNDLE_CRANK_DISC_INIT_TIP_DISTR 0x78, 0xbf, 0x19, 0xb6, 0x6f, 0x31, 0xb3, 0x37 +#define FD_BUNDLE_CRANK_DISC_CHANGE_TIP_RCV 0x45, 0x63, 0x16, 0x47, 0x0b, 0xe7, 0x56, 0x8f +#define FD_BUNDLE_CRANK_DISC_CHANGE_BLK_BLD 0x86, 0x50, 0x26, 0x89, 0xa5, 0x15, 0x72, 0x7b diff --git a/src/disco/plugin/fixtures/2niAy2BYpfqTuBM7pZtTkHFnWM2x7bFcFL1oyR1ixqGRcG5uydjZiG5AR8PRYHAaQ3JqA8JYyCRoc3VCwohQVwYP.bin b/src/disco/plugin/fixtures/2niAy2BYpfqTuBM7pZtTkHFnWM2x7bFcFL1oyR1ixqGRcG5uydjZiG5AR8PRYHAaQ3JqA8JYyCRoc3VCwohQVwYP.bin new file mode 100644 index 0000000000000000000000000000000000000000..64b23d14fd1491a08d15113e61df4e8d2d63187e GIT binary patch literal 606 zcmV-k0-^l@S#Nn?Hhdrc3;Aw`NVj_vJQiDFzIM<0BOt35Lq>dN;U%bL-vOn3GWbh_ zjOuz~-!QkGbF}{kR|)2J0iKWq0RRCG?8g$tD$mr5ia!C?Yq)cfr4sld@1=X;9}}Zu zByNA<3Ff7+(6q->6f?j^y@Cw!P?ixM(=icv2j(dCt3qiM3Xu)$EkgNrr;``MWChtz_hp%t2Gyi1GiU zqTl8hy~N{JGEt)<FWuUwz@% zKf201W7Sk~u}%%;UZ1*#Ic~qzNYD7mAQ9@vw_B>4Eu3nG^`UV=yen4*Uh&;#R4~3^ zYo=Lj0+lcut?!Fk{6PUhQ1lXXzn2?X5Q|%dpii)q@!}g!Kh(s?^oQLExmQwlpAFc< z;KnB+8=;h|_zNzx7%QF4lMPwL>vdW9Kz5TgvTeo|>2>Q(vT^u)MEJ`iK78fGbT^TtJNm}AgR$p zGe<)E7($nQ*%CTx|LsBJPIUJKhdaTpqe&~`%QtTKKj#0(0u2oe3JC!N3kC!S1qcEF s2t{KSM+@gxj|~kB2>}5E3kC!S1qcEF5Qb1DiKP{Cdj$Xh0000009Ob%7 literal 0 HcmV?d00001 diff --git a/src/disco/plugin/test_bundle_crank.c b/src/disco/plugin/test_bundle_crank.c new file mode 100644 index 0000000000..11de15687e --- /dev/null +++ b/src/disco/plugin/test_bundle_crank.c @@ -0,0 +1,242 @@ +#include "fd_bundle_crank.h" +#include "../pack/fd_chkdup.h" +#include "../../ballet/base64/fd_base64.h" + +#include + +/* crank2: */ +FD_IMPORT_BINARY( payload_2ni, + "src/disco/plugin/fixtures/2niAy2BYpfqTuBM7pZtTkHFnWM2x7bFcFL1oyR1ixqGRcG5uydjZiG5AR8PRYHAaQ3JqA8JYyCRoc3VCwohQVwYP.bin" ); + +FD_STATIC_ASSERT( sizeof(fd_bundle_crank_2_t)==FD_BUNDLE_CRANK_2_SZ, crank ); +FD_STATIC_ASSERT( sizeof(fd_bundle_crank_3_t)==FD_BUNDLE_CRANK_3_SZ, crank ); + +FD_STATIC_ASSERT( offsetof(fd_bundle_crank_2_t, change_tip_receiver.ix_discriminator )==FD_BUNDLE_CRANK_2_IX1_DISC_OFF, crank ); +FD_STATIC_ASSERT( offsetof(fd_bundle_crank_2_t, change_block_builder.ix_discriminator )==FD_BUNDLE_CRANK_2_IX2_DISC_OFF, crank ); +FD_STATIC_ASSERT( offsetof(fd_bundle_crank_3_t, init_tip_distribution_acct.ix_discriminator)==FD_BUNDLE_CRANK_3_IX1_DISC_OFF, crank ); +FD_STATIC_ASSERT( offsetof(fd_bundle_crank_3_t, change_tip_receiver.ix_discriminator )==FD_BUNDLE_CRANK_3_IX2_DISC_OFF, crank ); +FD_STATIC_ASSERT( offsetof(fd_bundle_crank_3_t, change_block_builder.ix_discriminator )==FD_BUNDLE_CRANK_3_IX3_DISC_OFF, crank ); + +fd_acct_addr_t _3iPuTgpWaaC6jYEY7kd993QBthGsQTK3yPCrNJyPMhCD[1] = {{ .b={ + 0x28,0x52,0x15,0xd0,0x34,0x59,0xfa,0xc3,0xaf,0xa0,0xa5,0x52,0xcf,0x8c,0xbb,0x79, + 0xe4,0xaa,0xb1,0x8c,0x04,0x4e,0x6d,0x0c,0x72,0x1f,0x03,0xda,0x2d,0xf9,0x03,0x6a } }}; +fd_acct_addr_t _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7[1] = {{ .b={ + 0x32,0xbc,0x07,0xc7,0xfd,0xe5,0x3f,0x2c,0x9f,0x45,0x8a,0xe8,0x51,0xf2,0x58,0x2a, + 0x9e,0xc4,0xfb,0x00,0x0a,0x87,0xd6,0x67,0xc4,0x77,0x0f,0x16,0xd1,0xd1,0xfc,0x9c } }}; +fd_acct_addr_t _96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5[1] = {{ .b={ + 0x78,0x52,0x1c,0xb1,0x79,0xce,0xbb,0x85,0x89,0xb5,0x56,0xa2,0xd5,0xec,0x94,0xd2, + 0x49,0x86,0x82,0xfd,0xf9,0xbb,0x2a,0xf5,0xad,0x64,0xe4,0x91,0xcc,0x41,0x53,0xda } }}; +fd_acct_addr_t _DNVZMSqeRH18Xa4MCTrb1MndNf3Npg4MEwqswo23eWkf[1] = {{ .b={ + 0xb7,0xcd,0xbb,0x03,0x1e,0x27,0xa7,0xcc,0xb0,0x1b,0xc0,0x7e,0x1c,0x5a,0x30,0x9b, + 0x18,0x61,0x2a,0x16,0xe8,0x10,0x00,0xb8,0x91,0xee,0x8d,0x46,0x0d,0x33,0x67,0x00 } }}; +fd_acct_addr_t _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY[1] = {{ .b={ + 0x09,0xe6,0xa5,0xb0,0xd0,0xb4,0xc7,0x53,0x14,0x33,0x60,0x33,0x1f,0x86,0x1a,0x39, + 0x2c,0xe7,0x46,0x08,0x74,0xbe,0x2f,0x8d,0x75,0x97,0xce,0x6b,0xa6,0xe3,0x8a,0xa9 } }}; +fd_acct_addr_t _G8RaABmvrvNCcGu41NV5oKjCfHeBv1zNn58dFcZzyRRw[1] = {{ .b={ + 0xe0,0xc6,0x27,0x22,0x1b,0xa1,0x94,0xab,0xf8,0x0b,0x2e,0xb3,0x18,0x2b,0x9d,0xcd, + 0x93,0x0d,0x59,0xc5,0xeb,0x75,0x59,0xf8,0x40,0x76,0x93,0x35,0xb2,0x6d,0xc6,0x16 } }}; +fd_acct_addr_t _GiLHMES95axFbFX7ogCTwL6QQ1uqspajz9SHMpt5dCGh[1] = {{ .b={ + 0xe9,0x75,0xeb,0x4d,0xb2,0x71,0x5c,0x24,0x46,0x3c,0xf2,0xf7,0x44,0x84,0xb4,0xd9, + 0x4e,0x3a,0x9d,0x5d,0x6c,0xb9,0xf4,0xe1,0x12,0xef,0xea,0xb6,0x52,0xc0,0x7e,0x9a } }}; +fd_acct_addr_t _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji[1] = {{ .b={ + 0xec,0xc7,0x12,0xc5,0x2a,0xcf,0xd4,0x8b,0x8a,0x3f,0x01,0xd6,0x6b,0xb8,0x73,0x91, + 0xa5,0x12,0xf8,0x21,0xef,0xa5,0x7b,0xe1,0x1f,0x13,0xa3,0x62,0x24,0x6e,0x7f,0xe1 } }}; +fd_acct_addr_t _GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib[1] = {{ .b={ + 0xe7,0x3a,0x76,0xdc,0xa8,0x18,0x4f,0xf8,0x7c,0x60,0xf5,0x2c,0xe8,0xc2,0x48,0x1d, + 0xd0,0x64,0x22,0x92,0xe6,0x09,0x9d,0xce,0x9d,0x79,0x76,0xf1,0xa4,0x4b,0x29,0x44 } }}; +fd_acct_addr_t _HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe[1] = {{ .b={ + 0xf1,0x87,0xec,0x87,0xd1,0xf7,0x45,0xcb,0x3a,0x03,0x38,0x4a,0x26,0xa6,0x9e,0xda, + 0x0c,0xa2,0xd1,0xaa,0x0f,0x41,0xe4,0x24,0x16,0x37,0x7e,0x91,0xff,0x5b,0x5d,0x31 } }}; +fd_acct_addr_t _HgzT81VF1xZ3FT9Eq1pHhea7Wcfq2bv4tWTP3VvJ8Y9D[1] = {{ .b={ + 0xf7,0xf9,0x9a,0x09,0x42,0xfa,0xc7,0x88,0x05,0x49,0x43,0xab,0xbc,0xf9,0x46,0xb1, + 0xf9,0xb9,0x16,0xcb,0xe1,0x0a,0xed,0xcf,0xa9,0x96,0x76,0x65,0x37,0x1c,0xa2,0x80 } }}; +fd_acct_addr_t _T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt[1] = {{ .b={ + 0x06,0xaa,0x09,0x54,0x8b,0x50,0x47,0x6a,0xd4,0x62,0xf9,0x1f,0x89,0xa3,0x01,0x50, + 0x33,0x26,0x4f,0xc9,0xab,0xd5,0x27,0x00,0x20,0xa9,0xd1,0x42,0x33,0x47,0x42,0xfb } }}; + +#define EXPAND_ARR8(arr, i) arr[(i)], arr[(i)+1], arr[(i)+2], arr[(i)+3], arr[(i)+4], arr[(i)+5], arr[(i)+6], arr[(i)+7], +#define EXPAND_ARR32(arr, i) EXPAND_ARR8(arr, (i)) EXPAND_ARR8(arr, (i)+8) EXPAND_ARR8(arr, (i)+16) EXPAND_ARR8(arr, (i)+24) + +static inline void +test_repro_onchain( void ) { + fd_bundle_crank_gen_t g[1]; + + fd_bundle_crank_gen_init( g, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, _T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt, + _3iPuTgpWaaC6jYEY7kd993QBthGsQTK3yPCrNJyPMhCD, _GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib, + 0UL ); + fd_acct_addr_t tip_payment_config[1]; + fd_acct_addr_t tip_receiver [1]; + + fd_bundle_crank_get_addresses( g, 740UL, tip_payment_config, tip_receiver ); + FD_TEST( fd_memeq( tip_payment_config, _HgzT81VF1xZ3FT9Eq1pHhea7Wcfq2bv4tWTP3VvJ8Y9D, 32UL ) ); + FD_TEST( fd_memeq( tip_receiver, _G8RaABmvrvNCcGu41NV5oKjCfHeBv1zNn58dFcZzyRRw, 32UL ) ); + + fd_bundle_crank_tip_payment_config_t old_tip_payment_config[1] = {{ + .discriminator = 0x82ccfa1ee0aa0c9bUL, + .tip_receiver = {{{ EXPAND_ARR32( _GiLHMES95axFbFX7ogCTwL6QQ1uqspajz9SHMpt5dCGh->b, 0UL ) }}}, + .block_builder = {{{ EXPAND_ARR32( _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY->b, 0UL ) }}}, + .commission_pct = 5UL, + .bumps = { 254, 255, 254, 255, 255, 252, 255, 252, 255 } + }}; + + uchar payload[ FD_TXN_MTU ]; + uchar _txn[ FD_TXN_MAX_SZ ]; + fd_txn_t * txn = (fd_txn_t *)_txn; + uchar _txn_2ni[ FD_TXN_MAX_SZ ]; + fd_txn_t * txn_2ni = (fd_txn_t *)_txn_2ni; + + ulong sz = fd_bundle_crank_generate( g, old_tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ); + FD_TEST( sz==sizeof(fd_bundle_crank_2_t) ); + /* The transactions are not necessarily byte-for-byte identical, + but they should be effectively identical */ + + ulong txn_2ni_sz = fd_txn_parse( payload_2ni, payload_2ni_sz, _txn_2ni, NULL ); + FD_TEST( txn_2ni_sz ); + + FD_TEST( txn->instr_cnt==txn_2ni->instr_cnt+1UL ); + fd_acct_addr_t const * addr = fd_txn_get_acct_addrs( txn, payload ); + fd_acct_addr_t const * addr_2ni = fd_txn_get_acct_addrs( txn_2ni, payload_2ni ); +#define ASSERT_PUBKEY_EQ( idx1, idx2 ) FD_TEST( fd_memeq( addr+(idx1), addr_2ni+(idx2), 32UL ) ); \ + FD_TEST( fd_txn_is_writable( txn, idx1 )==fd_txn_is_writable( txn_2ni, idx2 ) ); \ + FD_TEST( fd_txn_is_signer ( txn, idx1 )==fd_txn_is_signer ( txn_2ni, idx2 ) ); + for( ulong i=0UL; iinstr_cnt; i++ ) { + FD_TEST( fd_memeq( addr + txn->instr[i+1UL].program_id, addr_2ni + txn_2ni->instr[i].program_id, 32UL ) ); + ASSERT_PUBKEY_EQ( txn->instr[i+1UL].program_id, txn_2ni->instr[i].program_id ); + FD_TEST( txn->instr[i+1UL].acct_cnt == txn_2ni->instr[i].acct_cnt ); + FD_TEST( txn->instr[i+1UL].data_sz == txn_2ni->instr[i].data_sz ); + for( ulong j=0UL; jinstr[i+1UL].acct_cnt; j++ ) { + ASSERT_PUBKEY_EQ( payload[ txn->instr[i+1UL].acct_off+j ], payload_2ni[ txn_2ni->instr[i].acct_off+j ] ); + } + FD_TEST( fd_memeq( payload + txn->instr[i+1UL].data_off, payload_2ni + txn_2ni->instr[i].data_off, txn->instr[i].data_sz ) ); + } + + do { + uchar _txn2[ FD_TXN_MAX_SZ ]; + ulong txn_sz = fd_txn_parse( payload, sz, _txn2, NULL ); + FD_TEST( txn_sz ); + FD_TEST( fd_memeq( _txn2, _txn, txn_sz ) ); + } while( 0 ); + + do { + char base64[ FD_BASE64_ENC_SZ( 1232 ) ]; + base64[ fd_base64_encode( base64, payload, sz ) ] = '\0'; + FD_LOG_NOTICE(( "Sample transaction: %s", base64 )); + } while( 0 ); +} + +static inline int +check_duplicates( fd_bundle_crank_gen_t * g, + fd_rng_t * rng, + fd_acct_addr_t const * old_tip_receiver, + fd_acct_addr_t const * old_block_builder ) { + + fd_bundle_crank_tip_payment_config_t old_tip_payment_config[1] = {{ + .discriminator = 0x82ccfa1ee0aa0c9bUL, + .tip_receiver = {{{ EXPAND_ARR32( old_tip_receiver->b, 0UL ) }}}, + .block_builder = {{{ EXPAND_ARR32( old_block_builder->b, 0UL ) }}}, + .commission_pct = 6UL, + .bumps = { 254, 255, 254, 255, 255, 252, 255, 252, 255 } + }}; + + uchar payload[ FD_TXN_MTU ]; + uchar _txn[ FD_TXN_MAX_SZ ]; + fd_txn_t * txn = (fd_txn_t *)_txn; + + ulong sz = fd_bundle_crank_generate( g, old_tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ); + + FD_TEST( sz==sizeof(fd_bundle_crank_2_t) ); + + fd_acct_addr_t const * addr = fd_txn_get_acct_addrs( txn, payload ); + + if( FD_UNLIKELY( !fd_memeq( addr[ payload[ txn->instr[1].acct_off + 1UL ] ].b, old_tip_receiver, 32UL ) ) ) return 0; + if( FD_UNLIKELY( !fd_memeq( addr[ payload[ txn->instr[1].acct_off + 3UL ] ].b, old_block_builder, 32UL ) ) ) return 0; + if( FD_UNLIKELY( !fd_memeq( addr[ payload[ txn->instr[2].acct_off + 2UL ] ].b, old_block_builder, 32UL ) ) ) return 0; + + fd_chkdup_t chkdup[1]; + fd_chkdup_join( fd_chkdup_new( chkdup, rng ) ); + + return !fd_chkdup_check( chkdup, addr, fd_txn_account_cnt( txn, FD_TXN_ACCT_CAT_ALL ), NULL, 0UL ); +} + +static inline void +test_no_duplicates( void ) { + fd_bundle_crank_gen_t g[1]; + + fd_rng_t _rng[1]; + fd_rng_t * rng; + rng = fd_rng_join( fd_rng_new( _rng, 1U, 3UL ) ); + + fd_bundle_crank_gen_init( g, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, _T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt, + _3iPuTgpWaaC6jYEY7kd993QBthGsQTK3yPCrNJyPMhCD, _GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib, + 0UL ); + FD_TEST( check_duplicates( g, rng, _GiLHMES95axFbFX7ogCTwL6QQ1uqspajz9SHMpt5dCGh, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY ) ); + FD_TEST( check_duplicates( g, rng, _GiLHMES95axFbFX7ogCTwL6QQ1uqspajz9SHMpt5dCGh, _DNVZMSqeRH18Xa4MCTrb1MndNf3Npg4MEwqswo23eWkf ) ); + FD_TEST( check_duplicates( g, rng, _DNVZMSqeRH18Xa4MCTrb1MndNf3Npg4MEwqswo23eWkf, _DNVZMSqeRH18Xa4MCTrb1MndNf3Npg4MEwqswo23eWkf ) ); + FD_TEST( check_duplicates( g, rng, _96gYZGLnJYVFmbjzopPSU6QiEV5fGqZNyN9nmNhvrZU5, _HFqU5x63VTqvQss8hp11i4wVV8bD44PvwucfZ2bU7gRe ) ); + FD_TEST( check_duplicates( g, rng, _G8RaABmvrvNCcGu41NV5oKjCfHeBv1zNn58dFcZzyRRw, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY ) ); + + fd_rng_delete( fd_rng_leave( rng ) ); + +} + +static inline void +test_crank_cnt( void ) { + fd_bundle_crank_gen_t g[1]; + + fd_bundle_crank_gen_init( g, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, _T1pyyaTNZsKv2WcRAB8oVnk93mLJw2XzjtVYqCsaHqt, + _3iPuTgpWaaC6jYEY7kd993QBthGsQTK3yPCrNJyPMhCD, _GZctHpWXmsZC1YHACTGGcHhYxjdRqQvTpYkb9LMvxDib, + 1UL ); + + fd_bundle_crank_tip_payment_config_t tip_payment_config[1] = {{ + .discriminator = 0x82ccfa1ee0aa0c9bUL, + .tip_receiver = {{{ EXPAND_ARR32( _G8RaABmvrvNCcGu41NV5oKjCfHeBv1zNn58dFcZzyRRw->b, 0UL ) }}}, + .block_builder = {{{ EXPAND_ARR32( _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY->b, 0UL ) }}}, + .commission_pct = 5UL, + .bumps = { 254, 255, 254, 255, 255, 252, 255, 252, 255 } + }}; + + uchar payload[ FD_TXN_MTU ]; + uchar _txn[ FD_TXN_MAX_SZ ]; + fd_txn_t * txn = (fd_txn_t *)_txn; + + fd_acct_addr_t uncreated[1] = {{{ 0 }}}; + FD_TEST( sizeof(fd_bundle_crank_3_t)==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, uncreated, 740UL, 5UL, payload, txn ) ); + FD_TEST( 0UL==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ) ); + + tip_payment_config->commission_pct++; + FD_TEST( sizeof(fd_bundle_crank_2_t)==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ) ); + tip_payment_config->commission_pct--; + + tip_payment_config->tip_receiver->b[1]++; + FD_TEST( sizeof(fd_bundle_crank_2_t)==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ) ); + tip_payment_config->tip_receiver->b[1]--; + + tip_payment_config->block_builder->b[2]++; + FD_TEST( sizeof(fd_bundle_crank_2_t)==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ) ); + tip_payment_config->block_builder->b[2]--; + + FD_TEST( 0UL==fd_bundle_crank_generate( g, tip_payment_config, _feeywn2ffX8DivmRvBJ9i9YZnss7WBouTmujfQcEdeY, + _GwHH8ciFhR8vejWCqmg8FWZUCNtubPY2esALvy5tBvji, _4R3gSG8BpU4t19KYj8CfnbtRpnT8gtk4dvTHxVRwc2r7, 740UL, 5UL, payload, txn ) ); +} + +int +main( int argc, + char ** argv ) { + fd_boot( &argc, &argv ); + + test_repro_onchain(); + test_no_duplicates(); + test_crank_cnt(); + + FD_LOG_NOTICE(( "pass" )); + + fd_halt(); + return 0; +} diff --git a/src/disco/tiles.h b/src/disco/tiles.h index 80edb89747..51d48054b8 100644 --- a/src/disco/tiles.h +++ b/src/disco/tiles.h @@ -6,6 +6,7 @@ #include "../ballet/shred/fd_shred.h" #include "pack/fd_pack.h" #include "topo/fd_topo.h" +#include "plugin/fd_bundle_crank.h" #include @@ -61,6 +62,20 @@ struct fd_became_leader { publish to show peers they were skipped correctly. This is used to adjust some pack limits. */ ulong total_skipped_ticks; + + /* The epoch of the slot for which we are becoming leader. */ + ulong epoch; + + /* Information from the accounts database as of the start of the slot + determined by the bank above that is necessary to crank the bundle + tip programs properly. If bundles are not enabled (determined + externally, but the relevant tiles should know), these fields are + set to 0. */ + struct { + fd_bundle_crank_tip_payment_config_t config[1]; + uchar tip_receiver_owner[32]; + uchar last_blockhash[32]; + } bundle[1]; }; typedef struct fd_became_leader fd_became_leader_t; diff --git a/src/disco/topo/fd_topo.h b/src/disco/topo/fd_topo.h index 19e555bcb8..c7b4ee6714 100644 --- a/src/disco/topo/fd_topo.h +++ b/src/disco/topo/fd_topo.h @@ -194,6 +194,15 @@ typedef struct { int larger_max_cost_per_block; int larger_shred_limits_per_block; int use_consumed_cus; + struct { + int enabled; + uchar tip_distribution_program_addr[ 32 ]; + uchar tip_payment_program_addr[ 32 ]; + uchar tip_distribution_authority[ 32 ]; + ulong commission_bps; + char identity_key_path[ PATH_MAX ]; + char vote_account_path[ PATH_MAX ]; /* or pubkey is okay */ + } bundle; } pack; struct { @@ -201,6 +210,12 @@ typedef struct { int plugins_enabled; ulong bank_cnt; char identity_key_path[ PATH_MAX ]; + struct { + int enabled; + uchar tip_payment_program_addr[ 32 ]; + uchar tip_distribution_program_addr[ 32 ]; + char vote_account_path[ PATH_MAX ]; + } bundle; } poh; struct {