Build Your Own Decentralized Twitter, Part 2: Mitigations
Creating a distributed social media system is all well and good, but how do you address problems like deleted tweets? David Eastman explains.
Dec 3rd, 2022 9:07am by
Image via Shutterstock

Our decentralized architecture
- We use Unix time as both a unique id as well as the time the tweet was made.
- My replies only lock on to the last tweet — a simplification that saves a lot of code.
- When playing with the code, don’t forget to change BASEDIRECTORY in JsonServices.cs to match your directory structure.
Name Change
Just as you can on Twitter, you can easily change the name that will be displayed by our tweet view platform without affecting anything else. Alan is tired of his alphabetically convenient username, and decides to change it to “ZoZo” — at least allowing our output format to remain aligned. He changes his Identity in our example tweet view platform:..
{
"Id" : 1,
"Name" : "Zozo",
"StoreFile" : "AlansTweets.json",
"Permission" : true
}
..
Running the TweetDisplay gives us:
Because the identity controls the displayed name, not the tweet, everything is fine. Alan can be known as something different on another similar platform. With this architecture, tweeters have no unique identifying name — uniqueness would imply a central controller. Within each tweet view platform, they do at least have a local id. He is ‘1’ — we assume that Alan does not control that internal identifier.
Now, we have explicitly not given the platform its own identity (because this is not a federation), so with our architecture, there is no way to ensure that Alan is uniquely identified. Good for anonymity, but also useful for trolls.
No More Alan
Now let’s start the more destructive changes. What happens if Alan stops giving permission to display his tweets?{
"Id" : 1,
"Name" : "Alan",
"StoreFile" : "AlansTweets.json",
"Permission" : false
},
TweetDisplay now shows:
While legal, without Alan’s tweets the first thread is damaged. This is not particularly different to a multiple deletion of individual tweets in Twitter. They at least know that there is no simple way to delete all your tweets at once — and they could add friction to the process if they so wished.
What extra scaffolding could be erected to at least maintain structure? The tweet platform could replace the deleted tweets with a replica of the same id with the content “[deleted by user].” With that, the platform can assert its right to maintain a structure that the user has broken. But it also proves that an act of deletion took place from the tweeter, so could this betray the tweeter in some extreme circumstances?
On further examination, it is really just threads that need this warning — and this is where the structure matters. What we could do as an example mitigation is to add a shadow tweet record when a reply is made to an existing tweet.
Editing
Unlike with Twitter, not only is editing trivial in our project, but you could change every tweet you ever posted as quickly as you can a “search and replace” on a document. Of course this provides for a rather large number of legal problems. At least in Twitter, because you have to delete a tweet before you can change it, the thread is immediately broken. But by editing directly in his tweet store, Alan realises that he can placate Elon Musk rapidly:[
{
"Text": "Hi there, anyone listening?",
"Replyto": 0,
"Time": 1668435369
},
{
"Text": "Oh thank god. Hope Jack canu0027t see this",
"Replyto": 1668435540,
"Time": 1668438963
}
]
(Note: in case you hadn’t spotted it, you can’t use an apostrophe in JSON and hence it is escaped in the example above)
Immediately this changes the display, because nothing is triggered by a change of content:

Elon changes to Jack
More Vandalism
As stated in the earlier article, we use the tweeter’s platform identity to link a name to their tweet. That is the main job of the TweetFrom class. We can see that it also implements IComparable in order to allow us to use the tweet Time to compare whether a tweet is younger or older. As mentioned in the first article, the Time variable doubles up as a unique id for a tweet as well as defining the timeline.
<code class="language-json">class TweetFrom : IComparable<TweetFrom>
{
public string from;
public Tweet tweet;
public TweetFrom(string from, Tweet tweet)
{
this.from = from;
this.tweet = tweet;
}
public int CompareTo(TweetFrom? other)
{
if (tweet.Time == other?.tweet.Time)
{
return 0;
}
// CompareTo() method
return tweet.Time.CompareTo(other?.tweet.Time);
}
}</code>
<code class="language-json">...
List<Identity> idents = JsonServices.ReadIdentitesFromFile();
List<TweetFrom> totalTweets = new List<TweetFrom>();
foreach (var ident in idents)
if (ident.Permission)
{
List<Tweet> tweets = JsonServices.ReadTweetsFromFile(ident);
tweets.ForEach(tweet => totalTweets.Add(new TweetFrom(ident.Name, tweet)));
}
totalTweets.Sort();
...</code>
Mitigations
Mitigation: the action of reducing the severity, seriousness, or painfulness of something. This is exactly what we want to do. We have underlined the weakness of this model, but let’s see how easy it is to address that deletion problem in particular. What we can do is store tweets that have replies, and thus maintain some structure. We don’t have to worry about every tweet, nor content (in fact, we remove this), just the metadata. If you want to see this in code, I’ve used the branchmitigation in the project’s Github entry.
The main change in the code is the addition of a new Identity, which we use to store the mitigation tweets. We have also made use of the SortedDictionary so we can mix in our mitigation tweets if they disappear from the graph.
Here is our familiar tweet display:
The difference is that we have saved a set of tweets that have replies to them in our new Mitigations internal identity. You can see above that there are four tweets with replies, and they are stored:
[
{
"Text": "Tweet not available",
"Replyto": 0,
"Time": 1668435369
},
{
"Text": "Tweet not available",
"Replyto": 1668435369,
"Time": 1668435540
},
{
"Text": "Tweet not available",
"Replyto": 0,
"Time": 1668439104
},
{
"Text": "Tweet not available",
"Replyto": 1668439104,
"Time": 1668439185
}
]
So let’s turn off access to Alan’s tweets (either remove them from his store, or turn off permission in his Identity).
[
..
{
"Id" : 1,
"Name" : "Alan",
"StoreFile" : "AlansTweets.json",
"Permission" : false
},
..
]
and see the effect:
It is small, but at least you can now see that Beth wasn’t talking to herself. We don’t reveal anything further about the missing tweet, except when it was made. Also, Alan’s tweet at the end of the thread is not replaced as it doesn’t have any replies.
I hope I’ve persuaded you that while ‘complete user control’ is perfectly possible, the resulting mayhem may not be very palatable for the social graph. You might well respond that if the platform is in control, it could also vandalize things — but there are many eyes on that.
In the final article, we will look a bit closer at federation and how that changes things again.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.