-
Notifications
You must be signed in to change notification settings - Fork 715
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pull Genesis parsing out of state #2284
Conversation
vm.state, err = state.New( | ||
vm.db, | ||
genesisBytes, | ||
g, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
by pulling the byte parsing out of state and passing a well defined object instead, we open the way to simplify state testing. State is surprisingly not well tests (with a few notable exceptions), and part of the reason is the complex building of genesis state. This PR solves this problem
avaxAssetID := ids.GenerateTestID() | ||
genesisTime := time.Now().Truncate(time.Second) | ||
genesisEndTime := genesisTime.Add(28 * 24 * time.Hour) | ||
|
||
addr, err := address.FormatBech32(constants.UnitTestHRP, ids.GenerateTestShortID().Bytes()) | ||
require.NoError(err) | ||
|
||
genesisValidators := []api.GenesisPermissionlessValidator{{ | ||
GenesisValidator: api.GenesisValidator{ | ||
StartTime: json.Uint64(genesisTime.Unix()), | ||
EndTime: json.Uint64(genesisEndTime.Unix()), | ||
NodeID: ids.GenerateTestNodeID(), | ||
}, | ||
RewardOwner: &api.Owner{ | ||
Threshold: 1, | ||
Addresses: []string{addr}, | ||
}, | ||
Staked: []api.UTXO{{ | ||
Amount: json.Uint64(2 * units.KiloAvax), | ||
Address: addr, | ||
}}, | ||
DelegationFee: reward.PercentDenominator, | ||
}} | ||
|
||
buildGenesisArgs := api.BuildGenesisArgs{ | ||
NetworkID: json.Uint32(constants.UnitTestID), | ||
AvaxAssetID: avaxAssetID, | ||
UTXOs: nil, | ||
Validators: genesisValidators, | ||
Chains: nil, | ||
Time: json.Uint64(genesisTime.Unix()), | ||
InitialSupply: json.Uint64(360 * units.MegaAvax), | ||
Encoding: formatting.Hex, | ||
} | ||
|
||
buildGenesisResponse := api.BuildGenesisReply{} | ||
platformvmSS := api.StaticService{} | ||
require.NoError(platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse)) | ||
|
||
genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes) | ||
require.NoError(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just moved to buildGenesisTest
+ dropped the formatting nonsense.
func buildGenesisTest(ctx *snow.Context) []byte { | ||
genesisUTXOs := make([]api.UTXO, len(preFundedKeys)) | ||
func buildGenesisTest(ctx *snow.Context) *genesis.Genesis { | ||
genesisUtxos := make([]*genesis.UTXO, len(preFundedKeys)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these lines are basically what platformvmSS.BuildGenesis
used to do. Pulled up here and in other helpers_test.go files. In a following PR a drop the code duplication and centralize genesis creation in UTs
vms/platformvm/genesis/genesis.go
Outdated
@@ -22,6 +24,8 @@ type Genesis struct { | |||
Timestamp uint64 `serialize:"true"` | |||
InitialSupply uint64 `serialize:"true"` | |||
Message string `serialize:"true"` | |||
|
|||
GenesisID ids.ID `serialize:"false"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're changing a genesis related object but as long as changes are not serialized it's fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Kinda want to see if we can remove this field, wrote a comment in state
related to this
vms/platformvm/genesis/genesis.go
Outdated
@@ -39,5 +43,7 @@ func Parse(genesisBytes []byte) (*Genesis, error) { | |||
return nil, err | |||
} | |||
} | |||
|
|||
gen.GenesisID = hashing.ComputeHash256Array(genesisBytes) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is what we used to do in state package to derive genesisID. Just moved here
vms/platformvm/state/state.go
Outdated
// Create the genesis block and save it as being accepted (We don't do | ||
// genesisBlock.Accept() because then it'd look for genesisBlock's | ||
// non-existent parent) | ||
genesisID := hashing.ComputeHash256Array(genesisBytes) | ||
genesisBlock, err := block.NewApricotCommitBlock(genesisID, 0 /*height*/) | ||
genesisBlock, err := block.NewApricotCommitBlock(g.GenesisID, 0 /*height*/) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of having genesis id as a field attached to Genesis, I think it would be cleaner to just hash the genesis file here and have the caller pass in the id. This way we don't have to have the ID
field in Genesis
which is only ever used here anyways. I think the ID is more of a property of the genesis block than the genesis data itself
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@joshua-kim I don't really have a file to hash here, I have a byte slice. I understand your point about the ID being a block property.
I have replace the GenesisID attribute with GenesisBytes one and hashed it to get the genesis block ID wherever needed.
LMK if this is acceptable
EndTime: json.Uint64(defaultValidateEndTime.Unix()), | ||
NodeID: nodeID, | ||
vdrs := txheap.NewByEndTime() | ||
for idx, nodeID := range genesisNodeIDs { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We initialize genesisNodeIDs
in init()
, so I think it would make sense for us to also initialize them here as well. Maybe we can move that blob of code down here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then what if we write a test that does not really use the environment? The genesisNodeIDs content would change depending on the order tests are executed right?
This is the reason I'd keep them in the init,
@@ -395,67 +394,81 @@ func defaultFx(clk *mockable.Clock, log logging.Logger, isBootstrapped bool) fx. | |||
return res | |||
} | |||
|
|||
func buildGenesisTest(ctx *snow.Context) []byte { | |||
genesisUTXOs := make([]api.UTXO, len(preFundedKeys)) | |||
func buildGenesisTest(ctx *snow.Context) *genesis.Genesis { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually didn't reailze we have a lot of genesis helpers to create pre-funded genesis. Wondering if it makes sense for us to consolidate this into a single place somewhere and export it out of a package. It could be something like:
package somewhere
func NewPrefundedGenesis(keys ...*secp2561.PrivateKey) (*TestGenesis, error) {
...
}
type *TestGenesis struct {
*Genesis
Keys []*secp256k1.PrivateKey
}
I'm not sure what package i'd put his in. Maybe we can make a package like genesis/test
or put this in platformvm
somewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I do in #2107 for genesis helpers and subsequent PRs for other test helpers.
Sorry we got the code duplication here, just trying to segment changes limit PRs scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also historically, duplication arised when I repackaged platformvm but the way test parameters were set were slightly different across packaged. #2107 and subsequent PR remove duplicated code and fixes test to make them work with the default values and not ony with the specific values used in different packages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe our current model for VMs is they are initialized with genesis bytes: https://docs.avax.network/build/vm/create/golang-vm-complex#virtual-machine-methods
This feels like a step in the wrong direction. I think to simplify testing, we could instead have a defaultGenesisBytes as a UT constant.
Why does the configuration for testing have to mirror non-test scenarios? It's not like the temporary networks we use for e2e testing mirror the deployment semantics for fuji or mainnet. |
This PR modifies non-test files (specifically, adjusting the |
@dhrubabasu I am not changing the VM interface. Rather I am moving the byte parsing out of the state package. |
I think we can create a helper function that returns the genesisBytes. We shouldn't need to modify the func main() {
buildGenesisArgs := api.BuildGenesisArgs{
Encoding: formatting.Hex,
NetworkID: json.Uint32(constants.UnitTestID),
AvaxAssetID: ids.GenerateTestID(),
UTXOs: []api.UTXO{}, // No UTXOs
Validators: []api.GenesisPermissionlessValidator{}, // No Validators
Chains: nil,
Time: json.Uint64(time.Date(1997, 1, 1, 0, 0, 0, 0, time.UTC).Unix()),
InitialSupply: json.Uint64(360 * units.MegaAvax),
}
buildGenesisResponse := api.BuildGenesisReply{}
platformvmSS := api.StaticService{}
err := platformvmSS.BuildGenesis(nil, &buildGenesisArgs, &buildGenesisResponse)
if err != nil {
panic(err)
}
genesisBytes, err := formatting.Decode(buildGenesisResponse.Encoding, buildGenesisResponse.Bytes)
if err != nil {
panic(err)
}
fmt.Println(genesisBytes)
}
|
This PR has become stale because it has been open for 30 days with no activity. Adding the |
Why this should be merged
Building setup for unit test in platformVm is cumbersome. Once of the reasons is that we use genesisBytes to initialize the platformVM state. This forces us to build genesis and serialize it in UTs.
This PR drop the bytes and pass a genesisState data structure, thus simplifying the UTs context. This PR kills 130 LOC without giving up any functionality.
Thanks @joshua-kim and @marun for the discussions that brought me to these changes.
How this works
Let state.State accept a genesis state instead of its bytes, moving the unmarshalling to the client
How this was tested
CI