Add support for AccountDelete (#1120)

https://xrpl.org/accountdelete.html
This commit is contained in:
Elliot Lee
2020-01-06 04:01:10 -08:00
committed by GitHub
parent 23504821cf
commit 138e7942da
19 changed files with 294 additions and 34 deletions

View File

@@ -4511,7 +4511,7 @@ Prepare a transaction. The prepared transaction must subsequently be [signed](#s
This method works with any of [the transaction types supported by rippled](https://developers.ripple.com/transaction-types.html). This method works with any of [the transaction types supported by rippled](https://developers.ripple.com/transaction-types.html).
Notably, this is the preferred method for preparing a `DepositPreauth` transaction (added in rippled 1.1.0). Notably, this is the preferred method for preparing `DepositPreauth` or `AccountDelete` transactions.
### Parameters ### Parameters

View File

@@ -6,7 +6,7 @@ Prepare a transaction. The prepared transaction must subsequently be [signed](#s
This method works with any of [the transaction types supported by rippled](https://developers.ripple.com/transaction-types.html). This method works with any of [the transaction types supported by rippled](https://developers.ripple.com/transaction-types.html).
Notably, this is the preferred method for preparing a `DepositPreauth` transaction (added in rippled 1.1.0). Notably, this is the preferred method for preparing `DepositPreauth` or `AccountDelete` transactions.
### Parameters ### Parameters

View File

@@ -62,6 +62,8 @@ function loadSchemas() {
require('./schemas/specifications/check-cash.json'), require('./schemas/specifications/check-cash.json'),
require('./schemas/specifications/check-cancel.json'), require('./schemas/specifications/check-cancel.json'),
require('./schemas/specifications/trustline.json'), require('./schemas/specifications/trustline.json'),
require('./schemas/specifications/deposit-preauth.json'),
require('./schemas/specifications/account-delete.json'),
require('./schemas/output/sign.json'), require('./schemas/output/sign.json'),
require('./schemas/output/submit.json'), require('./schemas/output/submit.json'),
require('./schemas/output/get-account-info.json'), require('./schemas/output/get-account-info.json'),

View File

@@ -18,6 +18,8 @@
"paymentChannelClaim", "paymentChannelClaim",
"checkCreate", "checkCreate",
"checkCancel", "checkCancel",
"checkCash" "checkCash",
"depositPreauth",
"accountDelete"
] ]
} }

View File

@@ -208,6 +208,30 @@
"$ref": "paymentChannelClaim" "$ref": "paymentChannelClaim"
} }
} }
},
{
"properties": {
"type": {
"enum": [
"depositPreauth"
]
},
"specification": {
"$ref": "depositPreauth"
}
}
},
{
"properties": {
"type": {
"enum": [
"accountDelete"
]
},
"specification": {
"$ref": "accountDelete"
}
}
} }
] ]
} }

View File

@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "accountDelete",
"link": "account-delete",
"type": "object",
"properties": {
"destination": {
"$ref": "address",
"description": "Address of an account to receive any leftover XRP after deleting the sending account. Must be a funded account in the ledger, and must not be the sending account."
},
"destinationTag": {
"$ref": "tag",
"description": "(Optional) Arbitrary destination tag that identifies a hosted recipient or other information for the recipient of the deleted account's leftover XRP."
},
"destinationXAddress": {
"$ref": "address",
"description": "X-address of an account to receive any leftover XRP after deleting the sending account. Must be a funded account in the ledger, and must not be the sending account."
}
},
"anyOf": [
{
"required": ["destination"]
},
{
"required": ["destinationXAddress"]
}
],
"additionalProperties": false
}

View File

@@ -0,0 +1,21 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "depositPreauth",
"link": "deposit-preauth",
"type": "object",
"properties": {
"authorize": {
"$ref": "address",
"description": "Address of the account that can cash the check."
},
"unauthorize": {
"$ref": "address",
"description": "Address of the account that can cash the check."
}
},
"oneOf": [
{"required": ["authorize"]},
{"required": ["unauthorize"]}
],
"additionalProperties": false
}

View File

@@ -0,0 +1,34 @@
import * as assert from 'assert'
import {removeUndefined} from '../../common'
import {classicAddressToXAddress} from 'ripple-address-codec'
export type FormattedAccountDelete = {
// account (address) of an account to receive any leftover XRP after deleting the sending account.
// Must be a funded account in the ledger, and must not be the sending account.
destination: string
// (Optional) Arbitrary destination tag that identifies a hosted recipient or other information
// for the recipient of the deleted account's leftover XRP. NB: Ensure that the hosted recipient is
// able to account for AccountDelete transactions; if not, your balance may not be properly credited.
destinationTag?: number
// X-address of an account to receive any leftover XRP after deleting the sending account.
// Must be a funded account in the ledger, and must not be the sending account.
destinationXAddress: string
}
function parseAccountDelete(tx: any): FormattedAccountDelete {
assert.ok(tx.TransactionType === 'AccountDelete')
return removeUndefined({
destination: tx.Destination,
destinationTag: tx.DestinationTag,
destinationXAddress: classicAddressToXAddress(
tx.Destination,
tx.DestinationTag === undefined ? false : tx.DestinationTag,
false
)
})
}
export default parseAccountDelete

View File

@@ -1,27 +1,31 @@
import {parseOutcome} from './utils' import {parseOutcome} from './utils'
import {removeUndefined} from '../../common' import {removeUndefined} from '../../common'
import parsePayment from './payment'
import parseTrustline from './trustline'
import parseOrder from './order'
import parseOrderCancellation from './cancellation'
import parseSettings from './settings' import parseSettings from './settings'
import parseAccountDelete from './account-delete'
import parseCheckCancel from './check-cancel'
import parseCheckCash from './check-cash'
import parseCheckCreate from './check-create'
import parseDepositPreauth from './deposit-preauth'
import parseEscrowCancellation from './escrow-cancellation'
import parseEscrowCreation from './escrow-creation' import parseEscrowCreation from './escrow-creation'
import parseEscrowExecution from './escrow-execution' import parseEscrowExecution from './escrow-execution'
import parseEscrowCancellation from './escrow-cancellation' import parseOrderCancellation from './cancellation'
import parseCheckCreate from './check-create' import parseOrder from './order'
import parseCheckCash from './check-cash' import parsePayment from './payment'
import parseCheckCancel from './check-cancel' import parsePaymentChannelClaim from './payment-channel-claim'
import parseDepositPreauth from './deposit-preauth'
import parsePaymentChannelCreate from './payment-channel-create' import parsePaymentChannelCreate from './payment-channel-create'
import parsePaymentChannelFund from './payment-channel-fund' import parsePaymentChannelFund from './payment-channel-fund'
import parsePaymentChannelClaim from './payment-channel-claim' import parseTrustline from './trustline'
import parseFeeUpdate from './fee-update'
import parseAmendment from './amendment' import parseAmendment from './amendment' // pseudo-transaction
import parseFeeUpdate from './fee-update' // pseudo-transaction
function parseTransactionType(type) { function parseTransactionType(type) {
// Ordering matches https://developers.ripple.com/transaction-types.html // Ordering matches https://developers.ripple.com/transaction-types.html
const mapping = { const mapping = {
AccountSet: 'settings', AccountSet: 'settings',
AccountDelete: 'accountDelete',
CheckCancel: 'checkCancel', CheckCancel: 'checkCancel',
CheckCash: 'checkCash', CheckCash: 'checkCash',
CheckCreate: 'checkCreate', CheckCreate: 'checkCreate',
@@ -49,23 +53,25 @@ function parseTransactionType(type) {
function parseTransaction(tx: any, includeRawTransaction: boolean): any { function parseTransaction(tx: any, includeRawTransaction: boolean): any {
const type = parseTransactionType(tx.TransactionType) const type = parseTransactionType(tx.TransactionType)
const mapping = { const mapping = {
payment: parsePayment,
trustline: parseTrustline,
order: parseOrder,
orderCancellation: parseOrderCancellation,
settings: parseSettings, settings: parseSettings,
accountDelete: parseAccountDelete,
checkCancel: parseCheckCancel,
checkCash: parseCheckCash,
checkCreate: parseCheckCreate,
depositPreauth: parseDepositPreauth,
escrowCancellation: parseEscrowCancellation,
escrowCreation: parseEscrowCreation, escrowCreation: parseEscrowCreation,
escrowExecution: parseEscrowExecution, escrowExecution: parseEscrowExecution,
escrowCancellation: parseEscrowCancellation, orderCancellation: parseOrderCancellation,
checkCreate: parseCheckCreate, order: parseOrder,
checkCash: parseCheckCash, payment: parsePayment,
checkCancel: parseCheckCancel, paymentChannelClaim: parsePaymentChannelClaim,
depositPreauth: parseDepositPreauth,
paymentChannelCreate: parsePaymentChannelCreate, paymentChannelCreate: parsePaymentChannelCreate,
paymentChannelFund: parsePaymentChannelFund, paymentChannelFund: parsePaymentChannelFund,
paymentChannelClaim: parsePaymentChannelClaim, trustline: parseTrustline,
feeUpdate: parseFeeUpdate,
amendment: parseAmendment amendment: parseAmendment, // pseudo-transaction
feeUpdate: parseFeeUpdate // pseudo-transaction
} }
const parser: Function = mapping[type] const parser: Function = mapping[type]

View File

@@ -354,6 +354,16 @@ export default <TestSuite>{
) )
}, },
'AccountDelete': async (api, address) => {
const hash = 'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
const response = await api.getTransaction(hash)
assertResultMatch(
response,
RESPONSE_FIXTURES.accountDelete,
'getTransaction'
)
},
'no Meta': async (api, address) => { 'no Meta': async (api, address) => {
const hash = const hash =
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B' 'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA1B'

View File

@@ -745,6 +745,33 @@ export default <TestSuite>{
return assertResultMatch(response, expected, 'prepare') return assertResultMatch(response, expected, 'prepare')
}, },
'AccountDelete': async (api, address) => {
const localInstructions = {
...instructionsWithMaxLedgerVersionOffset,
maxFee: '5.0' // 5 XRP fee for AccountDelete
}
const txJSON = {
TransactionType: 'AccountDelete',
Account: address,
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe'
}
const response = await api.prepareTransaction(txJSON, localInstructions)
const expected = {
txJSON:
'{"TransactionType":"AccountDelete","Account":"' +
address +
'","Destination":"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe","Flags":2147483648,"LastLedgerSequence":8820051,"Fee":"12","Sequence":23}',
instructions: {
fee: '0.000012',
sequence: 23,
maxLedgerVersion: 8820051
}
}
return assertResultMatch(response, expected, 'prepare')
},
// prepareTransaction - Payment // prepareTransaction - Payment
'Payment - normal': async (api, address) => { 'Payment - normal': async (api, address) => {
const localInstructions = { const localInstructions = {

View File

@@ -212,7 +212,7 @@ export default <TestSuite>{
) => { ) => {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV' const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = { const request = {
// TODO: This fails when address is X-Address // TODO: This fails when address is X-address
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"1.2","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`, txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"1.2","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: { instructions: {
fee: '0.0000012', fee: '0.0000012',
@@ -232,7 +232,7 @@ export default <TestSuite>{
) => { ) => {
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV' const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = { const request = {
// TODO: This fails when address is X-Address // TODO: This fails when address is X-address
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"1123456.7","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`, txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"1123456.7","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: { instructions: {
fee: '1.1234567', fee: '1.1234567',
@@ -289,7 +289,7 @@ export default <TestSuite>{
api._maxFeeXRP = '2.1' api._maxFeeXRP = '2.1'
const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV' const secret = 'shsWGZcmZz6YsWWmcnpfr6fLTdtFV'
const request = { const request = {
// TODO: This fails when address is X-Address // TODO: This fails when address is X-address
txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`, txJSON: `{"Flags":2147483648,"TransactionType":"AccountSet","Account":"r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59","Domain":"726970706C652E636F6D","LastLedgerSequence":8820051,"Fee":"2010000","Sequence":23,"SigningPubKey":"02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8"}`,
instructions: { instructions: {
fee: '2.01', fee: '2.01',

View File

@@ -0,0 +1,32 @@
{
"type": "accountDelete",
"address": "rM5qup5BYDLMXaR5KU1hiC9HhFMuBVrnKv",
"sequence": 3227049,
"id": "EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9",
"specification": {
"destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
"destinationXAddress": "XV5kHfQmzDQjbFNv4jX3FX9Y7ig5QhpKGEFCq4mdLfhdxMq"
},
"outcome": {
"result": "tesSUCCESS",
"timestamp": "2019-12-17T09:16:51.000Z",
"fee": "5",
"balanceChanges": {
"rM5qup5BYDLMXaR5KU1hiC9HhFMuBVrnKv": [
{
"currency": "XRP",
"value": "-10000"
}
],
"rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe": [
{
"currency": "XRP",
"value": "9995"
}
]
},
"orderbookChanges": {},
"ledgerVersion": 3232071,
"indexInLedger": 0
}
}

View File

@@ -67,7 +67,8 @@ module.exports = {
paymentChannelClaim: paymentChannelClaim:
require('./get-transaction-payment-channel-claim.json'), require('./get-transaction-payment-channel-claim.json'),
amendment: require('./get-transaction-amendment.json'), amendment: require('./get-transaction-amendment.json'),
feeUpdate: require('./get-transaction-fee-update.json') feeUpdate: require('./get-transaction-fee-update.json'),
accountDelete: require('./get-transaction-account-delete.json')
}, },
getTransactions: { getTransactions: {
normal: require('./get-transactions.json'), normal: require('./get-transactions.json'),

View File

@@ -99,6 +99,7 @@ module.exports = {
NoMeta: require('./tx/no-meta.json'), NoMeta: require('./tx/no-meta.json'),
LedgerZero: require('./tx/ledger-zero.json'), LedgerZero: require('./tx/ledger-zero.json'),
Amendment: require('./tx/amendment.json'), Amendment: require('./tx/amendment.json'),
SetFee: require('./tx/set-fee.json') SetFee: require('./tx/set-fee.json'),
AccountDelete: require('./tx/account-delete.json')
} }
}; };

View File

@@ -0,0 +1,66 @@
{
"id": 0,
"result": {
"Account": "rM5qup5BYDLMXaR5KU1hiC9HhFMuBVrnKv",
"Destination": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
"Fee": "5000000",
"Flags": 2147483648,
"LastLedgerSequence": 3232818,
"Sequence": 3227049,
"SigningPubKey": "022E0DBF14BC4CFF96BC839557EE6F12F6DA45DCD917376F805E65D1B1C60A8CE6",
"TransactionType": "AccountDelete",
"TxnSignature": "304402207BDBE1B71C8BD00363905817C9373880CE9E8F0080623D457495E2B760BBBEE402202EDEB977D1ED865C1EAB88FE28581E3F8A672097B8BB0956E977C6EC87CA668C",
"date": 629889411,
"hash": "EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9",
"inLedger": 3232071,
"ledger_index": 3232071,
"meta": {
"AffectedNodes": [
{
"DeletedNode": {
"FinalFields": {
"Account": "rM5qup5BYDLMXaR5KU1hiC9HhFMuBVrnKv",
"Balance": "0",
"Flags": 0,
"OwnerCount": 0,
"PreviousTxnID": "2737BEDDDA1D7FB523CFB84B891216331CC4CC999349828D81C6C727A1115A44",
"PreviousTxnLgrSeq": 3227049,
"Sequence": 3227050
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "08EBF94D7BB527442E0B51F533B902DDCFBD423D8E95FAE752BE7876A29E875B",
"PreviousFields": {
"Balance": "10000000000",
"Sequence": 3227049
}
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
"Balance": "99991026734967448",
"Flags": 0,
"OwnerCount": 0,
"Sequence": 2978
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "31CCE9D28412FF973E9AB6D0FA219BACF19687D9A2456A0C2ABC3280E9D47E37",
"PreviousFields": {
"Balance": "99991016739967448"
},
"PreviousTxnID": "2737BEDDDA1D7FB523CFB84B891216331CC4CC999349828D81C6C727A1115A44",
"PreviousTxnLgrSeq": 3227049
}
}
],
"DeliveredAmount": "9995000000",
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"delivered_amount": "9995000000"
},
"validated": true
},
"status": "success",
"type": "response"
}

View File

@@ -524,6 +524,11 @@ export function createMockRippled(port) {
'81B9ECAE7195EB6E8034AEDF44D8415A7A803E14513FDBB34FA984AB37D59563' '81B9ECAE7195EB6E8034AEDF44D8415A7A803E14513FDBB34FA984AB37D59563'
) { ) {
conn.send(createResponse(request, fixtures.tx.PaymentChannelClaim)) conn.send(createResponse(request, fixtures.tx.PaymentChannelClaim))
} else if (
request.transaction ===
'EC2AB14028DC84DE525470AB4DAAA46358B50A8662C63804BFF38244731C0CB9'
) {
conn.send(createResponse(request, fixtures.tx.AccountDelete))
} else if ( } else if (
request.transaction === request.transaction ===
'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11' 'AFB3ADF22F3C605E23FAEFAA185F3BD763C4692CAC490D9819D117CD33BFAA11'

View File

@@ -42,7 +42,7 @@ describe('RippleAPI [Test Runner]', function() {
}) })
// Run each test with the newer, x-address style. // Run each test with the newer, x-address style.
if (!config.skipXAddress) { if (!config.skipXAddress) {
describe(`[X-Address]`, () => { describe(`[X-address]`, () => {
for (const [testName, fn] of tests) { for (const [testName, fn] of tests) {
it(testName, function() { it(testName, function() {
return fn(this.api, addresses.ACCOUNT_X) return fn(this.api, addresses.ACCOUNT_X)

View File

@@ -33,7 +33,7 @@ interface LoadedTestSuite {
name: string name: string
tests: [string, TestFn][] tests: [string, TestFn][]
config: { config: {
/** Set to true to skip re-running tests with an X-Address. */ /** Set to true to skip re-running tests with an X-address. */
skipXAddress?: boolean skipXAddress?: boolean
} }
} }