wowok
Version:
Wowok Blockchain TypeScript API
1 lines • 7.46 kB
JavaScript
import{toBase64}from'../../_deps/bcs/index.js';import{promiseWithResolvers}from'../../_deps/utils/index.js';import{bcs}from'../../bcs/index.js';import{Transaction}from'../Transaction.js';import{TransactionDataBuilder}from'../TransactionData.js';import{CachingTransactionExecutor}from'./caching.js';import{ParallelQueue,SerialQueue}from'./queue.js';import{getGasCoinFromEffects}from'./serial.js';const PARALLEL_EXECUTOR_DEFAULTS={'coinBatchSize':0x14,'initialCoinBalance':0xbebc200n,'minimumCoinBalance':0x2faf080n,'maxPoolSize':0x32,'epochBoundaryWindow':0x3e8};export class ParallelTransactionExecutor{#signer;#client;#coinBatchSize;#initialCoinBalance;#minimumCoinBalance;#epochBoundaryWindow;#defaultGasBudget;#maxPoolSize;#sourceCoins;#coinPool=[];#cache;#objectIdQueues=new Map();#buildQueue=new SerialQueue();#executeQueue;#lastDigest=null;#cacheLock=null;#pendingTransactions=0x0;#gasPrice=null;constructor(a){this.#signer=a['signer'],this.#client=a['client'],this.#coinBatchSize=a['coinBatchSize']??PARALLEL_EXECUTOR_DEFAULTS['coinBatchSize'],this.#initialCoinBalance=a['initialCoinBalance']??PARALLEL_EXECUTOR_DEFAULTS['initialCoinBalance'],this.#minimumCoinBalance=a['minimumCoinBalance']??PARALLEL_EXECUTOR_DEFAULTS['minimumCoinBalance'],this.#defaultGasBudget=a['defaultGasBudget']??this.#minimumCoinBalance,this.#epochBoundaryWindow=a['epochBoundaryWindow']??PARALLEL_EXECUTOR_DEFAULTS['epochBoundaryWindow'],this.#maxPoolSize=a['maxPoolSize']??PARALLEL_EXECUTOR_DEFAULTS['maxPoolSize'],this.#cache=new CachingTransactionExecutor({'client':a['client'],'cache':a['cache']}),this.#executeQueue=new ParallelQueue(this.#maxPoolSize),this.#sourceCoins=a['sourceCoins']?new Map(a['sourceCoins']['map'](b=>[b,null])):null;}['resetCache'](){return this.#gasPrice=null,this.#updateCache(()=>this.#cache['reset']());}async['waitForLastTransaction'](){await this.#updateCache(()=>this.#waitForLastDigest());}async['executeTransaction'](a,b,c=[]){const {promise:d,resolve:e,reject:f}=promiseWithResolvers(),g=await this.#getUsedObjects(a),h=()=>{this.#executeQueue['runTask'](()=>{const j=this.#execute(a,g,b,c);return j['then'](e,f);});},i=new Set();return g['forEach'](j=>{const k=this.#objectIdQueues['get'](j);k?(i['add'](j),this.#objectIdQueues['get'](j)['push'](()=>{i['delete'](j),i['size']===0x0&&h();})):this.#objectIdQueues['set'](j,[]);}),i['size']===0x0&&h(),d;}async #getUsedObjects(a){const b=new Set();let c=![];return a['addSerializationPlugin'](async(d,e,f)=>{await f();if(c)return;c=!![],d['inputs']['forEach'](g=>{if(g['Object']?.['ImmOrOwnedObject']?.['objectId'])b['add'](g['Object']['ImmOrOwnedObject']['objectId']);else{if(g['Object']?.['Receiving']?.['objectId'])b['add'](g['Object']['Receiving']['objectId']);else g['UnresolvedObject']?.['objectId']&&!g['UnresolvedObject']['initialSharedVersion']&&b['add'](g['UnresolvedObject']['objectId']);}});}),await a['prepareForSerialization']({'client':this.#client}),b;}async #execute(a,b,c,d=[]){let e;try{a['setSenderIfNotSet'](this.#signer['toWAddress']()),await this.#buildQueue['runTask'](async()=>{const m=a['getData']();!m['gasData']['price']&&a['setGasPrice'](await this.#getGasPrice()),a['setGasBudgetIfNotSet'](this.#defaultGasBudget),await this.#updateCache(),e=await this.#getGasCoin(),this.#pendingTransactions++,a['setGasPayment']([{'objectId':e['id'],'version':e['version'],'digest':e['digest']}]),await this.#cache['buildTransaction']({'transaction':a,'onlyTransactionKind':!![]});});const f=await a['build']({'client':this.#client}),{signature:g}=await this.#signer['signTransaction'](f),h=await this.#cache['executeTransaction']({'transaction':f,'signature':[g,...d],'options':{...c,'showEffects':!![]}}),i=Uint8Array['from'](h['rawEffects']),j=bcs['TransactionEffects']['parse'](i),k=getGasCoinFromEffects(j),l=j['V2']?.['gasUsed'];if(e&&l&&k['owner']===this.#signer['toWAddress']()){const m=BigInt(l['computationCost'])+BigInt(l['storageCost'])+BigInt(l['storageCost'])-BigInt(l['storageRebate']),n=e['balance']-m;let o=![];new TransactionDataBuilder(a['getData']())['mapArguments'](p=>{return p['$kind']==='GasCoin'&&(o=!![]),p;}),!o&&n>=this.#minimumCoinBalance?this.#coinPool['push']({'id':k['ref']['objectId'],'version':k['ref']['version'],'digest':k['ref']['digest'],'balance':n}):(!this.#sourceCoins&&(this.#sourceCoins=new Map()),this.#sourceCoins['set'](k['ref']['objectId'],k['ref']));}return this.#lastDigest=h['digest'],{'digest':h['digest'],'effects':toBase64(i),'data':h};}catch(p){e&&(!this.#sourceCoins&&(this.#sourceCoins=new Map()),this.#sourceCoins['set'](e['id'],null));await this.#updateCache(async()=>{await Promise['all']([this.#cache['cache']['deleteObjects']([...b]),this.#waitForLastDigest()]);});throw p;}finally{b['forEach'](q=>{const r=this.#objectIdQueues['get'](q);if(r&&r['length']>0x0)r['shift']()();else r&&this.#objectIdQueues['delete'](q);}),this.#pendingTransactions--;}}async #updateCache(a){this.#cacheLock&&await this.#cacheLock,this.#cacheLock=a?.()['then'](()=>{this.#cacheLock=null;},()=>{})??null;}async #waitForLastDigest(){const a=this.#lastDigest;a&&(this.#lastDigest=null,await this.#client['waitForTransaction']({'digest':a}));}async #getGasCoin(){this.#coinPool['length']===0x0&&this.#pendingTransactions<=this.#maxPoolSize&&await this.#refillCoinPool();if(this.#coinPool['length']===0x0)throw new Error('No\x20coins\x20available');const a=this.#coinPool['shift']();return a;}async #getGasPrice(){const a=this.#gasPrice?this.#gasPrice['expiration']-this.#epochBoundaryWindow-Date['now']():0x0;if(a>0x0)return this.#gasPrice['price'];if(this.#gasPrice){const c=Math['max'](this.#gasPrice['expiration']+this.#epochBoundaryWindow-Date['now'](),0x3e8);await new Promise(d=>setTimeout(d,c));}const b=await this.#client['getLatestWowSystemState']();return this.#gasPrice={'price':BigInt(b['referenceGasPrice']),'expiration':Number['parseInt'](b['epochStartTimestampMs'],0xa)+Number['parseInt'](b['epochDurationMs'],0xa)},this.#getGasPrice();}async #refillCoinPool(){const a=Math['min'](this.#coinBatchSize,this.#maxPoolSize-(this.#coinPool['length']+this.#pendingTransactions)+0x1);if(a===0x0)return;const b=new Transaction(),c=this.#signer['toWAddress']();b['setSender'](c);if(this.#sourceCoins){const k=[],l=[];for(const [m,n]of this.#sourceCoins){n?k['push'](n):l['push'](m);}if(l['length']>0x0){const o=await this.#client['multiGetObjects']({'ids':l});k['push'](...o['filter'](p=>p['data']!==null)['map'](({data:p})=>({'objectId':p['objectId'],'version':p['version'],'digest':p['digest']})));}b['setGasPayment'](k),this.#sourceCoins=new Map();}const d=new Array(a)['fill'](this.#initialCoinBalance),e=b['splitCoins'](b['gas'],d),f=[];for(let p=0x0;p<d['length'];p++){f['push'](e[p]);}b['transferObjects'](f,c),await this['waitForLastTransaction']();const g=await this.#client['signAndExecuteTransaction']({'transaction':b,'signer':this.#signer,'options':{'showRawEffects':!![]}}),h=bcs['TransactionEffects']['parse'](Uint8Array['from'](g['rawEffects']));h['V2']?.['changedObjects']['forEach'](([q,{outputState:r}],s)=>{if(s===h['V2']?.['gasObjectIndex']||!r['ObjectWrite'])return;this.#coinPool['push']({'id':q,'version':h['V2']['lamportVersion'],'digest':r['ObjectWrite'][0x0],'balance':BigInt(this.#initialCoinBalance)});});!this.#sourceCoins&&(this.#sourceCoins=new Map());const j=getGasCoinFromEffects(h)['ref'];this.#sourceCoins['set'](j['objectId'],j),await this.#client['waitForTransaction']({'digest':g['digest']});}}