Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flamenco: adds txn generation methods #1692

Merged
merged 2 commits into from
May 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/flamenco/txn/Local.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ifdef FD_HAS_INT128
$(call add-hdrs,fd_txn_generate.h)
$(call add-objs,fd_txn_generate,fd_flamenco)
endif
160 changes: 160 additions & 0 deletions src/flamenco/txn/fd_txn_generate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#include "fd_txn_generate.h"

/* Message header type */
struct __attribute__((packed)) fd_txn_message_hdr {
uchar num_signatures;
uchar num_readonly_signatures;
uchar num_readonly_unsigned;
};

typedef struct fd_txn_message_hdr fd_txn_message_hdr_t;

static fd_txn_instr_t *
fd_txn_instr_meta_generate( uchar * out_buf,
uchar program_id,
ushort acct_cnt,
ushort data_sz,
ushort acct_off,
ushort data_off ) {
fd_txn_instr_t * out_instr = (fd_txn_instr_t *) out_buf;
out_instr->program_id = program_id;
out_instr->acct_cnt = acct_cnt;
out_instr->data_sz = data_sz;
out_instr->acct_off = acct_off;
out_instr->data_off = data_off;
return out_instr;
}

ulong
fd_txn_base_generate( uchar out_txn_meta[ static FD_TXN_MAX_SZ ],
uchar out_txn_payload[ static FD_TXN_MTU ],
ulong num_signatures,
fd_txn_accounts_t * accounts,
uchar * opt_recent_blockhash ) {

/* Number of signatures cannot exceed 127. */
FD_TEST(num_signatures <= FD_TXN_SIG_MAX);
*out_txn_payload = (uchar)num_signatures;

/* Fill out txn metadata */
fd_txn_t * txn_meta = (fd_txn_t *) out_txn_meta;
txn_meta->acct_addr_cnt = accounts->acct_cnt;
txn_meta->readonly_signed_cnt = accounts->readonly_signed_cnt;
txn_meta->readonly_unsigned_cnt = accounts->readonly_unsigned_cnt;
/* Number of signatures is encoded as a compact u16 but
can always be encoded using 1 byte here. */
txn_meta->message_off = (ushort)(num_signatures * FD_TXN_SIGNATURE_SZ + 1);
Copy link
Contributor

@yhzhangjump yhzhangjump May 17, 2024

Choose a reason for hiding this comment

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

If num_signatures is higher than 20, then this offset is already going beyond FD_TXN_MTU (20*64=1280>1232). It seems useful to guarantee that this function would never write beyond FD_TXN_MTU bytes from out_txn_payload.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We have it setup such that the caller can specify a larger buffer. That can be changed in a follow up PR if you'd like to take a look at that

txn_meta->signature_off = (ushort)1UL;
txn_meta->instr_cnt = 0;

FD_TEST(txn_meta->acct_addr_cnt < FD_TXN_ACCT_ADDR_MAX);
txn_meta->acct_addr_off = (ushort)(txn_meta->message_off + (sizeof(fd_txn_message_hdr_t)) + 1);
txn_meta->recent_blockhash_off = (ushort)(txn_meta->acct_addr_off + (txn_meta->acct_addr_cnt * FD_TXN_ACCT_ADDR_SZ));

/* Fill message header in txn payload */
uchar * write_ptr = out_txn_payload + txn_meta->message_off;
fd_txn_message_hdr_t msg_header = { .num_signatures = accounts->signature_cnt,
.num_readonly_signatures = accounts->readonly_signed_cnt,
.num_readonly_unsigned = accounts->readonly_unsigned_cnt };
memcpy( write_ptr, &msg_header, sizeof(fd_txn_message_hdr_t) ) ;
write_ptr += sizeof(fd_txn_message_hdr_t);

/* Write number of accounts */
*write_ptr = (uchar)txn_meta->acct_addr_cnt;
write_ptr += 1;

/* Write accounts list to txn payload */
ulong signers_write_sz = FD_TXN_ACCT_ADDR_SZ * (accounts->signature_cnt - accounts->readonly_signed_cnt);
fd_memcpy( write_ptr, accounts->signers_w, signers_write_sz );
write_ptr += signers_write_sz;

fd_memcpy( write_ptr, accounts->signers_r, FD_TXN_ACCT_ADDR_SZ * accounts->readonly_signed_cnt );
write_ptr += FD_TXN_ACCT_ADDR_SZ * accounts->readonly_signed_cnt;

ulong non_signers_write_sz = FD_TXN_ACCT_ADDR_SZ * (ulong)(accounts->acct_cnt - accounts->readonly_unsigned_cnt - accounts->signature_cnt);
fd_memcpy( write_ptr, accounts->non_signers_w, non_signers_write_sz);
write_ptr += non_signers_write_sz;

fd_memcpy( write_ptr, accounts->non_signers_r, FD_TXN_ACCT_ADDR_SZ * accounts->readonly_unsigned_cnt );
write_ptr += FD_TXN_ACCT_ADDR_SZ * accounts->readonly_unsigned_cnt;
FD_TEST( (ushort)((ulong)write_ptr - (ulong)out_txn_payload) == txn_meta->recent_blockhash_off );

/* Write recent blockhash */
if( FD_LIKELY( opt_recent_blockhash ) ) {
memcpy( write_ptr, opt_recent_blockhash, FD_TXN_BLOCKHASH_SZ );
} else {
memset( write_ptr, 0UL, FD_TXN_BLOCKHASH_SZ );
}
write_ptr += FD_TXN_BLOCKHASH_SZ;

return (ulong)(write_ptr - out_txn_payload);
}

ulong
fd_txn_add_instr( uchar * txn_meta_ptr,
uchar out_txn_payload[ static FD_TXN_MTU ],
uchar program_id,
uchar const * accounts,
ulong accounts_sz,
uchar const * instr_buf,
ulong instr_buf_sz ) {

fd_txn_t * txn_meta = (fd_txn_t *) txn_meta_ptr;
FD_TEST( txn_meta->instr_cnt < FD_TXN_INSTR_MAX );
FD_TEST( txn_meta->recent_blockhash_off != 0 );

uchar * instr_start = out_txn_payload + txn_meta->recent_blockhash_off + FD_TXN_BLOCKHASH_SZ;
txn_meta->instr_cnt++;
uchar * write_ptr = instr_start;

uint compact_instr_cnt_sz = fd_cu16_enc( (ushort)txn_meta->instr_cnt, write_ptr );
FD_TEST( compact_instr_cnt_sz == 1 );

write_ptr += compact_instr_cnt_sz;

/* Calculate offset of next instruction. */
if ( FD_UNLIKELY( txn_meta->instr_cnt > 1 ) ) {
Copy link
Contributor

@yhzhangjump yhzhangjump May 17, 2024

Choose a reason for hiding this comment

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

I am not sure why this is txn_meta->instr_cnt > 1 instead of txn_meta->instr_cnt > 0. It seems that, even if txn_meta->instr_cnt == 1, we should recalculate write_ptr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the write_ptr is already at the right offset for that case

write_ptr = out_txn_payload + txn_meta->instr[txn_meta->instr_cnt-2].data_off + txn_meta->instr[txn_meta->instr_cnt-2].data_sz;
}

instr_start = write_ptr;

*write_ptr = program_id;
write_ptr += sizeof(uchar);

uint compact_accts_len_sz = fd_cu16_enc( (ushort)accounts_sz, write_ptr );
write_ptr += compact_accts_len_sz;

ushort acct_off = (ushort) (write_ptr - out_txn_payload);
fd_memcpy( write_ptr, accounts, accounts_sz );
write_ptr += accounts_sz;

ushort data_sz = (ushort)instr_buf_sz;
uint compact_data_len_sz = fd_cu16_enc( data_sz, write_ptr );
write_ptr += compact_data_len_sz;

/* Copy data buffer over */
ushort data_off = (ushort) (write_ptr - out_txn_payload);
fd_memcpy( write_ptr, instr_buf, data_sz );
write_ptr += data_sz;

(void) fd_txn_instr_meta_generate( (uchar*)&txn_meta->instr[txn_meta->instr_cnt-1],
program_id,
(ushort)accounts_sz,
data_sz, acct_off, data_off );
return (ulong)(instr_start - out_txn_payload);
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems that you are returning the old txn size instead of the new txn size. Is this deliberate? It seems more natural to return the new txn size.
Also, you never check whether we are writing more than FD_TXN_MTU bytes to out_txn_payload. It may be useful to guarantee that this function never writes overflow out_txn_payload, for security purposes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this returns the size of the just added instr

}

void
fd_txn_reset_instrs( uchar * txn_meta_ptr,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe call it fd_txn_remove_instrs?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

remove is used for removing a single element across firedancer so i steered clear of that for this

uchar out_txn_payload[ static FD_TXN_MTU ] ) {
fd_txn_t * txn_meta = (fd_txn_t *)txn_meta_ptr;
if( FD_UNLIKELY( txn_meta->instr_cnt == 0 ) ) {
return;
}

ulong instr_start = txn_meta->recent_blockhash_off + FD_TXN_BLOCKHASH_SZ;

*(out_txn_payload + instr_start) = 0;
txn_meta->instr_cnt = 0;
}
58 changes: 58 additions & 0 deletions src/flamenco/txn/fd_txn_generate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/* Provides utility methods to create txn templates for
pre-staging, as well as a mechanism to build out an
entire transaction with instructions. */

#include "../../ballet/txn/fd_txn.h"
#include "../../ballet/txn/fd_compact_u16.h"
#include "../../flamenco/types/fd_types_custom.h"
#include "../../flamenco/types/fd_types.h"

/* Struct used to define a list of accounts supplied in a txn.
Also provides information on number of signers/writeable accounts. */
struct fd_txn_accounts {
/* signature cnt <= 128 */
uchar signature_cnt;
/* readonly signed/unsigned <= 128 */
uchar readonly_signed_cnt;
uchar readonly_unsigned_cnt;
ushort acct_cnt;
fd_pubkey_t * signers_w;
fd_pubkey_t * signers_r;
fd_pubkey_t * non_signers_w;
fd_pubkey_t * non_signers_r;
};

typedef struct fd_txn_accounts fd_txn_accounts_t;

FD_PROTOTYPES_BEGIN

/* Method used to create a template for a txn (useful for pre-staging and re-use)
Num signatures is restricted to < 128. Returns the offset to the start of the
insructions. */
ulong
fd_txn_base_generate( uchar out_txn_meta[ static FD_TXN_MAX_SZ ],
uchar out_txn_payload[ static FD_TXN_MTU ],
ulong num_signatures,
fd_txn_accounts_t * accounts,
uchar * opt_recent_blockhash );

/* Method used for adding an instruction to a txn being generated.
The accounts param is a list of indices to the accounts in the txn.
The instruction buffer contains the data for the instruction to
be added. Returns the offset to the start of the instruction added
in the transaction buffer. */
ulong
arjain4 marked this conversation as resolved.
Show resolved Hide resolved
fd_txn_add_instr( uchar * txn_meta_ptr,
uchar out_txn_payload[ static FD_TXN_MTU ],
uchar program_id,
uchar const * accounts,
ulong accounts_sz,
uchar const * instr_buf,
ulong instr_buf_sz );

/* Helper method to reset the list of instrs in the metadata and
remove all instrs from the txn payload. */
void
fd_txn_reset_instrs( uchar * txn_meta_ptr,
uchar out_txn_payload[ static FD_TXN_MTU ] );
FD_PROTOTYPES_END