Skip to content

Commit e6997cc

Browse files
authored
Add batch price update instruction to Oracle (#142)
* Remove unused function rpc::upd_price::build_tx. The batched version is used everywhere in the codebase. * Capture solana logs in integration tests * Add cmd_upd_price_no_fail_on_error * Change RPC client to send e_cmd_upd_price_no_fail_on_error instructions
1 parent 48ee1ad commit e6997cc

File tree

7 files changed

+141
-65
lines changed

7 files changed

+141
-65
lines changed

pc/rpc_client.cpp

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,7 @@ rpc::upd_price::upd_price()
809809
ckey_( nullptr ),
810810
gkey_( nullptr ),
811811
akey_( nullptr ),
812-
cmd_( e_cmd_upd_price )
812+
cmd_( e_cmd_upd_price_no_fail_on_error )
813813
{
814814
}
815815

@@ -851,7 +851,7 @@ void rpc::upd_price::set_price( int64_t px,
851851
price_ = px;
852852
conf_ = conf;
853853
st_ = st;
854-
cmd_ = is_agg?e_cmd_agg_price:e_cmd_upd_price;
854+
cmd_ = is_agg?e_cmd_agg_price:e_cmd_upd_price_no_fail_on_error;
855855
}
856856

857857
void rpc::upd_price::set_slot( const uint64_t pub_slot )
@@ -884,52 +884,6 @@ class tx_wtr : public net_wtr
884884
}
885885
};
886886

887-
void rpc::upd_price::build_tx( bincode& tx )
888-
{
889-
// signatures section
890-
tx.add_len<1>(); // one signature (publish)
891-
size_t pub_idx = tx.reserve_sign();
892-
893-
// message header
894-
size_t tx_idx = tx.get_pos();
895-
tx.add( (uint8_t)1 ); // pub is only signing account
896-
tx.add( (uint8_t)0 ); // read-only signed accounts
897-
tx.add( (uint8_t)2 ); // sysvar and program-id are read-only
898-
// unsigned accounts
899-
900-
// accounts
901-
tx.add_len<4>(); // 4 accounts: publish, symbol, sysvar, program
902-
tx.add( *pkey_ ); // publish account
903-
tx.add( *akey_ ); // symbol account
904-
tx.add( *(pub_key*)sysvar_clock ); // sysvar account
905-
tx.add( *gkey_ ); // programid
906-
907-
// recent block hash
908-
tx.add( *bhash_ ); // recent block hash
909-
910-
// instructions section
911-
tx.add_len<1>(); // one instruction
912-
tx.add( (uint8_t)3); // program_id index
913-
tx.add_len<3>(); // 3 accounts: publish, symbol, sysvar
914-
tx.add( (uint8_t)0 ); // index of publish account
915-
tx.add( (uint8_t)1 ); // index of symbol account
916-
tx.add( (uint8_t)2 ); // index of sysvar account
917-
918-
// instruction parameter section
919-
tx.add_len<sizeof(cmd_upd_price)>();
920-
tx.add( (uint32_t)PC_VERSION );
921-
tx.add( (int32_t)cmd_ );
922-
tx.add( (int32_t)st_ );
923-
tx.add( (int32_t)0 );
924-
tx.add( price_ );
925-
tx.add( conf_ );
926-
tx.add( pub_slot_ );
927-
928-
// all accounts need to sign transaction
929-
tx.sign( pub_idx, tx_idx, *ckey_ );
930-
sig_.init_from_buf( (const uint8_t*)(tx.get_buf() + pub_idx) );
931-
}
932-
933887
bool rpc::upd_price::build_tx(
934888
bincode& tx, upd_price* upds[], const unsigned n
935889
)

pc/rpc_client.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,6 @@ namespace pc
429429
static bool request( json_wtr&, upd_price*[], const unsigned n );
430430

431431
private:
432-
void build_tx( bincode& );
433432
static bool build_tx( bincode&, upd_price*[], unsigned n );
434433

435434
hash *bhash_;

program/src/oracle/oracle.c

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ static uint64_t upd_price( SolParameters *prm, SolAccountInfo *ka )
520520
// reject if this price corresponds to the same or earlier time
521521
pc_price_info_t *fptr = &pptr->comp_[i].latest_;
522522
sysvar_clock_t *sptr = (sysvar_clock_t*)ka[clock_idx].data;
523-
if ( cptr->cmd_ == e_cmd_upd_price &&
523+
if ( ( cptr->cmd_ == e_cmd_upd_price || cptr->cmd_ == e_cmd_upd_price_no_fail_on_error ) &&
524524
cptr->pub_slot_ <= fptr->pub_slot_ ) {
525525
return ERROR_INVALID_ARGUMENT;
526526
}
@@ -531,7 +531,7 @@ static uint64_t upd_price( SolParameters *prm, SolAccountInfo *ka )
531531
}
532532

533533
// update component price if required
534-
if ( cptr->cmd_ == e_cmd_upd_price ) {
534+
if ( cptr->cmd_ == e_cmd_upd_price || cptr->cmd_ == e_cmd_upd_price_no_fail_on_error ) {
535535
fptr->price_ = cptr->price_;
536536
fptr->conf_ = cptr->conf_;
537537
fptr->status_ = cptr->status_;
@@ -540,6 +540,12 @@ static uint64_t upd_price( SolParameters *prm, SolAccountInfo *ka )
540540
return SUCCESS;
541541
}
542542

543+
static uint64_t upd_price_no_fail_on_error( SolParameters *prm, SolAccountInfo *ka )
544+
{
545+
upd_price( prm, ka );
546+
return SUCCESS;
547+
}
548+
543549
static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
544550
{
545551
if (prm->data_len < sizeof(cmd_hdr_t) ) {
@@ -551,19 +557,20 @@ static uint64_t dispatch( SolParameters *prm, SolAccountInfo *ka )
551557
}
552558
switch(hdr->cmd_) {
553559
case e_cmd_upd_price:
554-
case e_cmd_agg_price: return upd_price( prm, ka );
555-
case e_cmd_init_mapping: return init_mapping( prm, ka );
556-
case e_cmd_add_mapping: return add_mapping( prm, ka );
557-
case e_cmd_add_product: return add_product( prm, ka );
558-
case e_cmd_upd_product: return upd_product( prm, ka );
559-
case e_cmd_add_price: return add_price( prm, ka );
560-
case e_cmd_add_publisher: return add_publisher( prm, ka );
561-
case e_cmd_del_publisher: return del_publisher( prm, ka );
562-
case e_cmd_init_price: return init_price( prm, ka );
563-
case e_cmd_init_test: return init_test( prm, ka );
564-
case e_cmd_upd_test: return upd_test( prm, ka );
565-
case e_cmd_set_min_pub: return set_min_pub( prm, ka );
566-
default: return ERROR_INVALID_ARGUMENT;
560+
case e_cmd_agg_price: return upd_price( prm, ka );
561+
case e_cmd_upd_price_no_fail_on_error: return upd_price_no_fail_on_error( prm, ka );
562+
case e_cmd_init_mapping: return init_mapping( prm, ka );
563+
case e_cmd_add_mapping: return add_mapping( prm, ka );
564+
case e_cmd_add_product: return add_product( prm, ka );
565+
case e_cmd_upd_product: return upd_product( prm, ka );
566+
case e_cmd_add_price: return add_price( prm, ka );
567+
case e_cmd_add_publisher: return add_publisher( prm, ka );
568+
case e_cmd_del_publisher: return del_publisher( prm, ka );
569+
case e_cmd_init_price: return init_price( prm, ka );
570+
case e_cmd_init_test: return init_test( prm, ka );
571+
case e_cmd_upd_test: return upd_test( prm, ka );
572+
case e_cmd_set_min_pub: return set_min_pub( prm, ka );
573+
default: return ERROR_INVALID_ARGUMENT;
567574
}
568575
}
569576

program/src/oracle/oracle.h

Lines changed: 6 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 component price, never returning an error even if the update failed
225+
// key[0] funding account [signer writable]
226+
// key[1] price account [writable]
227+
// key[2] sysvar_clock account [readable]
228+
e_cmd_upd_price_no_fail_on_error,
229+
224230
// compute aggregate price
225231
// key[0] funding account [signer writable]
226232
// key[1] price account [writable]

program/src/oracle/test_oracle.c

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

451+
Test( oracle, upd_price_no_fail_on_error ) {
452+
cmd_upd_price_t idata = {
453+
.ver_ = PC_VERSION,
454+
.cmd_ = e_cmd_upd_price_no_fail_on_error,
455+
.status_ = PC_STATUS_TRADING,
456+
.price_ = 42L,
457+
.conf_ = 9L,
458+
.pub_slot_ = 1
459+
};
460+
SolPubkey p_id = {.x = { 0xff, }};
461+
SolPubkey pkey = {.x = { 1, }};
462+
SolPubkey skey = {.x = { 3, }};
463+
sysvar_clock_t cvar = {
464+
.slot_ = 1
465+
};
466+
uint64_t pqty = 100, sqty = 200;
467+
pc_price_t sptr[1];
468+
sol_memset( sptr, 0, sizeof( pc_price_t ) );
469+
sptr->magic_ = PC_MAGIC;
470+
sptr->ver_ = PC_VERSION;
471+
sptr->ptype_ = PC_PTYPE_PRICE;
472+
sptr->type_ = PC_ACCTYPE_PRICE;
473+
sptr->num_ = 1;
474+
475+
SolAccountInfo acc[] = {{
476+
.key = &pkey,
477+
.lamports = &pqty,
478+
.data_len = 0,
479+
.data = NULL,
480+
.owner = NULL,
481+
.rent_epoch = 0,
482+
.is_signer = true,
483+
.is_writable = true,
484+
.executable = false
485+
},{
486+
.key = &skey,
487+
.lamports = &sqty,
488+
.data_len = sizeof( pc_price_t ),
489+
.data = (uint8_t*)sptr,
490+
.owner = &p_id,
491+
.rent_epoch = 0,
492+
.is_signer = false,
493+
.is_writable = true,
494+
.executable = false
495+
},{
496+
.key = (SolPubkey*)sysvar_clock,
497+
.lamports = &sqty,
498+
.data_len = sizeof( sysvar_clock_t ),
499+
.data = (uint8_t*)&cvar,
500+
.owner = &p_id,
501+
.rent_epoch = 0,
502+
.is_signer = false,
503+
.is_writable = false,
504+
.executable = false
505+
}};
506+
SolParameters prm = {
507+
.ka = acc,
508+
.ka_num = 3,
509+
.data = (const uint8_t*)&idata,
510+
.data_len = sizeof( idata ),
511+
.program_id = &p_id
512+
};
513+
514+
// We haven't permissioned the publish account for the price account
515+
// yet, so any update should fail silently and have no effect. The
516+
// transaction should "succeed".
517+
cr_assert( SUCCESS == dispatch( &prm, acc ) );
518+
cr_assert( sptr->comp_[0].latest_.price_ == 0L );
519+
cr_assert( sptr->comp_[0].latest_.conf_ == 0L );
520+
cr_assert( sptr->comp_[0].latest_.pub_slot_ == 0 );
521+
cr_assert( sptr->agg_.pub_slot_ == 0 );
522+
cr_assert( sptr->valid_slot_ == 0 );
523+
524+
// Now permission the publish account for the price account.
525+
pc_pub_key_assign( &sptr->comp_[0].pub_, (pc_pub_key_t*)&pkey );
526+
527+
// The update should now succeed, and have an effect.
528+
cr_assert( SUCCESS == dispatch( &prm, acc ) );
529+
cr_assert( sptr->comp_[0].latest_.price_ == 42L );
530+
cr_assert( sptr->comp_[0].latest_.conf_ == 9L );
531+
cr_assert( sptr->comp_[0].latest_.pub_slot_ == 1 );
532+
cr_assert( sptr->agg_.pub_slot_ == 1 );
533+
cr_assert( sptr->valid_slot_ == 0 );
534+
535+
// Invalid updates, such as publishing an update for the current slot,
536+
// should still fail silently and have no effect.
537+
idata.price_ = 55L;
538+
idata.conf_ = 22L;
539+
idata.pub_slot_ = 1;
540+
cr_assert( SUCCESS == dispatch( &prm, acc ) );
541+
cr_assert( sptr->comp_[0].latest_.price_ == 42L );
542+
cr_assert( sptr->comp_[0].latest_.conf_ == 9L );
543+
cr_assert( sptr->comp_[0].latest_.pub_slot_ == 1 );
544+
cr_assert( sptr->agg_.pub_slot_ == 1 );
545+
cr_assert( sptr->valid_slot_ == 0 );
546+
}
547+
451548
Test( oracle, upd_aggregate ) {
452549
pc_price_t px[1];
453550
sol_memset( px, 0, sizeof( pc_price_t ) );

pyth/tests/conftest.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,19 @@ def pyth_add_publisher(
321321
return pyth_add_price
322322

323323

324+
@pytest.fixture(scope='session')
325+
def solana_logs(solana_test_validator, solana_keygen):
326+
with open("solana_logs.txt", 'w') as f:
327+
cmd = [
328+
'solana', 'logs',
329+
'--url', 'localhost',
330+
'--keypair', solana_keygen[1],
331+
]
332+
p = subprocess.Popen(cmd, stdout=f)
333+
yield
334+
p.kill()
335+
336+
324337
@pytest.fixture(scope='function')
325338
def pyth_init_price(solana_test_validator, pyth_dir, pyth_add_publisher):
326339

pyth/tests/test_update_price.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pyth.tests.conftest import PRODUCTS
1010

1111
@pytest.mark.asyncio
12-
async def test_batch_update_price(solana_test_validator, pythd, pyth_dir, pyth_init_product, pyth_init_price):
12+
async def test_batch_update_price(solana_test_validator, solana_logs, pythd, pyth_dir, pyth_init_product, pyth_init_price):
1313

1414
messageIds = itertools.count()
1515

0 commit comments

Comments
 (0)