Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(jellyfish-transaction): Add updatemasternode dftx #1863

Merged
merged 34 commits into from
Dec 1, 2022
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1673e35
add dftx (WIP)
marktanrj Nov 18, 2022
9fa64e0
Add mapping
imkven Nov 18, 2022
0ed853b
WIP
imkven Nov 19, 2022
30a9b3a
Resolve conflict
marktanrj Nov 20, 2022
5b8b3bc
Update image version
marktanrj Nov 21, 2022
e648101
Remove redundant code
marktanrj Nov 21, 2022
68674dd
Add transaction builder tests (WIP)
marktanrj Nov 21, 2022
67d6e5e
Fix format
marktanrj Nov 21, 2022
642b411
Fix format bug
marktanrj Nov 21, 2022
e4a9e87
Add tests for updating operator and reward address
marktanrj Nov 21, 2022
7810449
Add tests
marktanrj Nov 22, 2022
c7f541f
Update test and dftx for removing reward address
marktanrj Nov 22, 2022
da84f9c
Minor edit
marktanrj Nov 22, 2022
02e0cf4
Add tests for transaction builder
marktanrj Nov 22, 2022
8b9bc7a
Fix mistakes in comments
marktanrj Nov 22, 2022
860c373
Fix buggy code for UpdateMasternodeData
marktanrj Nov 22, 2022
b5c7fd7
Minor changes
marktanrj Nov 22, 2022
4fd412f
Add jellyfish transaction tests
marktanrj Nov 22, 2022
8c3d7b6
Merge main
marktanrj Nov 22, 2022
bc161a9
Update tests
marktanrj Nov 24, 2022
11c9e05
Add updatemasternode rpc
marktanrj Nov 25, 2022
08c0420
Attempt to fix update owner address in masternode test (WIP)
marktanrj Nov 25, 2022
e0de0f4
Update
marktanrj Nov 29, 2022
ffbfd9d
Fix update owner address test
marktanrj Nov 29, 2022
33fe966
Update tests
marktanrj Nov 30, 2022
0e59f91
Merge main
marktanrj Nov 30, 2022
75cfbb9
Update tests to use object assertion
marktanrj Nov 30, 2022
b55852e
Minor refactor and fix for test
marktanrj Nov 30, 2022
8d2f170
Add tests
marktanrj Nov 30, 2022
d130008
Add test for createDeFiTx
marktanrj Nov 30, 2022
444d86e
Update vout utxo amounts to be more deterministic
marktanrj Dec 1, 2022
0574935
Resolve PR comments
marktanrj Dec 1, 2022
71da6aa
Remove transaction builder code and shift into separate branch
marktanrj Dec 1, 2022
d4d7736
Merge branch 'main' into marktanrj/updatemasternode-dftx
marktanrj Dec 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import BigNumber from 'bignumber.js'
import { MasterNodeRegTestContainer } from '@defichain/testcontainers'
import { getProviders, MockProviders } from '../provider.mock'
import { P2WPKHTxnBuilder } from '../../src'
import { P2WPKHTxnBuilder, Prevout } from '../../src'
import { fundEllipticPair } from '../test.utils'
import { CDfTx, DeFiOpUnmapped, OP_CODES, OP_DEFI_TX } from '@defichain/jellyfish-transaction'
import { CDfTx, DeFiOpUnmapped, OP_CODES, OP_DEFI_TX, Vin, Vout } from '@defichain/jellyfish-transaction'
import { RegTest } from '@defichain/jellyfish-network'

// P2WPKHTxnBuilder is abstract and not instantiable
Expand Down Expand Up @@ -92,4 +92,46 @@ describe('createDeFiTx()', () => {
expect(result.vout[1].value.gt(new BigNumber(99.999).minus(spendAmount))).toBeTruthy()
expect(result.vout[1].value.lt(new BigNumber(100).minus(spendAmount))).toBeTruthy()
})

it('should create DfTx stack correctly with custom vin and vout', async () => {
const script = await providers.elliptic.script()

const collateralPrevout: Prevout = {
txid: '1111111122222222333333334444444455555555666666667777777788888888',
vout: 0,
script: script,
value: new BigNumber(15),
tokenId: 0
}
const collateralVout: Vout = {
script: script,
value: new BigNumber(15),
tokenId: 0
}
const collateralVin: Vin = {
txid: '1111111122222222333333334444444455555555666666667777777788888888',
index: 1,
script: { stack: [] },
sequence: 0xffffffff
}
const customVinVout = {
prevout: collateralPrevout,
vin: collateralVin,
vout: collateralVout
}

const result = await builder.createDeFiTx(dummyDfTx, script, undefined, [customVinVout])

expect(result.vin.length).toStrictEqual(8)
expect(result.vout.length).toStrictEqual(3)

expect(result.vout[0].value).toStrictEqual(new BigNumber(0))
expect(result.vout[1]).toStrictEqual(expect.objectContaining({
value: new BigNumber(15),
script: script
}))
expect(result.vout[2].value.gt(99.999)).toBeTruthy()
expect(result.vout[2].value.lt(100)).toBeTruthy()
expect(result.vout[2].script).toStrictEqual(script)
})
})
16 changes: 12 additions & 4 deletions packages/jellyfish-transaction-builder/src/txn/txn_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ export abstract class P2WPKHTxnBuilder {
* Craft a transaction with OP_DEFI_TX from the output of OP_CODES.OP_DEFI_TX_.
* This is a helper method for creating custom defi transactions.
*
* Optionally, allow for custom inputs with vin and vout.
*
* As DeFi custom transaction will always require small amount of DFI,
* collectPrevouts() is set to search for at least 0.001 DFI amount of prevout.
* This will also evidently merge small prevout during the operation.
Expand All @@ -98,15 +100,21 @@ export abstract class P2WPKHTxnBuilder {
* @param {OP_DEFI_TX} opDeFiTx to create
* @param {Script} changeScript to send unspent to after deducting the fees
* @param {BigNumber} [outValue=0] for the opDeFiTx, usually always be 0.
* @param {Array<{ vin: Vin, vout: Vout, prevout: Prevout }>} [customVinVout = []] for custom vin and vout
*/
async createDeFiTx (
opDeFiTx: OP_DEFI_TX,
changeScript: Script,
outValue: BigNumber = new BigNumber('0')
outValue: BigNumber = new BigNumber('0'),
customVinVout: Array<{ vin: Vin, vout: Vout, prevout: Prevout }> = []
marktanrj marked this conversation as resolved.
Show resolved Hide resolved
): Promise<TransactionSegWit> {
const minFee = outValue.plus(0.001) // see JSDoc above
const { prevouts, vin, total } = await this.collectPrevouts(minFee)

const customVins = customVinVout.map(({ vin }) => vin)
const customPrevouts = customVinVout.map(({ prevout }) => prevout)
const customVouts = customVinVout.map(({ vout }) => vout)

const deFiOut: Vout = {
value: outValue,
script: {
Expand All @@ -123,15 +131,15 @@ export abstract class P2WPKHTxnBuilder {

const txn: Transaction = {
version: DeFiTransactionConstants.Version,
vin: vin,
vout: [deFiOut, change],
vin: [...vin, ...customVins],
vout: [deFiOut, ...customVouts, change],
lockTime: 0x00000000
}

const fee = await this.calculateFee(txn)
change.value = total.minus(outValue).minus(fee)

return await this.sign(txn, prevouts)
return await this.sign(txn, [...prevouts, ...customPrevouts])
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import {
OP_CODES,
ResignMasternode,
Script,
TransactionSegWit
TransactionSegWit,
UpdateMasternode,
Vin,
Vout
} from '@defichain/jellyfish-transaction'
import { Prevout } from '..'
import { P2WPKHTxnBuilder } from './txn_builder'

export class TxnBuilderMasternode extends P2WPKHTxnBuilder {
Expand Down Expand Up @@ -41,4 +45,25 @@ export class TxnBuilderMasternode extends P2WPKHTxnBuilder {
changeScript
)
}

/**
* Build update masternode transaction
*
* @param {UpdateMasternode} updateMasternode transaction to create
* @param {Script} changeScript to send unspent to after deducting the (converted + fees)
* @param {Array<{ vin: Vin, vout: Vout, prevout: Prevout }>} [customVinVout = []] for custom vin and vout when updating owner address
* @return {Promise<TransactionSegWit>}
marktanrj marked this conversation as resolved.
Show resolved Hide resolved
*/
async update (
updateMasternode: UpdateMasternode,
changeScript: Script,
customVinVout?: Array<{ vin: Vin, vout: Vout, prevout: Prevout }>
): Promise<TransactionSegWit> {
return await this.createDeFiTx(
OP_CODES.OP_DEFI_TX_UPDATE_MASTER_NODE(updateMasternode),
changeScript,
undefined,
customVinVout
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { SmartBuffer } from 'smart-buffer'
import { OP_DEFI_TX } from '../../../../src/script/dftx'
import { OP_CODES } from '../../../../src'
import { toBuffer, toOPCodes } from '../../../../src/script/_buffer'
import { CUpdateMasternode, UpdateMasternode } from '../../../../src/script/dftx/dftx_masternode'

it('should bi-directional buffer-object-buffer', () => {
const fixtures = [
// update owner address with p2wpkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010104141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update operator address with p2wpkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010204141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update reward address with p2wpkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010304141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update owner, operator and reward addresses with p2wpkh address
'6a4c6b446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd030104141db4d4d5e7d01091545e5573494b2fd1e91215d902041476e76cdb4de312861ce893f307e7e2bd40a8ac2d030414b188e161a83571c5acd9dd40b316796a47c606d1',
// update owner address with p2pkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010101141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update operator address with p2pkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010201141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update reward address with p2pkh address
'6a3d446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd010301141db4d4d5e7d01091545e5573494b2fd1e91215d9',
// update owner, operator and reward addresses with p2pkh address
'6a4c6b446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd030101141db4d4d5e7d01091545e5573494b2fd1e91215d902011476e76cdb4de312861ce893f307e7e2bd40a8ac2d030114b188e161a83571c5acd9dd40b316796a47c606d1',
// remove reward address
'6a29446654786d2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd01040000'
]

fixtures.forEach(hex => {
const stack = toOPCodes(
SmartBuffer.fromBuffer(Buffer.from(hex, 'hex'))
)
const buffer = toBuffer(stack)
expect(buffer.toString('hex')).toStrictEqual(hex)
expect((stack[1] as OP_DEFI_TX).tx.type).toStrictEqual(0x6d)
})
})

describe('UpdateMasternode', () => {
const header = '6a4c54446654786d' // OP_RETURN(0x6a) OP_PUSHDATA1 (length 84 = 0x54) CDfTx.SIGNATURE(0x44665478) CUpdateMasternode.OP_CODE(0x6d)

const data = '2319bc7b76b6916d0b61ee7a408d47d33380be746d11a5702bd87bbee5f8d3bd020104141db4d4d5e7d01091545e5573494b2fd1e91215d902041476e76cdb4de312861ce893f307e7e2bd40a8ac2d'
// UpdateMasternode.nodeId [LE] (bdd3f8e5be7bd82b70a5116d74be8033d3478d407aee610b6d91b6767bbc1923)
// UpdateMasternode.updates [array length] (0x02)
// UpdateMasternodeData.updateType [OwnerAddress] (0x01)
// UpdateMasternodeAddress.addressType [p2wpkh] (0x04)
// UpdateMasternodeAddress.addressPubKeyHash (length 20 = 0x14) (1db4d4d5e7d01091545e5573494b2fd1e91215d9)
// UpdateMasternodeData.updateType [OperatorAddress] (0x02)
// UpdateMasternodeAddress.addressType [p2wpkh] (0x04)
// UpdateMasternodeAddress.addressPubKeyHash (length 20 = 0x14) (76e76cdb4de312861ce893f307e7e2bd40a8ac2d)
marktanrj marked this conversation as resolved.
Show resolved Hide resolved

const updateMasternode: UpdateMasternode = {
nodeId: 'bdd3f8e5be7bd82b70a5116d74be8033d3478d407aee610b6d91b6767bbc1923',
updates: [
{
updateType: 0x01,
address: { addressType: 0x04, addressPubKeyHash: '1db4d4d5e7d01091545e5573494b2fd1e91215d9' }
},
{
updateType: 0x02,
address: { addressType: 0x04, addressPubKeyHash: '76e76cdb4de312861ce893f307e7e2bd40a8ac2d' }
}
]
}

it('should craft dftx with OP_CODES._()', () => {
const stack = [
OP_CODES.OP_RETURN,
OP_CODES.OP_DEFI_TX_UPDATE_MASTER_NODE(updateMasternode)
]

const buffer = toBuffer(stack)
expect(buffer.toString('hex')).toStrictEqual(header + data)
})

describe('Composable', () => {
it('should compose from buffer to composable', () => {
const buffer = SmartBuffer.fromBuffer(Buffer.from(data, 'hex'))
const composable = new CUpdateMasternode(buffer)
expect(composable.toObject()).toStrictEqual(updateMasternode)
})

it('should compose from composable to buffer', () => {
const composable = new CUpdateMasternode(updateMasternode)
const buffer = new SmartBuffer()
composable.toBuffer(buffer)

expect(buffer.toBuffer().toString('hex')).toStrictEqual(data)
})
})
})
11 changes: 10 additions & 1 deletion packages/jellyfish-transaction/src/script/dftx/dftx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,14 @@ import {
CUtxosToAccount,
CSetFutureSwap
} from './dftx_account'
import { CCreateMasternode, CreateMasternode, CResignMasternode, ResignMasternode } from './dftx_masternode'
import {
CCreateMasternode,
CreateMasternode,
CResignMasternode,
ResignMasternode,
CUpdateMasternode,
UpdateMasternode
} from './dftx_masternode'
import { CAutoAuthPrep } from './dftx_misc'
import {
CPoolAddLiquidity,
Expand Down Expand Up @@ -225,6 +232,8 @@ export class CDfTx extends ComposableBuffer<DfTx<any>> {
return compose(CAutoAuthPrep.OP_NAME, () => new CAutoAuthPrep())
case CCreateMasternode.OP_CODE:
return compose<CreateMasternode>(CCreateMasternode.OP_NAME, d => new CCreateMasternode(d))
case CUpdateMasternode.OP_CODE:
return compose<UpdateMasternode>(CUpdateMasternode.OP_NAME, d => new CUpdateMasternode(d))
case CResignMasternode.OP_CODE:
return compose<ResignMasternode>(CResignMasternode.OP_NAME, d => new CResignMasternode(d))
case CSetGovernance.OP_CODE:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,63 @@ export class CResignMasternode extends ComposableBuffer<ResignMasternode> {
]
}
}

export interface UpdateMasternodeAddress {
addressType: number // --------------------------| 1 byte, 0x01 = p2pkh, 0x04 = p2wpkh, 0x00 to remove reward address
addressPubKeyHash?: string // -------------------| VarUInt{20 bytes}, set undefined to remove reward address
}

/**
* Composable UpdateMasternodeAddress, C stands for Composable.
* Immutable by design, bi-directional fromBuffer, toBuffer deep composer.
*/
export class CUpdateMasternodeAddress extends ComposableBuffer<UpdateMasternodeAddress> {
composers (umn: UpdateMasternodeAddress): BufferComposer[] {
return [
ComposableBuffer.uInt8(() => umn.addressType, v => umn.addressType = v),
ComposableBuffer.compactSizeOptionalHex(() => umn.addressPubKeyHash, v => umn.addressPubKeyHash = v)
]
}
}

interface UpdateMasternodeData {
updateType: number // ----| 1 byte, 0x01 = OwnerAddress, 0x02 = OperatorAddress, 0x03 = SetRewardAddress, 0x04 = RemRewardAddress
address: UpdateMasternodeAddress
}

/**
* Composable UpdateMasternodeData, C stands for Composable.
* Immutable by design, bi-directional fromBuffer, toBuffer deep composer.
*/
export class CUpdateMasternodeData extends ComposableBuffer<UpdateMasternodeData> {
composers (umn: UpdateMasternodeData): BufferComposer[] {
return [
ComposableBuffer.uInt8(() => umn.updateType, v => umn.updateType = v),
ComposableBuffer.single<UpdateMasternodeAddress>(() => umn.address, v => umn.address = v, v => new CUpdateMasternodeAddress(v))
]
}
}

/**
* UpdateMasternode DeFi Transaction
*/
export interface UpdateMasternode {
nodeId: string // --------------------------------| VarUInt{32 bytes}
updates: UpdateMasternodeData[]
}

/**
* Composable UpdateMasternode, C stands for Composable.
* Immutable by design, bi-directional fromBuffer, toBuffer deep composer.
*/
export class CUpdateMasternode extends ComposableBuffer<UpdateMasternode> {
static OP_CODE = 0x6d // 'm'
static OP_NAME = 'OP_DEFI_TX_UPDATE_MASTER_NODE'

composers (umn: UpdateMasternode): BufferComposer[] {
return [
ComposableBuffer.hexBEBufferLE(32, () => umn.nodeId, v => umn.nodeId = v),
ComposableBuffer.compactSizeArray(() => umn.updates, v => umn.updates = v, v => new CUpdateMasternodeData(v))
]
}
}
17 changes: 16 additions & 1 deletion packages/jellyfish-transaction/src/script/mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,14 @@ import {
CICXClaimDFCHTLC,
ICXClaimDFCHTLC
} from './dftx/dftx_icxorderbook'
import { CCreateMasternode, CreateMasternode, CResignMasternode, ResignMasternode } from './dftx/dftx_masternode'
import {
CCreateMasternode,
CreateMasternode,
CResignMasternode,
ResignMasternode,
CUpdateMasternode,
UpdateMasternode
} from './dftx/dftx_masternode'

/**
* @param num to map as OPCode, 1 byte long
Expand Down Expand Up @@ -389,6 +396,14 @@ export const OP_CODES = {
data: resignMasternode
})
},
OP_DEFI_TX_UPDATE_MASTER_NODE: (updateMasternode: UpdateMasternode): OP_DEFI_TX => {
return new OP_DEFI_TX({
signature: CDfTx.SIGNATURE,
type: CUpdateMasternode.OP_CODE,
name: CUpdateMasternode.OP_NAME,
data: updateMasternode
})
},
OP_DEFI_TX_SET_GOVERNANCE: (setGovernance: SetGovernance) => {
return new OP_DEFI_TX({
signature: CDfTx.SIGNATURE,
Expand Down