Skip to content
This repository was archived by the owner on Jan 3, 2023. It is now read-only.

Bitcoin Checkpointing MVP#83

Draft
rllola wants to merge 23 commits intofilecoin-project:eudicofrom
Zondax:zondax/eudico
Draft

Bitcoin Checkpointing MVP#83
rllola wants to merge 23 commits intofilecoin-project:eudicofrom
Zondax:zondax/eudico

Conversation

@rllola
Copy link

@rllola rllola commented Dec 3, 2021

Abstract

This PR add the checkpointing feature in Bitcoin (see #41).

Details

  • Load distributed key file config.toml (see ./data/alice/config.toml)
  • Ping bitcoind to see if available
  • Connect to participants using Libp2p PubSub
  • Generate new distributed keys among participants
  • First participant publish the funding transaction to the taproot address (loaded pubkey + block 0 CID)
  • Every 20 blocks create a new checkpoint
  • Create a config file in S3
  • Collect precedent checkpoint tx (add taproot address to wallet and list transactions)
  • Verify checkpoints
  • Kick misbehaving participant
  • Sign using tweaked value in a distributed fashion
  • Mocked power actors

Run it

You will need docker bitcoind running locally under port 18443 with rpc user satoshi and rpc password amiens.
Under data you will find preconfigure participants in order to quickly start (Alice, Bob and Charlie).

$ make eudico
$ ./scripts/taproot.sh

It will start 3 nodes and a miner.

@rllola
Copy link
Author

rllola commented Dec 3, 2021

After rebasing I started to have this error :

2021-12-03T14:23:59.999+0100    ERROR   events  events/events_called.go:352     event diff fn failed: cbor input had wrong number of fields

I am not sure how to fix it.

@adlrocha
Copy link
Collaborator

adlrocha commented Dec 3, 2021

After rebasing I started to have this error :
2021-12-03T14:23:59.999+0100 ERROR events events/events_called.go:352 event diff fn failed: cbor input had wrong number of fields
I am not sure how to fix it.

Try running make type-gen to generate all cbor schemas again. It seems like some state that you are unmarshalling the wrong state into an object. Let me know if you don't manage to fix it and I can try to look at it next week.

@rllola
Copy link
Author

rllola commented Dec 4, 2021

After adding the mocked power it seems that the error has disappeared.

* fail to detect actor code

* working but need clean

* remove println
* saving file /tmp

* verify if share file exist
id := party.ID(c.host.ID().String())

threshold := 2
n := NewNetwork(ids, c.sub, c.topic)
Copy link
Collaborator

@adlrocha adlrocha Dec 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewNetwork is independent in every round of the DKG and that is why we start it every time a new state change is detected? If this is the case we should probably try to find a better name for the Network struct :) From my first read, it felt like that is the permanent network layer used by the DKG protocol (and reused in every round)

@@ -0,0 +1,185 @@
[API]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just noting it here for the future. Before merging and getting this ready for production, we should probably move these configs to a scripts/taproot that we can use for testing purposes, or remove them. Also, we can probably remove all the commented lines if they are already in the config template.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We agreed to move everything that is under data will be move in Minio and downloaded before starting the demo. Those are pre-generated keys and config only required when developing/running the demo.

if newTs.Height()%20 == 0 {
fmt.Println("Check point time")

// Initiation and config should be happening at start
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We may need to revise this logic to be sure that:

  • The checkpoint is running every 20 blocks.
  • We may need to wait a few epochs before detecting a checkpointing epoch to ensure finality (the tipset we detect in the event change may be reverted and not be final, and committing a reverted tipset in a checkpoint may be quite bad).
  • We should check that we are only committing the checkpoints one. Due to the way checkFunc works, it may trigger the ChangeFunc every time the state change is detected, which could end up translating in a checkpoint being committed several times.

Just wanted to note it here so we test it at some point.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should probably add a config parameter to configure the checkpointing period.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reading the function again, it may be a good idea to detect the change here, wait for a few epochs, and the trigger the changeFunc to create a new checkpoint. That way we ensure finality. I do something similar here in case you need inspiration:

func (s *SubnetMgr) matchCheckpointSignature(ctx context.Context, sh *Subnet, newTs *types.TipSet, diff *diffInfo) (bool, error) {

*/

f := frost.SignTaprootWithTweak(c.config, ids, hashedTx[:], c.tweakedValue[:])
n := NewNetwork(ids, c.sub, c.topic)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should definitely the change the name of the Network struct, because without additional docs it is very misleading.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add a comment. Any name suggestions ? I re-use Network because it is how it was called in the lib and our script.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewRound maybe? Or something like NewState? Let's try to find something more self-describing than Network if we can. It led me to confusion the first time I read it :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought of NewRound too but it could be confusing at the cryptographic level because there is several rounds of communication when you do the DKG/signing... What about NewNetInterface ? Because it is an interface for communication between participants. They can receive and send messages through this instance.

What about Communication ? NewCommunicationRound ?


lc.Append(fx.Hook{
OnStop: func(ctx context.Context) error {
// Do we need to stop something here ?
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably not much. In Start you are joining the keygen topic, you should probably leave it here,but I can't think of anything else that needs to be done right now.

func (c *CheckpointingSub) prefundTaproot() error {
taprootAddress := PubkeyToTapprootAddress(c.pubkey)

payload := "{\"jsonrpc\": \"1.0\", \"id\":\"wow\", \"method\": \"sendtoaddress\", \"params\": [\"" + taprootAddress + "\", 50]}"
Copy link
Collaborator

@adlrocha adlrocha Dec 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same from above regarding raw payload v.s. marshalled struct.

}
newValue := value - FEE

payload := "{\"jsonrpc\": \"1.0\", \"id\":\"wow\", \"method\": \"createrawtransaction\", \"params\": [[{\"txid\":\"" + c.ptxid + "\",\"vout\": " + strconv.Itoa(index) + ", \"sequence\": 4294967295}], [{\"" + newTaprootAddress + "\": \"" + fmt.Sprintf("%.2f", newValue) + "\"}, {\"data\": \"" + hex.EncodeToString(data) + "\"}]]}"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick question: why are you defining the raw payload instead of creating a struct and then marshalling? It may be more readable and less prone to bugs.

return h.Sum(nil)
}

func TaprootSignatureHash(tx []byte, utxo []byte, hash_type byte) ([]byte, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sa8 may I ask for you for some help reviewing this part? I am not super familiar with our design and I may be missing some details. Happy to do it sync together if it works best for you (that way I can learn a bit more about how the protocol works :) ).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sa8 Here the BIP 341 detailing how a message should be serialized before being signed (https://github.com/bitcoin/bips/blob/master/bip-0341.mediawiki#common-signature-message). The scheme for Taproot has changed and is a bit more complicated.

You can also look at this python script used to test against the golang function (https://github.com/Zondax/fil-taproot/blob/main/scripts/main.py).

This step is being done right before signing and it return a hash. The hash is the message then signed by the participants.

return taprootAddressLegacy
}

func ApplyTweakToPublicKeyTaproot(public []byte, tweak []byte) []byte {
Copy link
Collaborator

@adlrocha adlrocha Dec 10, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sa8 same for the next few function related with taproot if possible. Thanks :)

@adlrocha
Copy link
Collaborator

adlrocha commented Dec 10, 2021

@rllola, I did a first pass through the code and left a few comments. Let me know if you feel any part of the code needs a deeper review at this point. Feel free to resolve the comments as you fix them or if you see that they don't apply, and ping me whenever you need/want another review.


// If Power Actors list has changed start DKG
// Changes detected so generate new key
if oldSt.MinerCount != newSt.MinerCount {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may only need to check that the size of symmetrical difference between the two sets is bigger than some threshold u.

}

func GetTaprootScript(pubkey []byte) string {
return "5120" + hex.EncodeToString(pubkey)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "5120" is not clear to me here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the taproot script. The taproot script should start with 1 and is followed by the pubkey.

return false, nil, nil
}

err := c.events.StateChanged(checkFunc, changeHandler, revertHandler, 5, 76587687658765876, match)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does "76587687658765876" refers to?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the confidence threshold used to determine if the StateChangeHandler should be triggered. It is an absurdly high number so the metric used to determine if to trigger it or not is the number of tipsets that have passed in the heaviest chain (the 5 you see there)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants