OverviewFilesCommitsIssuesDocsReleasesPeers

holepunchto-deps / @holepunchto / keet-core / lib / core / room-engine / index.js

1 lines · javascript ref:
const e=require("hypercore-id-encoding"),t=require("autobee"),i=require("b4a"),s=require("hyperblobs"),r=require("hyperbee2"),a=require("compact-encoding"),{KeetView:n}=require("@holepunchto/keet-indexing"),o=require("hyperbee-compat"),{KeetSchema:u}=require("@holepunchto/legacy-keet-core-schemas"),l=require("../migrations/3/apply"),{KeetContract:h}=require("../contract"),{KeetErrorV2:c}=require("../../errors"),{KeetContractError:g}=require("../contract/errors.js"),d=require("../sync/warmup"),{contract:{hyperdispatch:{decode:y,DispatchError:p}}}=require("../../../schema/spec"),b=require("../log.js"),w="keet",m=[h.DB_VIEW_NAME,w,h.BLOB_VIEW_NAME],f="keet/view/"+h.BLOB_VIEW_NAME;function _(){}function k(e,t){return e&&t?e.length===t.length&&i.equals(e.key,t.key):!e==!t}module.exports=class{constructor(e,i,s,r){this._keet=e,this.auto=new t(i,s,{viewName:"keet/v3/db",update:this._autobeeUpdate.bind(e,this),apply:this._autobeeApply.bind(e,this),warmup:t=>(this.warmup=new d(e,t,this),this.warmup.run()),close:this._autobeeClose.bind(e),open:this._autobeeOpen.bind(e),isTrusted:this._autobeeIsTrusted.bind(e),mostRecentTrusted:this._autobeeMostRecentTrusted.bind(e),migrate:this._autobeeMigrate.bind(this),preapply:e=>this._runPreApply(e),legacyViews:m,...r}),this.blobs=null,this.warmup=null,this._migratedViews=new Map,this._openingLegacyState=null,this._migrationRecord=null,this._closing=null}async ready(){await this.auto.ready(),await this.auto.view.bee.ready()}_runPreApply(e){if(this._keet.internalIsRoomDisabled(this.key))throw c.ROOM_DISABLED("Room is disabled",{roomKey:this.key});return this.loadMigratedViews(e)}loadMigratedViews(e){return this._openingLegacyState||(this._openingLegacyState=this._openLegacyState(e).finally(()=>{this._openingLegacyState=null})),this._openingLegacyState}async ensureMigratedViews(){const e=await this.view.db.get("@keet/autobee-migration");var t,i;if(e&&(t=e,!((i=this._migrationRecord)&&k(t.legacyViewHead,i.legacyViewHead)&&k(t.blobsViewHead,i.blobsViewHead))))return await this._closeLegacyState(),this.loadMigratedViews()}async _openLegacyState(e=this.view){if(this.blobs||this.legacy)return;let t=await e.db.get("@keet/autobee-migration");t||(t=await this._keet.getAutobeeMigrationInfo(this.key)),this._migrationRecord=t,t?.legacyViewHead&&!this.legacy&&await this._openLegacyView(t.legacyViewHead);const i=t?.blobsViewHead?.key||await this.auto.bootstrap.getUserData(f);i&&(this.blobs=L(this._keet.store,this.key,i,this.encryptionKey),await this.blobs.ready())}async _closeLegacyState(){const e=this.legacy,t=this.blobs;this.legacy=null,this.blobs=null,this._migrationRecord=null,e&&await e.close(),t&&await t.close()}async _openLegacyView(i,s){const a=w+"/"+e.encode(this.key),u=this._keet.store.namespace(a),l=()=>t.getViewEncryption(this.key,this.encryptionKey,w),h=u.get({name:"legacy-bee",manifestVersion:3,encryption:l(),inflightRange:[256,512]}),c=new r(u,{core:h,getEncryptionProvider:l});await c.ready(),c.move(i),this.legacy=new n(s,new o(c))}close(){if(this._closing)return this._closing;const e=[this.auto.close()];this.blobs&&e.push(this.blobs.close());for(const t of this._migratedViews.values())e.push(t.close());return this._closing=Promise.all(e),this._closing}get opened(){return this.auto.opened}on(...e){return this.auto.on(...e)}append(...e){return this.auto.append(...e)}getUserData(...e){return this.auto.getUserData(...e)}hintWakeup(...e){return this.auto.hintWakeup(...e)}setLocal(...e){return this.auto.setLocal(...e)}views(){return this.auto.views()}repair(){return this.auto.repair()}update(){return this.auto.update()}advance(){return this.auto.updated()}flush(){return this.auto.flush()}get id(){return this.auto.id}get bootstrap(){return this.auto.bootstrap}get view(){return this.auto.view}get system(){return this.auto.system}get writable(){return this.auto.writable}get busy(){return this.auto.busy}get isIndexer(){return this.auto.isIndexer}get local(){return this.auto.local}get core(){return this.auto.core}get viewCore(){return this.auto.bee.core}get length(){return this.auto.length}get signedLength(){return this.auto.signedLength}get key(){return this.auto.key}get discoveryKey(){return this.auto.discoveryKey}get encryptionKey(){return this.auto.encryptionKey}get wakeupCapability(){return this.auto.wakeupCapability}get peers(){return this.auto.peers}get activeWriters(){return this.auto.activeWriters}get migratedViews(){return[...this._migratedViews.values()]}get stats(){return{opened:this.auto.opened,closed:this.auto.closed,opening:!!this.auto.opening,closing:!!this.auto.closing,writable:this.auto.writable,interrupted:!!this.auto.interrupted,advancing:this.auto.busy,undos:this.auto.stats.undos,fastForwards:this.auto.stats.fastForwards,drains:this.auto.stats.drains,applies:this.auto.stats.applies,appends:this.auto.stats.appends}}get systemStats(){const{hash:e}=this.auto.system;return{...R(this.auto.system.bee),flushes:this.auto.system.flushes,hash:e?i.toString(e,"hex"):null}}get viewStats(){return R(this.auto.bee)}get writerStats(){return K(this.auto.local)}get tipSize(){return 0}get wakeupKey(){return this.key}shouldRepair(){return!1}getLastError(){return this.auto.getLastError()}async getBlob(e){if(e.blob){if(!this.blobs)throw new Error("Expected legacy blobs, but none found");return this.blobs.get(e.blob)}if(e.bee){const t=await this.view.db.get("@keet/blobs",e.bee);return t?t.data:null}return null}getSystemKey(){return this.auto.system.bee.head().key}batch(){return new V(this)}getMostRecentAdmin(){return A(this.view)}heads(){return this.auto.system.heads.slice()}dryRun(e,t){return this._autobeeApply.call(this._keet,this,e,t,v,{dryRun:!0})}_autobeeOpen(e,t){let i=null;const s=t.key;e.internal||(i=(e,t)=>this._cacheRoomReads(s,e,t));const r={autoUpdate:!0,extension:!1,trace:i},a=h.bee2(e,{hyperdb:r});return{...a,bee:e,core:a.cores.db,roomKey:s,blobs:null}}async _autobeeIsTrusted(e,t){try{const i=await t.db.get("@keet/devices",{key:e});if(!i||!i.status.isActive)return!1;const s=await t.db.get("@keet/members",{key:i.memberKey});return!!s&&s.status.isActive&&s.status.isAdmin}catch(e){return this.isTesting&&this.onerror(e),!1}}async _autobeeMostRecentTrusted(e,t){try{if(!t)return await A(e);const s=[],r=[];for await(const i of e.db.find("@keet/admins-by-most-recent",{reverse:!0}))s.push(i),r.push(t.db.get("@keet/admin-last-seen",{memberKey:i.memberKey}));const a=await Promise.all(r);for(let e=0;e<s.length;e++)if(a[e]&&i.equals(a[e].deviceKey,s[e].deviceKey))return{key:s[e].deviceKey,length:s[e].deviceLength};return null}catch(e){return this.isTesting&&this.onerror(e),null}}_autobeeClose(e){return Promise.all([e.db.close()])}async _autobeeMigrate(e,t,s){const r=e.get(w),a=e.get(h.DB_VIEW_NAME),n=e.get(h.BLOB_VIEW_NAME);for(const t of e.values()){if(!t)continue;const e=i.toString(t.key,"hex");this._migratedViews.set(e,this._keet.store.get(t.key))}if(t){const e=i.toString(t.key,"hex");this._migratedViews.set(e,this._keet.store.get(t.key))}r&&await this._openLegacyView(r,s),n&&(await this.auto.bootstrap.setUserData(f,n.key),this.blobs=L(this._keet.store,this.key,n.key,this.encryptionKey)),this._migrationRecord={legacyViewHead:r,blobsViewHead:n};const o=await this._keet.internalEnterLocalTransaction();try{await o.insert("@local/autobee-migrations",{roomKey:this.key,autobeeMigrationHead:t,legacyViewHead:r,dbViewHead:a,blobsViewHead:n}),await o.flush()}finally{await this._keet.internalExitLocalTransaction()}return a}async _autobeeUpdate(t,i,s){const r=s.get(h.DB_VIEW_NAME_AUTOBEE);await this.internalMarkRoomUpdated(t.key,r.to,r.incremental),this.roomProcessor.enqueue(t.key);const a=e.encode(t.discoveryKey),n=this._discovering.get(a);n&&(n.updated=!0,this.swarmWorker.notifyDiscoveryUpdate(a))}async _autobeeApply(e,t,i,s,{dryRun:r=!1}={}){const{db:n,roomKey:o}=i,u=r?null:this._boundAutobeeTrace,h=i.bee.head().length>0;let c=!1,d=0;if(e.legacy&&!h){if(e.legacy.base=s,d=await l.autobaseApply(t,e.legacy,s),d===t.length)return!0;c=e.legacy.core.length>0}const b=n.transaction();let w=null,m=!1,f=null;try{if(c){const i=t[d];if(r||i?.optimistic)return!0;s.preferFastForward(),await l.migrate(this.store,s.auto,e.legacy,b)}const n=await b.get("@keet/system-digest");if(n){const e=n.settings.isExperimental?this.abi.max:this.abi.production;if(n.abi.version>e){if(r)return!1;s.interrupt({unsupportedABI:n.abi.version,digest:n,nodes:t})}}f=n?n.abi.version:this.abi.max,f<this.abi.min&&(f=this.abi.min);let o=await this.contractBundles.loadBundle(f),h=await this.contractBundles.loadRouter(f);b.setDefinition(o.spec.contract.hyperdb);for(let e=0;e<t.length;e++){const n=t[e];if(!(a.decode(a.uint,n.value)<2))try{const e=y(n.value),t=E(b,n,s,i,h,{dryRun:r,trace:u,abi:this.abi});await h.dispatch(e,t),e&&"@keet/upgrade"===e.name&&(f=e.value.version,f<this.abi.min&&(f=this.abi.min),o=await this.contractBundles.loadBundle(f),h=await this.contractBundles.loadRouter(f),b.setDefinition(o.spec.contract.hyperdb))}catch(e){if(g.isContractError(e))throw e;if(p.isDispatchError(e))throw g.DISPATCH_ERROR(e.message);throw e}}}catch(e){"BUNDLE_NOT_AVAILABLE"===e.code?s.interrupt({unsupportedABI:e.abi,digest:null,nodes:t}):g.isContractError(e)?(m=!0,e.abi=f,e.roomKey=o,r&&!this.isTesting||this.onerror(e)):w=e}const _=!w&&!m&&b.updated();if(r||w||m?await Promise.all([b.close()]):await Promise.all([b.flush()]),w)throw w;return _}async logs(){const t=this.auto,i=t.system.bee.head(),s=t.system.view,r=t._workingBee.head(),n=await t.replay(),o=await Promise.all(n.map(async function(e){if(null===e)return{unavailable:!0};const i=await t._getOplog(e.key,e.length,{wait:!1});if(null===i)return{key:e.key,length:e.length,weight:e.weight,op:null};const{key:s,length:r,op:a}=i;return{key:s,length:r,weight:e.weight,op:w(a)}})),l=Date.now(),h={logFile:`/tmp/autobee-${e.encode(t.key)}-logs-${l}`},c=new b(h),g={system:i,view:s,viewHead:r,ops:o},{ops:d,...p}=function t(i){return Buffer.isBuffer(i)&&32===i.length?e.encode(i):Buffer.isBuffer(i)&&32!==i.length?i.toString("hex"):Array.isArray(i)?i.map(t):i&&"object"==typeof i?Object.fromEntries(Object.entries(i).map(([e,i])=>[e,t(i)])):i}(g);c.info(p);for(const e of d)e.unavailable||null===e.op?c.info(e):c.info({...e,op:{...e.op,value:JSON.stringify(e.op.value)}});function w(e){if(!e.value||!e.value.byteLength)return null;try{return y(e.value)}catch{}try{return a.decode(u.Messages.KeetOplogMessage,e.value)}catch(t){return{type:"undecodable",value:{error:t.message,raw:e.value}}}}}},module.exports.USER_DATA_BLOBS=f,module.exports.getBootOption=function(e,t){if(e&&e.length)return{head:e};const i=e?e.key:t;return i?{legacy:{key:i,length:0}}:null};const v={interrupt:_,addWriter:_,removeWriter:_,ackWriter:_,preferFastForward:_,removeable:function(){return!0},createAnchor:(e,t)=>({key:e,length:t})};function E(e,t,i,s,r,{abi:a,trace:n,dryRun:o=!1}={}){return new h.OpContext(e,{fork:s.core.fork,writer:{key:t.from?.key||t.key,seq:t.length-1,version:t.version},bootstrap:i.bootstrap,router:r,hostcalls:i,dryRun:o,abi:a,trace:n})}class V{constructor(e){this.auto=e,this.value=[],this.closed=!1}append(e){return this.value.push(e),this.auto.local.length+this.value.length}async flush(){if(this.closed)throw new Error("Batch is closed");const e=this.value;this.value=[],await this.auto.append(e)}close(){this.value=[],this.closed=!0}}function L(e,i,r,a){const n=t.getViewEncryption(i,a,h.BLOB_VIEW_NAME);return o=new s(e.get({key:r,encryption:n,writable:!1})),new Proxy(o,{get(e,t){return"put"===t?B:Reflect.get(...arguments)}});var o}function B(){return{start:0,length:0}}async function A(e){const t=await e.db.findOne("@keet/admins-by-most-recent",{reverse:!0});return t?{key:t.deviceKey,length:t.deviceLength}:null}function R(e){return{core:K(e.core),local:K(e.context&&e.context.local)}}function K(e){return e?{id:e.id,writable:e.writable,length:e.length,remoteContiguousLength:e.remoteContiguousLength,byteLength:e.byteLength,peers:e.peers.length}:{id:null,writable:!1,length:0,remoteContiguousLength:0,byteLength:0,peers:0}}