Skip to content

Commit

Permalink
Merge pull request #89 from ardriveapp/PE-726_excise_arfs_tag_from_da…
Browse files Browse the repository at this point in the history
…ta_tx

PE-726: Excise ArFS tag from data transactions
  • Loading branch information
fedellen authored Nov 24, 2021
2 parents 0c5a504 + 58e1c9f commit 6a22ded
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 20 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ardrive-core-js",
"version": "1.0.3",
"version": "1.0.4",
"description": "ArDrive Core contains the essential back end application features to support the ArDrive CLI and Desktop apps, such as file management, Permaweb upload/download, wallet management and other common functions.",
"main": "./lib/exports.js",
"types": "./lib/exports.d.ts",
Expand Down
91 changes: 91 additions & 0 deletions src/arfs/arfsdao.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import Arweave from 'arweave';
import { stubEntityID } from '../../tests/stubs';
import { ByteCount, FeeMultiple, stubTransactionID, UnixTime, W } from '../types';
import { readJWKFile } from '../utils/common';
import { ArFSDAO } from './arfsdao';
import { ArFSPublicFileMetaDataPrototype } from './arfs_prototypes';
import { ArFSPublicFileMetadataTransactionData } from './arfs_trx_data_types';
import { expect } from 'chai';
import { Tag } from 'arweave/node/lib/transaction';

describe('The ArFSDAO class', () => {
const wallet = readJWKFile('./test_wallet.json');
const fakeArweave = Arweave.init({
host: 'localhost',
port: 443,
protocol: 'https',
timeout: 600000
});

const arfsDao = new ArFSDAO(wallet, fakeArweave, true, 'ArFSDAO-Test', '1.0');

const stubFileMetaDataTrx = new ArFSPublicFileMetaDataPrototype(
new ArFSPublicFileMetadataTransactionData(
'Test Metadata',
new ByteCount(10),
new UnixTime(123456789),
stubTransactionID,
'text/plain'
),
stubEntityID,
stubEntityID,
stubEntityID
);

describe('prepareObjectTransaction function', () => {
// Helper function to grab the decoded gql tags off of a Transaction
const getDecodedTagName = (tag: Tag) => tag.get('name', { decode: true, string: true });

it('includes the base ArFS tags by default', async () => {
const transaction = await arfsDao.prepareArFSObjectTransaction({
objectMetaData: stubFileMetaDataTrx,
rewardSettings: { reward: W(10) }
});
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'ArFS')).to.exist;
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'App-Name')).to.exist;
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'App-Version')).to.exist;
expect(transaction.tags.length).to.equal(9);
});

it('includes the boost tag when boosted', async () => {
const transaction = await arfsDao.prepareArFSObjectTransaction({
objectMetaData: stubFileMetaDataTrx,
rewardSettings: { reward: W(10), feeMultiple: new FeeMultiple(1.5) }
});
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'Boost')).to.exist;
expect(transaction.tags.length).to.equal(10);
});

it('excludes the boost tag when boosted and boost tag is excluded', async () => {
const transaction = await arfsDao.prepareArFSObjectTransaction({
objectMetaData: stubFileMetaDataTrx,
rewardSettings: { reward: W(10), feeMultiple: new FeeMultiple(1.5) },
excludedTagNames: ['Boost']
});
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'Boost')).to.be.undefined;
expect(transaction.tags.length).to.equal(9);
});

it('excludes ArFS tag if its within the exclusion array', async () => {
const transaction = await arfsDao.prepareArFSObjectTransaction({
objectMetaData: stubFileMetaDataTrx,
rewardSettings: { reward: W(10) },
excludedTagNames: ['ArFS']
});
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'ArFS')).to.be.undefined;
expect(transaction.tags.length).to.equal(8);
});

it('can exclude multiple tags if provided within the exclusion array', async () => {
const transaction = await arfsDao.prepareArFSObjectTransaction({
objectMetaData: stubFileMetaDataTrx,
rewardSettings: { reward: W(10) },
excludedTagNames: ['ArFS', 'App-Version', 'App-Name']
});
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'ArFS')).to.be.undefined;
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'App-Name')).to.be.undefined;
expect(transaction.tags.find((tag) => getDecodedTagName(tag) === 'App-Version')).to.be.undefined;
expect(transaction.tags.length).to.equal(6);
});
});
});
67 changes: 48 additions & 19 deletions src/arfs/arfsdao.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ export class PrivateDriveKeyData {
}
}

export interface PrepareObjectTransactionParams {
objectMetaData: ArFSObjectMetadataPrototype;
rewardSettings?: RewardSettings;
excludedTagNames?: string[];
otherTags?: GQLTagInterface[];
}

export interface ArFSMoveParams<O extends ArFSFileOrFolderEntity, T extends ArFSObjectTransactionData> {
originalMetaData: O;
newParentFolderId: FolderID;
Expand Down Expand Up @@ -208,7 +215,7 @@ export class ArFSDAO extends ArFSDAOAnonymous {

// Create a root folder metadata transaction
const folderMetadata = folderPrototypeFactory(folderId, parentFolderId);
const folderTrx = await this.prepareArFSObjectTransaction(folderMetadata, rewardSettings);
const folderTrx = await this.prepareArFSObjectTransaction({ objectMetaData: folderMetadata, rewardSettings });

// Execute the upload
if (!this.dryRun) {
Expand Down Expand Up @@ -275,7 +282,10 @@ export class ArFSDAO extends ArFSDAOAnonymous {

// Create a drive metadata transaction
const driveMetaData = await createMetadataFn(driveId, rootFolderId);
const driveTrx = await this.prepareArFSObjectTransaction(driveMetaData, driveRewardSettings);
const driveTrx = await this.prepareArFSObjectTransaction({
objectMetaData: driveMetaData,
rewardSettings: driveRewardSettings
});

// Execute the upload
if (!this.dryRun) {
Expand Down Expand Up @@ -369,7 +379,10 @@ export class ArFSDAO extends ArFSDAOAnonymous {
const metadataPrototype = metaDataFactory();

// Prepare meta data transaction
const metaDataTrx = await this.prepareArFSObjectTransaction(metadataPrototype, metaDataBaseReward);
const metaDataTrx = await this.prepareArFSObjectTransaction({
objectMetaData: metadataPrototype,
rewardSettings: metaDataBaseReward
});

// Upload meta data
if (!this.dryRun) {
Expand Down Expand Up @@ -493,7 +506,11 @@ export class ArFSDAO extends ArFSDAOAnonymous {

// Build file data transaction
const fileDataPrototype = await dataPrototypeFactoryFn(fileData, dataContentType, fileId);
const dataTrx = await this.prepareArFSObjectTransaction(fileDataPrototype, fileDataRewardSettings);
const dataTrx = await this.prepareArFSObjectTransaction({
objectMetaData: fileDataPrototype,
rewardSettings: fileDataRewardSettings,
excludedTagNames: ['ArFS']
});

// Upload file data
if (!this.dryRun) {
Expand All @@ -513,7 +530,10 @@ export class ArFSDAO extends ArFSDAOAnonymous {
fileId
);
const fileMetadata = metadataFactoryFn(metadataTrxData, fileId);
const metaDataTrx = await this.prepareArFSObjectTransaction(fileMetadata, metadataRewardSettings);
const metaDataTrx = await this.prepareArFSObjectTransaction({
objectMetaData: fileMetadata,
rewardSettings: metadataRewardSettings
});

// Upload meta data
if (!this.dryRun) {
Expand Down Expand Up @@ -612,11 +632,12 @@ export class ArFSDAO extends ArFSDAOAnonymous {
);
}

async prepareArFSObjectTransaction(
objectMetaData: ArFSObjectMetadataPrototype,
rewardSettings: RewardSettings = {},
otherTags: GQLTagInterface[] = []
): Promise<Transaction> {
async prepareArFSObjectTransaction({
objectMetaData,
rewardSettings = {},
excludedTagNames = [],
otherTags = []
}: PrepareObjectTransactionParams): Promise<Transaction> {
const wallet = this.wallet as JWKWallet;

// Create transaction
Expand All @@ -641,23 +662,31 @@ export class ArFSDAO extends ArFSDAOAnonymous {
transaction.reward = rewardSettings.feeMultiple.boostReward(transaction.reward);
}

// Add baseline ArFS Tags
transaction.addTag('App-Name', this.appName);
transaction.addTag('App-Version', this.appVersion);
transaction.addTag('ArFS', CURRENT_ARFS_VERSION);
let tagsToAdd: GQLTagInterface[] = [
// Add baseline App Name and App Version Tags
{ name: 'App-Name', value: this.appName },
{ name: 'App-Version', value: this.appVersion },
{ name: 'ArFS', value: CURRENT_ARFS_VERSION }
];

if (rewardSettings.feeMultiple?.wouldBoostReward()) {
transaction.addTag('Boost', rewardSettings.feeMultiple.toString());
tagsToAdd.push({ name: 'Boost', value: rewardSettings.feeMultiple.toString() });
}

// Add object-specific tags
objectMetaData.addTagsToTransaction(transaction);

// Enforce that other tags are not protected
objectMetaData.assertProtectedTags(otherTags);
otherTags.forEach((tag) => {
tagsToAdd.push(...otherTags);

// Remove any excluded tags
tagsToAdd = tagsToAdd.filter((tag) => !excludedTagNames.includes(tag.name));

tagsToAdd.forEach((tag) => {
transaction.addTag(tag.name, tag.value);
});

// Add object-specific tags
objectMetaData.addTagsToTransaction(transaction);

// Sign the transaction
await this.arweave.transactions.sign(transaction, wallet.getPrivateKey());
return transaction;
Expand Down

0 comments on commit 6a22ded

Please sign in to comment.