Skip to content

Commit cb3aced

Browse files
committed
Add e_cmd_batch_upd_price
1 parent 44699b7 commit cb3aced

File tree

3 files changed

+226
-16
lines changed

3 files changed

+226
-16
lines changed

program/src/oracle/oracle.c

Lines changed: 79 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ static bool is_valid_price_upd( cmd_upd_price_t *cptr, SolAccountInfo *price_acc
507507

508508
// Reject if this price corresponds to the same or earlier time
509509
pc_price_info_t *fptr = &pptr->comp_[comp_idx].latest_;
510-
if ( cptr->cmd_ == e_cmd_upd_price &&
510+
if ( ( cptr->cmd_ == e_cmd_upd_price || cptr->cmd_ == e_cmd_batch_upd_price ) &&
511511
cptr->pub_slot_ <= fptr->pub_slot_ ) {
512512
return false;
513513
}
@@ -525,14 +525,72 @@ static void do_price_upd( cmd_upd_price_t *cptr, SolAccountInfo *clock_account,
525525

526526
// update component price if required
527527
pc_price_info_t *fptr = &pptr->comp_[comp_idx].latest_;
528-
if ( cptr->cmd_ == e_cmd_upd_price ) {
528+
if ( ( cptr->cmd_ == e_cmd_upd_price || cptr->cmd_ == e_cmd_batch_upd_price ) ) {
529529
fptr->price_ = cptr->price_;
530530
fptr->conf_ = cptr->conf_;
531531
fptr->status_ = cptr->status_;
532532
fptr->pub_slot_ = cptr->pub_slot_;
533533
}
534534
}
535535

536+
static uint64_t batch_upd_price( SolParameters *prm, SolAccountInfo *ka ) {
537+
// Check that header information has been provided
538+
if ( prm->data_len < sizeof( cmd_batch_upd_price_header_t ) ) {
539+
return ERROR_INVALID_ARGUMENT;
540+
}
541+
cmd_batch_upd_price_header_t *hptr = (cmd_batch_upd_price_header_t *)prm->data;
542+
543+
// Check that within the batch, the correct number of updates have been provided
544+
if ( prm->data_len != ( sizeof( cmd_batch_upd_price_header_t ) + ( hptr->count_ * sizeof( cmd_upd_price_t ) ) ) ) {
545+
return ERROR_INVALID_ARGUMENT;
546+
}
547+
cmd_batch_upd_price_t *cptr = (cmd_batch_upd_price_t *)prm->data;
548+
549+
// Check that the correct number of account keys have been provided.
550+
// We expect the publisher account, the clock account and the price accounts to be present.
551+
if ( prm->ka_num != ( 2 + hptr->count_ ) ) {
552+
return ERROR_INVALID_ARGUMENT;
553+
}
554+
SolAccountInfo *publish_account = &ka[0];
555+
SolAccountInfo *clock_account = &ka[hptr->count_ + 1];
556+
557+
// Check that the publish accounts and the clock accounts are valid
558+
if ( !valid_funding_account( publish_account ) ||
559+
!pc_pub_key_equal( (pc_pub_key_t*)clock_account->key, (pc_pub_key_t*)sysvar_clock ) ) {
560+
return ERROR_INVALID_ARGUMENT;
561+
}
562+
563+
// Loop over all the given updates.
564+
bool any_succeeded = false;
565+
for ( uint64_t i = 0; i < hptr->count_; i++ ) {
566+
567+
cmd_upd_price_t *uptr = &cptr->upds_[i];
568+
SolAccountInfo *price_account = &ka[1 + i];
569+
570+
// Check that the price account is signed, writable with the correct ownership and is of the correct size
571+
if ( !valid_writable_account( prm, price_account, sizeof( pc_price_t ) ) ) {
572+
continue;
573+
}
574+
575+
// Check that this price update is valid
576+
uint32_t comp_idx = find_comp_idx( publish_account, price_account );
577+
if ( !is_valid_price_upd( uptr, price_account, comp_idx ) ) {
578+
continue;
579+
}
580+
581+
do_price_upd( uptr, clock_account, price_account, comp_idx );
582+
583+
any_succeeded = true;
584+
}
585+
586+
// Fail the transaction if no updates succeeded
587+
if ( !any_succeeded && hptr->count_ > 0 ) {
588+
return ERROR_INVALID_ARGUMENT;
589+
}
590+
591+
return SUCCESS;
592+
}
593+
536594
static uint64_t upd_price( SolParameters *prm, SolAccountInfo *ka )
537595
{
538596
// Validate command parameters
@@ -581,25 +639,30 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
581639
}
582640
switch(hdr->cmd_) {
583641
case e_cmd_upd_price:
584-
case e_cmd_agg_price: return upd_price( prm, ka );
585-
case e_cmd_init_mapping: return init_mapping( prm, ka );
586-
case e_cmd_add_mapping: return add_mapping( prm, ka );
587-
case e_cmd_add_product: return add_product( prm, ka );
588-
case e_cmd_upd_product: return upd_product( prm, ka );
589-
case e_cmd_add_price: return add_price( prm, ka );
590-
case e_cmd_add_publisher: return add_publisher( prm, ka );
591-
case e_cmd_del_publisher: return del_publisher( prm, ka );
592-
case e_cmd_init_price: return init_price( prm, ka );
593-
case e_cmd_init_test: return init_test( prm, ka );
594-
case e_cmd_upd_test: return upd_test( prm, ka );
595-
case e_cmd_set_min_pub: return set_min_pub( prm, ka );
596-
default: return ERROR_INVALID_ARGUMENT;
642+
case e_cmd_agg_price: return upd_price( prm, ka );
643+
case e_cmd_batch_upd_price: return batch_upd_price( prm, ka );
644+
case e_cmd_init_mapping: return init_mapping( prm, ka );
645+
case e_cmd_add_mapping: return add_mapping( prm, ka );
646+
case e_cmd_add_product: return add_product( prm, ka );
647+
case e_cmd_upd_product: return upd_product( prm, ka );
648+
case e_cmd_add_price: return add_price( prm, ka );
649+
case e_cmd_add_publisher: return add_publisher( prm, ka );
650+
case e_cmd_del_publisher: return del_publisher( prm, ka );
651+
case e_cmd_init_price: return init_price( prm, ka );
652+
case e_cmd_init_test: return init_test( prm, ka );
653+
case e_cmd_upd_test: return upd_test( prm, ka );
654+
case e_cmd_set_min_pub: return set_min_pub( prm, ka );
655+
default: return ERROR_INVALID_ARGUMENT;
597656
}
598657
}
599658

600659
extern uint64_t entrypoint(const uint8_t *input)
601660
{
602-
SolAccountInfo ka[4];
661+
// Batch transactions contain a price account for each symbol,
662+
// the publishing account and the sysvar_clock account.
663+
int16_t num_accounts = MAX_BATCH_UPD_PRICE_COUNT + 2;
664+
SolAccountInfo ka[num_accounts];
665+
603666
SolParameters prm[1];
604667
prm->ka = ka;
605668
if (!sol_deserialize(input, prm, SOL_ARRAY_SIZE(ka))) {

program/src/oracle/oracle.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,12 @@ typedef enum {
221221
// key[2] sysvar_clock account [readable]
222222
e_cmd_upd_price,
223223

224+
// publish a batch of component prices
225+
// key[0] funding account [signer writable]
226+
// key[1..n] price accounts [writable]
227+
// key[n] sysvar_clock account [readable]
228+
e_cmd_batch_upd_price,
229+
224230
// compute aggregate price
225231
// key[0] funding account [signer writable]
226232
// key[1] price account [writable]
@@ -334,6 +340,26 @@ typedef struct cmd_upd_price
334340

335341
static_assert( sizeof( cmd_upd_price_t ) == 40, "" );
336342

343+
// The maximum amount of price updates that pythd will send.
344+
// The limiting factor is the maximum size of a pythd JRPC request,
345+
// so we may be able to increase this in the futute.
346+
#define MAX_BATCH_UPD_PRICE_COUNT 8
347+
348+
typedef struct cmd_batch_upd_price_header
349+
{
350+
uint32_t ver_;
351+
int32_t cmd_;
352+
uint64_t count_;
353+
} cmd_batch_upd_price_header_t;
354+
355+
static_assert( sizeof( cmd_batch_upd_price_header_t ) == 16, "" );
356+
357+
typedef struct cmd_batch_upd_price
358+
{
359+
cmd_batch_upd_price_header_t header_;
360+
cmd_upd_price_t upds_[0];
361+
} cmd_batch_upd_price_t;
362+
337363
typedef struct cmd_upd_test
338364
{
339365
uint32_t ver_;

program/src/oracle/test_oracle.c

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,127 @@ Test( oracle, upd_price ) {
448448
cr_assert( ERROR_INVALID_ARGUMENT == dispatch( &prm, acc ) );
449449
}
450450

451+
Test( oracle, batch_upd_price ) {
452+
453+
// In this test we submit a batch update containing both a valid and invalid
454+
// price update. The valid update should have affect, the invalid update
455+
// should not, and the transaction should complete successfully.
456+
457+
// Set up the Solana accounts
458+
SolPubkey p_id = {.x = { 0xff, }};
459+
SolPubkey pkey = {.x = { 1, }};
460+
SolPubkey skey = {.x = { 3, }};
461+
sysvar_clock_t cvar = {
462+
.slot_ = 1
463+
};
464+
uint64_t pqty = 100, sqty = 200;
465+
466+
uint64_t num_price_accounts = 2;
467+
pc_price_t sptr[num_price_accounts];
468+
for ( uint64_t i = 0; i < num_price_accounts; i++) {
469+
sol_memset( &sptr[i], 0, sizeof( pc_price_t ) );
470+
sptr[i].magic_ = PC_MAGIC;
471+
sptr[i].ver_ = PC_VERSION;
472+
sptr[i].ptype_ = PC_PTYPE_PRICE;
473+
sptr[i].type_ = PC_ACCTYPE_PRICE;
474+
sptr[i].num_ = 1;
475+
pc_pub_key_assign( &sptr[i].comp_[0].pub_, (pc_pub_key_t*)&pkey );
476+
}
477+
478+
SolAccountInfo acc[] = {{
479+
.key = &pkey,
480+
.lamports = &pqty,
481+
.data_len = 0,
482+
.data = NULL,
483+
.owner = NULL,
484+
.rent_epoch = 0,
485+
.is_signer = true,
486+
.is_writable = true,
487+
.executable = false
488+
},{
489+
.key = &skey,
490+
.lamports = &sqty,
491+
.data_len = sizeof( pc_price_t ),
492+
.data = (uint8_t*)&sptr[0],
493+
.owner = &p_id,
494+
.rent_epoch = 0,
495+
.is_signer = false,
496+
.is_writable = true,
497+
.executable = false
498+
},{
499+
.key = &skey,
500+
.lamports = &sqty,
501+
.data_len = sizeof( pc_price_t ),
502+
.data = (uint8_t*)&sptr[1],
503+
.owner = &p_id,
504+
.rent_epoch = 0,
505+
.is_signer = false,
506+
.is_writable = true,
507+
.executable = false
508+
},{
509+
.key = (SolPubkey*)sysvar_clock,
510+
.lamports = &sqty,
511+
.data_len = sizeof( sysvar_clock_t ),
512+
.data = (uint8_t*)&cvar,
513+
.owner = &p_id,
514+
.rent_epoch = 0,
515+
.is_signer = false,
516+
.is_writable = false,
517+
.executable = false
518+
}};
519+
520+
// Create a request with two updates: one valid and one invalid
521+
uint64_t data_len = sizeof(cmd_batch_upd_price_header_t) + (sizeof(cmd_upd_price_t) * num_price_accounts);
522+
char input_data[data_len];
523+
cmd_batch_upd_price_t *req = (cmd_batch_upd_price_t*)&input_data;
524+
req->header_.ver_ = PC_VERSION;
525+
req->header_.cmd_ = e_cmd_batch_upd_price;
526+
req->header_.count_ = 2;
527+
528+
// The valid update
529+
cmd_upd_price_t *upd = &req->upds_[0];
530+
upd->ver_ = PC_VERSION;
531+
upd->cmd_ = e_cmd_batch_upd_price;
532+
upd->status_ = PC_STATUS_TRADING;
533+
upd->price_ = 55L;
534+
upd->conf_ = 9L;
535+
upd->pub_slot_ = 1;
536+
537+
// The invalid update: incorrect program version
538+
upd = &req->upds_[1];
539+
upd->ver_ = 1;
540+
upd->cmd_ = e_cmd_batch_upd_price;
541+
upd->status_ = PC_STATUS_TRADING;
542+
upd->price_ = 73L;
543+
upd->conf_ = 8L;
544+
upd->pub_slot_ = 1;
545+
546+
SolParameters prm = {
547+
.ka = acc,
548+
.ka_num = 4,
549+
.data = (const uint8_t*)req,
550+
.data_len = data_len,
551+
.program_id = &p_id
552+
};
553+
554+
// Dispatch the request and check that the transaction completed successfully
555+
cr_assert( SUCCESS == dispatch( &prm, acc ) );
556+
557+
// Check that the first account got updated successfully
558+
cr_assert( sptr[0].comp_[0].latest_.price_ == 55L );
559+
cr_assert( sptr[0].comp_[0].latest_.conf_ == 9L );
560+
cr_assert( sptr[0].comp_[0].latest_.pub_slot_ == 1 );
561+
cr_assert( sptr[0].agg_.pub_slot_ == 1 );
562+
cr_assert( sptr[0].valid_slot_ == 0 );
563+
564+
// Check that the second account didn't
565+
cr_assert( sptr[1].comp_[0].latest_.price_ == 0L );
566+
cr_assert( sptr[1].comp_[0].latest_.conf_ == 0L );
567+
cr_assert( sptr[1].comp_[0].latest_.pub_slot_ == 0 );
568+
cr_assert( sptr[1].agg_.pub_slot_ == 0 );
569+
cr_assert( sptr[1].valid_slot_ == 0 );
570+
}
571+
451572
Test( oracle, upd_aggregate ) {
452573
pc_price_t px[1];
453574
sol_memset( px, 0, sizeof( pc_price_t ) );

0 commit comments

Comments
 (0)