mining FOMO

This commit is contained in:
jeka 2024-10-04 17:54:34 +03:00
parent 6a57def079
commit 9a4699ff76
6 changed files with 450 additions and 65 deletions

View File

@ -1,7 +1,7 @@
## CLI Miner for META PoW on the Sui Blockchain
## CLI Miner for META and FOMO PoW on the Sui Blockchain
- [Meta](https://github.com/suidouble/sui_meta) is the PoW coin on the Sui blockchain
- [Web Miner](https://suimine.xyz/)
- [Web Miner](https://suimine.xyz/) for the FOMO token
- [Follow me on X](https://x.com/suidouble)
- CLI Miner:
@ -11,7 +11,22 @@ CLI miner expects you to have node.js installed of version >= 18 [node.js](https
git clone https://github.com/suidouble/sui_meta_miner.git
cd sui_meta_miner
npm install
node mine.js --chain=mainnet --phrase="secretphrase"
```
#### Run it
Miner supports both META and FOMO coins:
```
node mine.js --meta --chain=mainnet --phrase="secretphrase"
node mine.js --fomo --chain=mainnet --phrase="secretphrase"
```
or you can run it in the boss mode, mining both:
```
node mine.js --fomo --meta --chain=mainnet --phrase="secretphrase"
```
Where secretphrase is 24 words secret phrase for your wallet private key or private key in the format of "suiprivkey1....." ( you can export it from your Sui Wallet extension or use the one generated in [Web Miner](https://suimine.xyz/) )

View File

@ -17,6 +17,21 @@ const settings = {
"packageId": "0x3c680197c3d3c3437f78a962f4be294596c5ebea6cea6764284319d5e832e8e4",
"blockStoreId": "0x57035b093ecefd396cd4ccf1a4bf685622b58353566b2f29b271afded2cb4390",
"treasuryId": "0xeefe819f6f1b219f9460bfc78bfbac6568e2aec78cf808d2005ff2367e1de528",
"fomo": {
"packageId": '0xa340e3db1332c21f20f5c08bef0fa459e733575f9a7e2f5faca64f72cd5a54f2',
"configId": '0x684de611aafbc5fbb6aa4288b4050281219dd5efc5764de22ba0f30b2bb2dd15',
"buses": [
'0x1aa2497f14d27b2d7c2ebd9dd607cda0279dc4d54a57e3a8476a1236474b6567',
'0x367fdbde371d96018a572e354a6af4f74b69f412c0817eabff52bb166da6df0c',
'0x5e42608bc32e6ff4bd024ce3928832ebfa27efe557ba4763084222226e3413c0',
'0x98b9610b1b61cb02cae304d8b8a4fbd4e5cb018d24adf59f728ff886b597e9dc',
'0xaae095f1fd39d20418efd6ded79303fe8b53751907a17d2723594480655785f0',
'0xc941f30b0714306d256a8efa56d9b1fb9e63ebd94de14d6a4b5d9c52ece98bc5',
'0xcc18677841febd80ad34a4535c88ec402a968d24a824c00af29d1b5737e528d0',
'0xea11f05cb06456755a0b1b52bbd1b4a9e186ec89495f6ba02094ed84a075248a',
],
},
},
"testnet": {
"phrase": "coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin coin",
@ -32,4 +47,12 @@ if (argv.phrase) {
settings[selectedChain].phrase = argv.phrase;
}
settings[selectedChain].do = {meta: true};
if (argv.fomo) {
settings[selectedChain].do.fomo = true;
if (!argv.meta) {
settings[selectedChain].do.meta = false;
}
}
export default settings[selectedChain];

View File

@ -19,7 +19,7 @@ export default class Miner {
this._treasury = null;
this._movePackage = null;
this._nonceFinder = new NonceFinder();
this._nonceFinder = new NonceFinder({ name: 'META' });
}
async checkObjects() {
@ -71,39 +71,6 @@ export default class Miner {
return balance;
}
async initGenesis() {
await this.checkObjects();
const tx = new suidouble.Transaction();
const clockId = '0x0000000000000000000000000000000000000000000000000000000000000006';
tx.moveCall({
target: `${this._packageId}::mining::init_genesis`,
arguments: [
tx.object(clockId),
tx.object(this._blockStore.id),
],
// typeArguments: [`${testScenario._packages.admin.address}::mining::BlockInfo`],
});
const r = await this._suiMaster.signAndExecuteTransaction({
transaction: tx,
requestType: 'WaitForLocalExecution',
sender: this._suiMaster.address,
options: {
"showEffects": true, // @todo: remove?
"showEvents": true, // @todo: remove?
"showObjectChanges": true,
showType: true,
showContent: true,
showOwner: true,
showDisplay: true,
},
});
return (r?.effects?.status?.status == 'success');
}
async mine(startNonce = 0, meta = null, payload = null) {
await this.checkObjects();
@ -126,12 +93,13 @@ export default class Miner {
const __checkForOutdatedInterval = setInterval(()=>{
this.hasBlockInfoChanged(blockInfo)
.then((changed)=>{
console.log('META | block hash changed', changed);
if (changed) {
isOutdated = true;
this._nonceFinder.pleaseStop();
}
});
}, 1000);
}, 3000);
while (!foundValid && !isOutdated) {
// nonce = await this.findValidNonce(preparedHash, blockInfo.difficulty, nonce);
@ -139,12 +107,12 @@ export default class Miner {
nonce = await this._nonceFinder.findValidNonce(preparedHash, blockInfo.target);
if (nonce !== null) {
console.log('valid nonce '+nonce+' found in '+((new Date()).getTime() - startFindingNonceAt)+'ms');
console.log('META | valid nonce '+nonce+' found in '+((new Date()).getTime() - startFindingNonceAt)+'ms');
const success = await this.submit(nonce, meta, payload);
if (success) {
foundValid = true;
} else {
console.log('blockInfo was wrong!!!');
console.log('META | blockInfo was wrong!!!');
nonce = nonce + 1;
blockInfo = await this.getBlockInfo(); // maybe we have wrong block info?
preparedHash = this.prepareHash(blockInfo, meta, payload);
@ -206,13 +174,13 @@ export default class Miner {
});
if (r && r.effects && r.effects.status && r.effects.status.status && r.effects.status.status == 'success') {
console.log('valid nonce submited');
console.log('META | valid nonce submited');
return true;
} else {
console.log('can not submit nonce');
console.log('META | can not submit nonce');
}
} catch (e) {
console.log('can not submit nonce');
console.log('META | can not submit nonce');
console.error(e);
}
@ -282,7 +250,7 @@ export default class Miner {
async hasBlockInfoChanged(blockInfo) {
const currentBlockInfo = await this.getBlockInfo();
console.log('Current previous_hash: '+currentBlockInfo.previousHashAsBigInt);
// console.log('Current previous_hash: '+currentBlockInfo.previousHashAsBigInt);
if (currentBlockInfo.previousHashAsBigInt != blockInfo.previousHashAsBigInt) {
return true;
}

View File

@ -10,6 +10,8 @@ export default class NonceFinder {
this._workersCount = 8;
this._askedToStop = false;
this._name = params.name;
}
pleaseStop() {
@ -57,7 +59,7 @@ export default class NonceFinder {
let i = 0;
for (const worker of this._workers) {
const tryNonce = this.getNextNonceToTry();
console.log('thread #'+i+' | trying nonce: ', tryNonce);
// console.log('thread #'+i+' | trying nonce: ', tryNonce);
const promise = new Promise((res,rej)=>{
worker.findSalt(preparedHash, maxTarget, tryNonce)
.then((foundNonce)=>{
@ -81,11 +83,11 @@ export default class NonceFinder {
} catch (e) {
foundGoodHash = false;
}
console.log('current rate is: '+this.hashesPerSecond()+' H/s');
console.log((this._name ? this._name : '') + ' | current rate is: '+this.hashesPerSecond()+' H/s');
} while (!foundGoodHash && !this._askedToStop);
if (this._askedToStop) { // we are stoping once
console.log('stoping finding nonce as we were asked to stop');
console.log((this._name ? this._name : '') + ' | goes to the next nonces block');
this._askedToStop = false;
}

343
includes/fomo/FomoMiner.js Normal file
View File

@ -0,0 +1,343 @@
import suidouble from 'suidouble';
import { bcs } from '@mysten/sui/bcs';
import hasher from 'js-sha3';
// import { spawn, Thread, Worker } from "threads";
import { bytesTou64, bigIntTo32Bytes, u64toBytes } from '../math.js';
import NonceFinder from '../NonceFinder.js';
export default class FomoMiner {
constructor(params = {}) {
this._suiMaster = params.suiMaster || null;
this._buses = params.buses || null;
this._configId = params.configId || null;
this._packageId = params.packageId || null;
// this._blockStore = null; // to be initialized as SuiObject in .checkObjects() initializer
// this._treasury = null;
// this._movePackage = null;
this._nonceFinder = new NonceFinder({ name: 'FOMO' });
this._config = null;
this._movePackage = null;
}
async checkObjects() {
if (this.__checkObjectsPromise) {
return await this.__checkObjectsPromise;
}
this.__checkObjectsPromiseResolver = null; // to be sure it's executed once async
this.__checkObjectsPromise = new Promise((res)=>{ this.__checkObjectsPromiseResolver = res; });
if (!this._configId || !this._packageId || !this._buses) {
throw new Error('FOMO | configId, packageId are required');
}
const SuiObject = suidouble.SuiObject;
const config = new SuiObject({
id: this._configId,
suiMaster: this._suiMaster,
});
// const treasury = new SuiObject({
// id: this._treasuryId,
// suiMaster: this._suiMaster,
// });
this._suiMaster.objectStorage.push(config);
// this._suiMaster.objectStorage.push(treasury);
await config.fetchFields(); // get fields from the blockchain
this._config = config;
// this._treasury = treasury;
const movePackage = this._suiMaster.addPackage({
id: this._packageId,
});
await movePackage.isOnChain(); // check the package on the blockchain
this._movePackage = movePackage;
this.__checkObjectsPromiseResolver(true); // initialized
return true;
}
async getOrCreateMiner() {
await this.checkObjects();
// check for owned objects
if (this._suiMaster._debug) {
console.log('FOMO | Trying to find the miner object already registered on the blockchain....');
}
const paginated = await this._movePackage.modules.miner.getOwnedObjects({ typeName: 'Miner' });
let miner = null;
await paginated.forEach((suiObject)=>{ miner = suiObject; });
if (miner) {
if (this._suiMaster._debug) {
console.log('FOMO | It is there, id is: ', miner.id);
}
return miner;
}
console.log('FOMO | Can not find it. Lets register the new one...');
await this._movePackage.modules.miner.moveCall('register', []);
console.log('FOMO | Miner succesfully registered');
await new Promise((res)=>{ setTimeout(res, 2000); });
return await this.getOrCreateMiner();
}
async fetchBus() {
await this.checkObjects();
const randomBusId = this._buses[Math.floor(Math.random() * this._buses.length)];
// console.log(randomBusId);
const bus = new (this._suiMaster.SuiObject)({ id: randomBusId, suiMaster: this._suiMaster });
await bus.fetchFields();
return bus;
}
async hasBlockInfoChanged(oldHash) {
const miner = await this.getOrCreateMiner();
const newHash = new Uint8Array(miner.fields.current_hash); // changed on the new block
// console.log('Current hash: '+newHash);
if (bytesTou64(oldHash) != bytesTou64(newHash)) {
return true;
}
return false;
}
async mine(startNonce = 0) {
await this.checkObjects();
let miner = await this.getOrCreateMiner();
let bus = await this.fetchBus();
const currentHash = new Uint8Array(miner.fields.current_hash); // changed on the new block
const signerAddressBytes = bcs.Address.serialize(this._suiMaster.address).toBytes();
const difficulty = Number(bus.fields.difficulty);
const difficultyAsTarget = '0x'+(''.padEnd(difficulty*2, '00').padEnd(64, 'ff'));
let foundValid = false;
let preparedHash = this.prepareHash(currentHash, signerAddressBytes);
let nonce = startNonce || 0;
const startFindingNonceAt = (new Date()).getTime();
let isOutdated = false;
const __checkForOutdatedInterval = setInterval(()=>{
this.hasBlockInfoChanged(currentHash)
.then((changed)=>{
console.log('FOMO | block hash changed', changed);
if (changed) {
isOutdated = true;
this._nonceFinder.pleaseStop();
}
});
}, 3000);
while (!foundValid && !isOutdated) {
nonce = await this._nonceFinder.findValidNonce(preparedHash, difficultyAsTarget);
if (nonce !== null) {
console.log('FOMO | valid nonce '+nonce+' found in '+((new Date()).getTime() - startFindingNonceAt)+'ms');
const success = await this.submit(nonce, bus, miner);
if (success) {
foundValid = true;
} else {
console.log('FOMO | blockInfo was wrong!!!');
nonce = nonce + 1;
miner = await this.getOrCreateMiner();
preparedHash = this.prepareHash(new Uint8Array(miner.fields.current_hash), signerAddressBytes);
}
} else {
// asked to stop
isOutdated = true;
}
};
clearInterval(__checkForOutdatedInterval);
return true;
}
async prepare() {
await this.checkObjects();
const miner = await this.getOrCreateMiner();
const currentHash = new Uint8Array(miner.fields.current_hash); // changed on the new block
let startNonce = BigInt(0);
const signerAddressBytes = bcs.Address.serialize(this._suiMaster.address).toBytes();
let bus = await this.fetchBus();
console.log(signerAddressBytes);
console.log(currentHash);
console.log(miner.fields.current_hash);
const difficulty = Number(bus.fields.difficulty);
console.log(difficulty);
const difficultyAsTarget = '0x'+(''.padEnd(difficulty*2, '00').padEnd(64, 'ff'));
console.log(difficultyAsTarget);
// const hash = this.createHash(currentHash, signerAddressBytes, startNonce);
// const hashIsValid = this.validateHash(hash, difficulty);
// console.log(hash);
// console.log(hashIsValid);
console.log(this.busIsOk(bus));
// let found = false;
// while(!found) {
// const hash = this.createHash(currentHash, signerAddressBytes, startNonce);
// const hashIsValid = this.validateHash(hash, difficulty);
// console.log(startNonce, hashIsValid);
// if (hashIsValid) {
// found = true;
// }
// startNonce = startNonce + 1n;
// }
const preparedHash = this.prepareHash(currentHash, signerAddressBytes);
let nonce = await this._nonceFinder.findValidNonce(preparedHash, difficultyAsTarget);
console.log(nonce);
if (nonce) {
this.submit(nonce, bus, miner);
}
}
busIsOk(bus) {
const epochLength = 60000;
const fundsOk = BigInt(bus.fields.rewards) >= BigInt(bus.fields.reward_rate);
const threshold = Number(bus.fields.last_reset) + epochLength;
const buffer = 4000;
const resetTimeOk = Date.now() < threshold - buffer;
return resetTimeOk && fundsOk;
}
async submit(nonce, bus, miner) {
// await this.checkObjects();
// if (!meta) {
// meta = new Uint8Array([]);
// }
// if (!payload) {
// payload = new Uint8Array([]);
// }
const tx = new suidouble.Transaction();
const args = [
tx.pure('u64', nonce),
tx.object(bus.id), // bus
tx.object(miner.id), // miner
tx.object('0x0000000000000000000000000000000000000000000000000000000000000006'), // clock
// tx.object(this._treasury.id),
// suidouble.txInput(tx, 'vector<u8>', Array.from(meta)),
// suidouble.txInput(tx, 'vector<u8>', Array.from(payload)),
// suidouble.txInput(tx, 'u64', nonce),
// tx.object(this._suiMaster.address),
];
const moveCallResult = tx.moveCall({
target: `${this._packageId}::fomo::mine`,
arguments: args
});
tx.transferObjects([moveCallResult], this._suiMaster.address);
try {
const r = await this._suiMaster.signAndExecuteTransaction({
transaction: tx,
requestType: 'WaitForLocalExecution',
sender: this._suiMaster.address,
options: {
"showEffects": true, // @todo: remove?
"showEvents": true, // @todo: remove?
"showObjectChanges": true,
showType: true,
showContent: true,
showOwner: true,
showDisplay: true,
},
});
if (r && r.effects && r.effects.status && r.effects.status.status && r.effects.status.status == 'success') {
console.log('FOMO | valid nonce submited');
return true;
} else {
console.log('FOMO | can not submit nonce');
}
} catch (e) {
console.log('FOMO | can not submit nonce');
console.error(e);
}
return false;
}
async waitUntilNextReset(currentReset) {
const epochLength = 60000;
const bus = await this.fetchBus();
const nextReset = Number(bus.fields.last_reset) + epochLength;
const timeUntilNextReset = nextReset - Date.now();
if (timeUntilNextReset > 0) {
await new Promise((res)=>setTimeout(res, timeUntilNextReset));
}
while (true) {
const freshBus = await this.fetchBus();
if (Number(freshBus.fields.last_reset) !== Number(currentReset)) {
return true;
} else {
if (Date.now() > nextReset + 12000) {
return false;
}
await new Promise((res)=>setTimeout(res, 1500));
}
}
}
prepareHash(currentHash, signerAddressBytes) {
const prepared = new Uint8Array(32 + 32); // nonce bytes would be empty
prepared.set(currentHash, 0);
prepared.set(signerAddressBytes, 32);
return prepared;
}
createHash(currentHash, signerAddressBytes, nonce) {
const dataToHash = new Uint8Array(32 + 32 + 8);
dataToHash.set(currentHash, 0);
dataToHash.set(signerAddressBytes, 32);
dataToHash.set(u64toBytes(nonce), 64);
return bigIntTo32Bytes(BigInt('0x'+hasher.keccak256(dataToHash)));
}
validateHash(hash, difficulty) {
return hash.slice(0, difficulty).reduce((a, b) => a + b, 0) === 0;
}
}

70
mine.js
View File

@ -1,14 +1,19 @@
import { SuiMaster } from 'suidouble';
import config from './config.js';
import Miner from './includes/Miner.js';
import FomoMiner from './includes/fomo/FomoMiner.js';
const run = async()=>{
const phrase = config.phrase;
const chain = config.chain;
if (!config.phrase || !config.chain) {
throw new Error('phrase and chain parameters are required');
}
const suiMasterParams = {
client: chain,
debug: true,
debug: !!config.debug,
};
if (phrase.indexOf('suiprivkey') === 0) {
suiMasterParams.privateKey = phrase;
@ -16,29 +21,58 @@ const run = async()=>{
suiMasterParams.phrase = phrase;
}
const suiMaster = new SuiMaster(suiMasterParams);
await suiMaster.initialize();
const miner = new Miner({
suiMaster,
packageId: config.packageId,
blockStoreId: config.blockStoreId,
treasuryId: config.treasuryId,
});
console.log('suiMaster connected as ', suiMaster.address);
let i = 0;
let balance = null;
const miners = {};
while (true) {
// await miner.printAdjustDifficultyEvents();
await miner.mine();
i = i + 1;
// balance = await miner.getBTCBalance();
// console.log('BTC balance: ', balance);
const doMine = async(minerInstance)=>{
while (true) {
await minerInstance.mine();
await new Promise((res)=>setTimeout(res, 100));
};
};
// await miner.printAdjustDifficultyEvents();
}
if (config.do.meta) {
const miner = new Miner({
suiMaster,
packageId: config.packageId,
blockStoreId: config.blockStoreId,
treasuryId: config.treasuryId,
});
miners.meta = miner;
doMine(miners.meta);
};
if (config.do.fomo) {
const fomoMiner = new FomoMiner({
suiMaster,
packageId: config.fomo.packageId,
configId: config.fomo.configId,
buses: config.fomo.buses,
});
miners.fomo = fomoMiner;
doMine(miners.fomo);
};
// // let i = 0;
// // let balance = null;
// while (true) {
// // await miner.printAdjustDifficultyEvents();
// await miner.mine();
// i = i + 1;
// // balance = await miner.getBTCBalance();
// // console.log('BTC balance: ', balance);
// // await miner.printAdjustDifficultyEvents();
// }
};
run()
.then(()=>{
console.error('done');
console.error('running');
});