Skip to content

Commit

Permalink
add ExpandedSignerList amendment support (#406)
Browse files Browse the repository at this point in the history
expand the maximum signer list to 32 entries
  • Loading branch information
khancode committed Sep 13, 2022
1 parent 5058b42 commit dd9d024
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [[Unreleased]]
### Added:
- Support for ExpandedSignerList amendment that expands the maximum signer list to 32 entries
- Function to parse the final account balances from a transaction's metadata
- Function to parse order book changes from a transaction's metadata
- Support for Ed25519 seeds that don't use the `sEd` prefix
Expand Down
133 changes: 133 additions & 0 deletions tests/unit/models/transactions/test_signer_list_set.py
Expand Up @@ -177,3 +177,136 @@ def test_signer_entries_signer_quorum_valid(self):
signer_entries=_SIGNER_ENTRIES_VALID,
)
self.assertTrue(tx.is_valid())

def test_max_signer_entries_above_8_below_32(self):
signers = [
"rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6",
"r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm",
"rpwq8vi4Mn3L5kDJmb8Mg59CanPFPzMCnj",
"rB72Gzqfejai46nkA4HaKYBHwAnn2yUoT4",
"rGqsJSAW71pCfUwDD5m52bLw69RzFg6kMW",
"rs8smPRA31Ym4mGxb1wzgwxtU5eVK82Gyk",
"rLrugpGxzezUQLDh7Jv1tZpouuV4MQLbU9",
"rUQ6zLXQdh1jJLGwMXp9P8rgi42kwuafzs",
"rMjY8sPdfxsyRrnVKQcutxr4mTHNXy9dEF",
]
signer_entries = []
for acc in signers:
signer_entries.append(
SignerEntry(
account=acc,
signer_weight=1,
)
)

tx = SignerListSet(
account=_ACCOUNT,
fee=_FEE,
sequence=_SEQUENCE,
signer_quorum=9,
signer_entries=signer_entries,
)
self.assertTrue(tx.is_valid())

def test_max_signer_entries_exceeded(self):
signers = [
"rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6",
"r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm",
"rpwq8vi4Mn3L5kDJmb8Mg59CanPFPzMCnj",
"rB72Gzqfejai46nkA4HaKYBHwAnn2yUoT4",
"rGqsJSAW71pCfUwDD5m52bLw69RzFg6kMW",
"rs8smPRA31Ym4mGxb1wzgwxtU5eVK82Gyk",
"rLrugpGxzezUQLDh7Jv1tZpouuV4MQLbU9",
"rUQ6zLXQdh1jJLGwMXp9P8rgi42kwuafzs",
"rMjY8sPdfxsyRrnVKQcutxr4mTHNXy9dEF",
"rUaxYLeFGm6SmMoa2WCqLKSyHwJyvaQmeG",
"r9wUfeVtqMfqrcDTfCpNYbNZvs5q9M9Rpo",
"rQncVNak5kvJGPUFa6fuKH7t8Usjs7Np1c",
"rnwbSSnPbVbUzuBa4etkeYrfy5v7SyhtPu",
"rDXh5D3t48MdBJyXByXq47k5P8Kuf1758B",
"rh1D4jd2mAiqUPHfAZ2cY9Nbfa3kAkaQXP",
"r9T129tXgtnyfGoLeS35c2HctaZAZSQoCH",
"rUd2uKsyCWfJP7Ve36mKoJbNCA7RYThnYk",
"r326x8PaAFtnaH7uoxaKrcDWuwpeHn4wDa",
"rpN3mkXkYhfNadcXPrY4LniM1KpM3egyQM",
"rsPKbR155hz1zrA4pSJp5Y2fxasZAatcHb",
"rsyWFLaEKTpaoSJusjpcDvGexuHCwMnqss",
"rUbc5RXfyF81oLDMgd3d7jpY9YMNMZG4XN",
"rGpYHM88BZe1iVKFHm5xiWYYxR74oxJEXf",
"rPsetWAtR1KxDtxzgHjRMD7Rc87rvXk5nD",
"rwSeNhL6Hi34igr12mCr61jY42psfTkWTq",
"r46Mygy98qjkDhVB6qs4sBnqaf7FPiA2vU",
"r4s8GmeYN4CiwVate1nMUvwMQbundqf5cW",
"rKAr4dQWDYG8cG2hSwJUVp4ry4WNaWiNgp",
"rPWXRLp1vqeUHEH3WiSKuyo9GM9XhaENQU",
"rPgmdBdRKGmndxNEYxUrrsYCZaS6go9RvW",
"rPDJZ9irzgwKRKScfEmuJMvUgrqZAJNCbL",
"rDuU2uSXMfEaoxN1qW8sj7aUNFLGEn3Hr2",
"rsbjSjA4TCB9gtm7x7SrWbZHB6g4tt9CGU",
]
signer_entries = []
for acc in signers:
signer_entries.append(
SignerEntry(
account=acc,
signer_weight=1,
)
)

with self.assertRaises(XRPLModelException):
SignerListSet(
account=_ACCOUNT,
fee=_FEE,
sequence=_SEQUENCE,
signer_quorum=33,
signer_entries=signer_entries,
)

def test_signer_entries_with_wallet_locator(self):
signer_entries = [
SignerEntry(
account="rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6",
signer_weight=1,
wallet_locator="CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAF"
"ECAFECAFE",
),
SignerEntry(
account="r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm",
signer_weight=1,
),
SignerEntry(
account="rpwq8vi4Mn3L5kDJmb8Mg59CanPFPzMCnj",
signer_weight=1,
wallet_locator="0000000000000000000000000000000000000000000000000000000"
"0DEADBEEF",
),
]
tx = SignerListSet(
account=_ACCOUNT,
fee=_FEE,
sequence=_SEQUENCE,
signer_quorum=3,
signer_entries=signer_entries,
)
self.assertTrue(tx.is_valid())

def test_signer_entries_with_invalid_wallet_locator(self):
signer_entries = [
SignerEntry(
account="rBFBipte4nAQCTsRxd2czwvSurhCpAf4X6",
signer_weight=1,
wallet_locator="not_valid",
),
SignerEntry(
account="r3ijUH32iiy9tYNj3rD7hKWYjy1BFUxngm",
signer_weight=1,
),
]
with self.assertRaises(XRPLModelException):
SignerListSet(
account=_ACCOUNT,
fee=_FEE,
sequence=_SEQUENCE,
signer_quorum=2,
signer_entries=signer_entries,
)
42 changes: 38 additions & 4 deletions xrpl/models/transactions/signer_list_set.py
@@ -1,15 +1,32 @@
"""Model for SignerListSet transaction type."""
from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Pattern

from typing_extensions import Final

from xrpl.models.nested_model import NestedModel
from xrpl.models.required import REQUIRED
from xrpl.models.transactions.transaction import Transaction
from xrpl.models.transactions.types import TransactionType
from xrpl.models.utils import require_kwargs_on_init

MAX_SIGNER_ENTRIES: Final[int] = 32
"""
Maximum number of signer entries allowed.
:meta private:
"""

HEX_WALLET_LOCATOR_REGEX: Final[Pattern[str]] = re.compile("[A-Fa-f0-9]{64}")
"""
Matches hex-encoded WalletLocator in the format allowed by XRPL.
:meta private:
"""


@require_kwargs_on_init
@dataclass(frozen=True)
Expand All @@ -30,6 +47,13 @@ class SignerEntry(NestedModel):
:meta hide-value:
"""

wallet_locator: Optional[str] = None
"""
An arbitrary 256-bit (32-byte) field that can be used to identify the signer, which
may be useful for smart contracts, or for identifying who controls a key in a large
organization.
"""


@require_kwargs_on_init
@dataclass(frozen=True)
Expand Down Expand Up @@ -78,11 +102,14 @@ def _get_errors(self: SignerListSet) -> Dict[str, str]:
"signer_quorum"
] = "`signer_quorum` must be greater than or equal to 0."

if len(self.signer_entries) < 1 or len(self.signer_entries) > 8:
if (
len(self.signer_entries) < 1
or len(self.signer_entries) > MAX_SIGNER_ENTRIES
):
errors["signer_entries"] = (
"`signer_entries` must have at least 1 member and no more than 8 "
"`signer_entries` must have at least 1 member and no more than {} "
"members. If this transaction is deleting the SignerList, then "
"this parameter must be omitted."
"this parameter must be omitted.".format(MAX_SIGNER_ENTRIES)
)
return errors

Expand All @@ -95,6 +122,13 @@ def _get_errors(self: SignerListSet) -> Dict[str, str]:
"The account submitting the transaction cannot appear in a "
"signer entry."
)
if signer_entry.wallet_locator is not None and not bool(
HEX_WALLET_LOCATOR_REGEX.fullmatch(signer_entry.wallet_locator)
):
errors["signer_entries"] = (
"A SignerEntry's wallet_locator must be a 256-bit (32-byte)"
"hexadecimal value."
)
account_set.add(signer_entry.account)
signer_weight_sum += signer_entry.signer_weight

Expand Down

0 comments on commit dd9d024

Please sign in to comment.