Parser l'ensemble des blocs du stockage local de Duniter

Je cherche un bon moyen d’accéder simplement à tous les blocs de notre blockchain. Pour l’instant, le plus simple que j’ai trouvé est d’avoir un nœud duniter synchronisé et de lire les chunks dans les fichiers chunk.json. Le problème est qu’il faut ensuite parser les différentes parties comme PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID. Ce n’est pas compliqué, mais c’est un peu chiant à (re)écrire, surtout si ça existe déjà ailleurs. J’ai cherché du côté de duniterpy mais il ne parse les documents qu’au format document, pas au format json. L’API d’un nœud duniter fournit également le document au format json. Y a-t-il une manière d’obtenir tous les blocs au format document de telle sorte à pouvoir le parser avec duniterpy ?

@yyy, tu as peut-être déjà fait ça ?

(le seul truc qui ressemble un peu dans duniterpy c’est from_bma_history mais uniquement pour les transactions)

PS:

  • pourquoi y a-t-il un champ wrong dans le json ?
  • quel est l’intérêt du champ writtenOn (previous block number) sachant qu’on a ensuite le champ written_on (previous blockstamp) ?
  • pourquoi y a-t-il un champ monetaryMass alors que c’est plutôt une affaire d’index ?
1 J'aime

Je comprends pas ce que tu veux dire ni ce que tu veux faire. Que veux-tu dire avec le format document ?
DuniterPy récupère les blocs via BMA au format JSON, les parse, puis c’est stocké dans une intance de la classe Block qui hérite de Document. Ça rend le document plus facilement exploitable.

Tu peux t’inspirer et modifier la commande verify qui récupère des blocs, puis vérifie leurs signatures. Tu peux modifier le code pour faire d’autres opérations.


Dans quel document vois-tu ces champs ? Je ne le vois pas dans le document bloc. L’est-il dans ses sous-documents (cert, tx…) ?

Je comprends pas. Peux-tu préciser ?

1 J'aime

Par « format document », je veux dire « format pur texte », avec champ: valeur

Je voudrais éviter des requêtes sur le réseau, même local, puisque j’ai directement accès aux blocs dans les fichiers chunk.

Les champs dont je parle sont ceux qui figurent dans .config/duniter/duniter_default/g1-test/chunk_0-250.json par exemple.

Merci pour la commande verify, je n’avais juste pas vu la fonction duniterpy.documents.Block.from_signed_raw. Est-ce bien elle qui prend du json en entrée ?

1 J'aime

Non, cette fonction prends le raw qui est la partie en dessous du bloc en json dans les requêtes BMA. Il est possible de parser le json en objet Python avec le module json. DuniterPy le fait à certains endroits Ça pourrait t’être utile pour lire ces chunks en local.

3 J'aimes

Des reliquats dans les définitions des types qui ne servent plus mais qui n’ont pas été supprimés, et tu n’as encore rien vu il y en a beaucoup d’autres des champs qui ne servent plus et qui sont encore là. Au fur et a mesure de la migration en Rust j’essayerai de supprimer tout ce qui ne sert plus, mais ça prendra du temps.

On pourrait se poser la même question pour les champs membersCount, issuersCount, issuersFrame et issuersFrameVar. Leur déclaration dans un bloc est en effet purement inutile puisque la valeur de ses champs peut être déduite pour chaque bloc en indexant depuis le bloc genesis.
Après c’est plus simple d’avoir ses méta-données directement a dispo plutôt que de devoir indexer, et l’impact sur la taille d’un bloc est négligeable, donc je vois pas de nécessité de les supprimer.

2 J'aimes

C’est le format « raw », que tu retrouve dans le champ raw de chaque bloc json.

2 J'aimes

C’est ce que j’ai commencé par faire, avant de me rendre compte que je ne trouvais pas de fonction acceptant du json parsé (dict, array…) en entrée. Y en a-t-il une ? Peut on facilement passer du format json au format raw pour donner à manger à la fonction from_signed_raw ?

1 J'aime

Non, pas à ma connaissance. Cependant j’ai une piste.

Dans Silkaj < v0.7.0, qui n’utilisait pas encore DuniterPy, le json était parsé avec json.loads().

Sinon, il faut implémenter dans DuniterPy le parsing du json.

2 J'aimes

Est-ce que ça aurait une utilité ? Si oui, je peux peut-être le faire proprement et l’intégrer. Sinon, je ferai à l’arrache juste pour moi.

1 J'aime

Ce que tu cherches à faire m’a donné l’idée de récupérer la chaîne de blocs directement localement au lieu d’une API cliente, en particulier pour plus de rapidité.
Donc oui, cette fonctionnalité trouvera un usage. La tienne est déjà un cas d’usage qui mérite une implémentation.
Si tu as besoin d’aide dans ton implémentation n’hésite pas.

2 J'aimes

Je veux bien un peu d’aide pour comprendre le message :

# identité extraite du bloc zéro
>>> i = "3QLkBNoCNJENY8HyCDh1kDG2UKdg3q66z1Q91hpSJinD:2vfmih7xhW/QLJ85PZH1Tc6j5fooIXca+yr/esnt0yvdk5LhEKrOB32JFqCctAoRNwpRjBdZ2Q8l15+In1rrDg==:0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855:Alfybe"
>>> duniterpy.documents.identity.Identity.from_inline(10,"g1",i)
Traceback (most recent call last):
    [...]
    raise MalformedDocumentError("Inline self certification")
duniterpy.documents.document.MalformedDocumentError: Could not parse field Inline self certification

Je tente de créer un objet « Identity » à partir de sa version inline. Le format de l’identité inline est-il le bon ? (il a l’air de bien correspondre à PUBLIC_KEY:SIGNATURE:TIMESTAMP:USER_ID). Pourquoi est-ce une auto-certification ? La clé est-elle autosignée ? Pourquoi l’identité n’est-elle pas valide ? Est-ce spécifique au bloc zéro ?

1 J'aime

Il te faut ajouter un \n à la fin pour que ça matche la regex. Cf la regex en question :

2 J'aimes

Dans la RFC, il n’y a pas de format inline précisé pour le document de révocation. Je suppose que c’est juste : PUBLIC_KEY:IDTY_SIGNATURE:REVOCATION_SIGNATURE puisque le reste du document peut être reconstruit avec ces informations. Me trompe-je ?

1 J'aime

Oh, ça marche, c’est magique ! Merci :star_struck:

Oui, je me trompe, la regex est : ({pubkey_regex}):({signature_regex}). Merci Moul !

1 J'aime

@Moul j’ai créé une MR pour pouvoir discuter directement sur le code : https://git.duniter.org/clients/python/duniterpy/-/merge_requests/105, peux-tu me dire ce que tu en penses ?

Edit: c’est bon, ça passe les tests. Je pense que c’est prêt à être mergé sauf si le fait que le fichier de tests pèse 180Ko à cause des blocs que j’ai mis dedans pose problème.

Dans ma branche, on peut maintenant faire :

>>> from duniterpy.localblockchain import load
>>> bc = load() # cherche dans le chemin par défaut path=~/.config/duniter/duniter_default/g1
>>> b = next(bc) # bc est un itérateur, next récupère le premier bloc
>>> b.number # b est bien le bloc zéro
0
>>> b.currency # c'est un document Block duniterpy
'g1'
>>> b.inner_hash
'55FBB918864D56922100211DAEF9CB71AC412BE10CC16B3A7AE56E10FB3A7569'

Est-ce que tu imagines une API cliente locale ou un système différent qui parcourt les blocs afin de répondre aux requêtes ? De ce que je vois du code de Silkaj, il ne suffit pas d’implémenter un autre « endpoint » pour le Client, il faut écrire un autre système de récupération des sources…

1 J'aime

@Moul j’ai intégré les modifications que tu suggérais, mais la pipeline indique une erreur que je ne comprends pas : https://git.duniter.org/HugoTrentesaux/duniterpy/-/jobs/40952