From cf9a93ad607c7583742d532504a6ed0175f94641 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 19 Oct 2020 12:56:47 +0530 Subject: [PATCH 01/18] acala-network commits squashed --- chain/acala/acala.go | 139 ++++++++++++++ chain/acala/acala_suite_test.go | 13 ++ chain/acala/acala_test.go | 17 ++ chain/acala/address.go | 49 +++++ chain/acala/address_test.go | 1 + chain/acala/decode.go | 322 ++++++++++++++++++++++++++++++++ chain/ethereum/account.go | 151 +++++++++++++++ chain/ethereum/gas.go | 32 ++++ go.mod | 3 +- go.sum | 13 +- infra/acala/Dockerfile | 8 +- 11 files changed, 739 insertions(+), 9 deletions(-) create mode 100644 chain/acala/acala.go create mode 100644 chain/acala/acala_suite_test.go create mode 100644 chain/acala/acala_test.go create mode 100644 chain/acala/address.go create mode 100644 chain/acala/address_test.go create mode 100644 chain/acala/decode.go create mode 100644 chain/ethereum/account.go create mode 100644 chain/ethereum/gas.go diff --git a/chain/acala/acala.go b/chain/acala/acala.go new file mode 100644 index 00000000..da233da5 --- /dev/null +++ b/chain/acala/acala.go @@ -0,0 +1,139 @@ +package acala + +import ( + "bytes" + "context" + "fmt" + + gsrpc "github.com/centrifuge/go-substrate-rpc-client" + "github.com/centrifuge/go-substrate-rpc-client/scale" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/multichain" + "github.com/renproject/multichain/api/address" + "github.com/renproject/pack" + "go.uber.org/zap" +) + +const ( + DefaultClientRPCURL = "http://127.0.0.1:9944" +) + +type ClientOptions struct { + Logger *zap.Logger + rpcURL pack.String +} + +func DefaultClientOptions() ClientOptions { + logger, err := zap.NewDevelopment() + if err != nil { + panic(err) + } + return ClientOptions{ + Logger: logger, + rpcURL: DefaultClientRPCURL, + } +} + +func (opts ClientOptions) WithRPCURL(rpcURL pack.String) ClientOptions { + opts.rpcURL = rpcURL + return opts +} + +type Client struct { + opts ClientOptions + api gsrpc.SubstrateAPI +} + +func NewClient(opts ClientOptions) (*Client, error) { + substrateAPI, err := gsrpc.NewSubstrateAPI(string(opts.rpcURL)) + if err != nil { + return nil, err + } + + return &Client{ + opts: opts, + api: *substrateAPI, + }, nil +} + +func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw) (eventBurnt, error) { + eventRecords := types.EventRecordsRaw(*data) + + events := EventsWithBurn{} + if err := ParseEvents(&eventRecords, meta, &events); err != nil { + return eventBurnt{}, err + } + if len(events.RenToken_Burnt) != 1 { + return eventBurnt{}, fmt.Errorf("expected burn events: %v, got: %v", 1, len(events.RenToken_Burnt)) + } + + return events.RenToken_Burnt[0], nil +} + +func (client *Client) BurnEvent( + ctx context.Context, + asset multichain.Asset, + nonce pack.Bytes32, + blockheight pack.U64, +) (pack.U256, pack.String, pack.U64, error) { + // get metadata + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + // subscribe to system events via storage + key, err := types.CreateStorageKey(meta, "System", "Events", nil, nil) + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + // get the block's hash + blockhash, err := client.api.RPC.Chain.GetBlockHash(blockheight.Uint64()) + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + header, err := client.api.RPC.Chain.GetHeaderLatest() + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + // retrieve raw bytes for the stored data + data, err := client.api.RPC.State.GetStorageRaw(key, blockhash) + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + // decode event data to a burn event + burnEvent, err := decodeEventData(meta, data) + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + // calculate block confirmations for the event + confs := uint64(header.Number) - blockheight.Uint64() + 1 + + // get and encode destination address + dest := types.NewAddressFromAccountID(burnEvent.Dest[:]) + buf := new(bytes.Buffer) + encoder := scale.NewEncoder(buf) + if err := dest.Encode(*encoder); err != nil { + // FIXME: return err instead of panicking + panic(err) + } + addrEncodeDecoder := NewAddressEncodeDecoder() + to, err := addrEncodeDecoder.EncodeAddress(address.RawAddress(pack.NewBytes(buf.Bytes()))) + if err != nil { + // FIXME: return err instead of panicking + panic(err) + } + + return pack.NewU256FromU128(pack.NewU128FromInt(burnEvent.Amount.Int)), pack.String(to), pack.NewU64(confs), nil +} diff --git a/chain/acala/acala_suite_test.go b/chain/acala/acala_suite_test.go new file mode 100644 index 00000000..f91d1e63 --- /dev/null +++ b/chain/acala/acala_suite_test.go @@ -0,0 +1,13 @@ +package acala_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestSubstratecompat(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Acala Suite") +} diff --git a/chain/acala/acala_test.go b/chain/acala/acala_test.go new file mode 100644 index 00000000..55021661 --- /dev/null +++ b/chain/acala/acala_test.go @@ -0,0 +1,17 @@ +package acala_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/renproject/multichain/chain/acala" +) + +var _ = Describe("Substrate client", func() { + Context("when verifying burns", func() { + It("should verify a valid burn", func() { + _, err := acala.NewClient(acala.DefaultClientOptions()) + Expect(err).ToNot(HaveOccurred()) + }) + }) +}) diff --git a/chain/acala/address.go b/chain/acala/address.go new file mode 100644 index 00000000..49e5dc7d --- /dev/null +++ b/chain/acala/address.go @@ -0,0 +1,49 @@ +package acala + +import ( + "fmt" + + "github.com/btcsuite/btcutil/base58" + "github.com/renproject/multichain/api/address" +) + +// AddressDecoder implements the address.Decoder interface. +type AddressDecoder struct{} + +// AddressEncoder implements the address.Encoder interface. +type AddressEncoder struct{} + +// AddressEncodeDecoder implements the address.EncodeDecoder interface. +type AddressEncodeDecoder struct { + AddressEncoder + AddressDecoder +} + +// NewAddressEncodeDecoder constructs a new AddressEncodeDecoder. +func NewAddressEncodeDecoder() AddressEncodeDecoder { + return AddressEncodeDecoder{ + AddressEncoder: AddressEncoder{}, + AddressDecoder: AddressDecoder{}, + } +} + +// DecodeAddress the string using the Bitcoin base58 alphabet. If the string +// does not a 2-byte address type, 32-byte array, and 1-byte checksum, then an +// error is returned. +func (AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) { + data := base58.Decode(string(addr)) + if len(data) != 35 { + return address.RawAddress([]byte{}), fmt.Errorf("expected 35 bytes, got %v bytes", len(data)) + } + return address.RawAddress(data), nil +} + +// EncodeAddress the raw bytes using the Bitcoin base58 alphabet. If the data to +// encode is not a 2-byte address type, 32-byte array, and 1-byte checksum, then +// an error is returned. +func (AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) { + if len(rawAddr) != 35 { + return address.Address(""), fmt.Errorf("expected 35 bytes, got %v bytes", len(rawAddr)) + } + return address.Address(base58.Encode(rawAddr)), nil +} diff --git a/chain/acala/address_test.go b/chain/acala/address_test.go new file mode 100644 index 00000000..dc367890 --- /dev/null +++ b/chain/acala/address_test.go @@ -0,0 +1 @@ +package acala_test diff --git a/chain/acala/decode.go b/chain/acala/decode.go new file mode 100644 index 00000000..3175503f --- /dev/null +++ b/chain/acala/decode.go @@ -0,0 +1,322 @@ +package acala + +import ( + "bytes" + "errors" + "fmt" + "reflect" + + "github.com/centrifuge/go-substrate-rpc-client/scale" + "github.com/centrifuge/go-substrate-rpc-client/types" +) + +type eventMinted struct { + Phase types.Phase + Who types.AccountID + Currency types.U8 + Amount types.U128 + Topics []types.Hash +} + +type eventBurnt struct { + Owner types.AccountID + Dest types.AccountID + Amount types.U128 +} + +type EventsWithMint struct { + types.EventRecords + RenToken_Minted []eventMinted //nolint:stylecheck,golint +} + +type EventsWithBurn struct { + types.EventRecords + RenToken_Burnt []eventBurnt //nolint:stylecheck,golint +} + +// ParseEvents decodes the events records from an EventRecordRaw into a target +// t using the given Metadata m. If this method returns an error like `unable +// to decode Phase for event #x: EOF`, it is likely that you have defined a +// custom event record with a wrong type. For example your custom event record +// has a field with a length prefixed type, such as types.Bytes, where your +// event in reality contains a fixed width type, such as a types.U32. +func ParseEvents(e *types.EventRecordsRaw, m *types.Metadata, t interface{}) error { + // ensure t is a pointer + ttyp := reflect.TypeOf(t) + if ttyp.Kind() != reflect.Ptr { + return errors.New("target must be a pointer, but is " + fmt.Sprint(ttyp)) + } + // ensure t is not a nil pointer + tval := reflect.ValueOf(t) + if tval.IsNil() { + return errors.New("target is a nil pointer") + } + val := tval.Elem() + typ := val.Type() + // ensure val can be set + if !val.CanSet() { + return fmt.Errorf("unsettable value %v", typ) + } + // ensure val points to a struct + if val.Kind() != reflect.Struct { + return fmt.Errorf("target must point to a struct, but is " + fmt.Sprint(typ)) + } + + decoder := scale.NewDecoder(bytes.NewReader(*e)) + + // determine number of events + n, err := decoder.DecodeUintCompact() + if err != nil { + return err + } + + fmt.Println(fmt.Sprintf("found %v events", n)) + + // iterate over events + for i := uint64(0); i < n.Uint64(); i++ { + fmt.Println(fmt.Sprintf("decoding event #%v", i)) + + // decode Phase + phase := types.Phase{} + err := decoder.Decode(&phase) + if err != nil { + return fmt.Errorf("unable to decode Phase for event #%v: %v", i, err) + } + + // decode EventID + id := types.EventID{} + err = decoder.Decode(&id) + if err != nil { + return fmt.Errorf("unable to decode EventID for event #%v: %v", i, err) + } + + fmt.Println(fmt.Sprintf("event #%v has EventID %v", i, id)) + + // ask metadata for method & event name for event + moduleName, eventName, err := m.FindEventNamesForEventID(id) + // moduleName, eventName, err := "System", "ExtrinsicSuccess", nil + if err != nil { + fmt.Printf("unable to find event with EventID %v in metadata for event #%v: %s\n", id, i, err) + continue + // return fmt.Errorf("unable to find event with EventID %v in metadata for event #%v: %s", id, i, err) + } + + fmt.Println(fmt.Sprintf("event #%v is in module %v with event name %v", i, moduleName, eventName)) + + // check whether name for eventID exists in t + field := val.FieldByName(fmt.Sprintf("%v_%v", moduleName, eventName)) + if !field.IsValid() { + eventParams, err := findEventForEventID(m.AsMetadataV10, id) + if err != nil { + return fmt.Errorf("unable to find event with EventID %v in metadata for event #%v: %s", id, i, err) + } + + for j := 0; j < len(eventParams.Args); j++ { + fmt.Printf("decoding field: %v (%v)\n", j, eventParams.Args[j]) + switch eventParams.Args[j] { + case "u8": + param := types.U8(0) + err = decoder.Decode(param) + case "u16": + param := types.U16(0) + err = decoder.Decode(param) + case "u32": + param := types.U32(0) + err = decoder.Decode(param) + case "u64": + param := types.U64(0) + err = decoder.Decode(param) + case "u128": + param := types.U128{} + err = decoder.Decode(param) + case "u256": + param := types.U256{} + err = decoder.Decode(param) + case "Phase": + param := types.Phase{} + err = decoder.Decode(param) + case "DispatchInfo": + param := types.DispatchInfo{} + err = decoder.Decode(param) + case "DispatchError": + param := types.DispatchError{} + err = decoder.Decode(param) + case "AccountId": + param := types.AccountID{} + err = decoder.Decode(param) + case "AccountIndex": + param := types.AccountIndex(0) + err = decoder.Decode(param) + // case "Balance": + // param := types.Balance{} + // err = decoder.Decode(param) + // case "Status": + // param := types.Status{} + // err = decoder.Decode(param) + case "bool": + param := types.Bool(false) + err = decoder.Decode(param) + // case "CallHash": + // param := types.CallHash{} + // err = decoder.Decode(param) + // case "Timepoint": + // param := types.Timepoint{} + // err = decoder.Decode(param) + // case "ProposalIndex": + // param := types.ProposalIndex{} + // err = decoder.Decode(param) + case "Hash": + param := types.Hash{} + err = decoder.Decode(param) + // case "EraIndex": + // param := types.EraIndex{} + // err = decoder.Decode(param) + // case "SessionIndex": + // param := types.SessionIndex{} + // err = decoder.Decode(param) + // case "ElectionCompute": + // param := types.ElectionCompute{} + // err = decoder.Decode(param) + // case "MemberCount": + // param := types.MemberCount{} + // err = decoder.Decode(param) + // case "sp_std": + // param := // types.sp_std::marker::PhantomData<(AccountId, Event)>{} + // err = decoder.Decode(param) + // case "Vec": + // param := // types.Vec<(OracleKey, OracleValue)>{} + // err = decoder.Decode(param) + // case "CurrencyId": + // param := types.CurrencyId{} + // err = decoder.Decode(param) + // case "Amount": + // param := types.Amount{} + // err = decoder.Decode(param) + // case "VestingSchedule": + // param := types.VestingSchedule{} + // err = decoder.Decode(param) + case "BlockNumber": + param := types.BlockNumber(0) + err = decoder.Decode(param) + // case "DispatchId": + // param := types.DispatchId{} + // err = decoder.Decode(param) + case "StorageKey": + param := types.StorageKey{} + err = decoder.Decode(param) + // case "StorageValue": + // param := types.StorageValue{} + // err = decoder.Decode(param) + // case "AuctionId": + // param := types.AuctionId{} + // err = decoder.Decode(param) + // case "Price": + // param := types.Price{} + // err = decoder.Decode(param) + // case "DebitAmount": + // param := types.DebitAmount{} + // err = decoder.Decode(param) + // case "DebitBalance": + // param := types.DebitBalance{} + // err = decoder.Decode(param) + // case "Share": + // param := types.Share{} + // err = decoder.Decode(param) + // case "LiquidationStrategy": + // param := types.LiquidationStrategy{} + // err = decoder.Decode(param) + // case "Option": + // param := types.Option{} + // err = decoder.Decode(param) + // case "Option": + // param := types.Option{} + // err = decoder.Decode(param) + // case "Rate": + // param := types.Rate{} + // err = decoder.Decode(param) + // case "Vec": + // param := // types.Vec<(CurrencyId, Balance)>{} + // err = decoder.Decode(param) + // case "AirDropCurrencyId": + // param := types.AirDropCurrencyId{} + // err = decoder.Decode(param) + // case "Vec": + // param := // types.Vec{} + // err = decoder.Decode(param) + + case "AuthorityList": + param := []struct { + AuthorityID types.AuthorityID + AuthorityWeight types.U64 + }{} + err = decoder.Decode(param) + default: + return fmt.Errorf("unable to decode field %v_%v arg #%v %v", moduleName, + eventName, j, eventParams.Args[j]) + } + } + + fmt.Printf("unable to find field %v_%v for event #%v with EventID %v\n", moduleName, eventName, i, id) + continue + // return fmt.Errorf("unable to find field %v_%v for event #%v with EventID %v", moduleName, eventName, i, id) + } + + // create a pointer to with the correct type that will hold the decoded event + holder := reflect.New(field.Type().Elem()) + + // ensure first field is for Phase, last field is for Topics + numFields := holder.Elem().NumField() + fmt.Printf("numFields: %v\n", numFields) + if numFields < 2 { + return fmt.Errorf("expected event #%v with EventID %v, field %v_%v to have at least 2 fields "+ + "(for Phase and Topics), but has %v fields", i, id, moduleName, eventName, numFields) + } + phaseField := holder.Elem().FieldByIndex([]int{0}) + if phaseField.Type() != reflect.TypeOf(phase) { + return fmt.Errorf("expected the first field of event #%v with EventID %v, field %v_%v to be of type "+ + "types.Phase, but got %v", i, id, moduleName, eventName, phaseField.Type()) + } + topicsField := holder.Elem().FieldByIndex([]int{numFields - 1}) + if topicsField.Type() != reflect.TypeOf([]types.Hash{}) { + return fmt.Errorf("expected the last field of event #%v with EventID %v, field %v_%v to be of type "+ + "[]types.Hash for Topics, but got %v", i, id, moduleName, eventName, topicsField.Type()) + } + + // set the phase we decoded earlier + phaseField.Set(reflect.ValueOf(phase)) + + // set the remaining fields + for j := 1; j < numFields; j++ { + fmt.Printf("decoding field: %v\n", j) + err = decoder.Decode(holder.Elem().FieldByIndex([]int{j}).Addr().Interface()) + if err != nil { + return fmt.Errorf("unable to decode field %v event #%v with EventID %v, field %v_%v: %v", j, i, id, moduleName, + eventName, err) + } + } + + // add the decoded event to the slice + field.Set(reflect.Append(field, holder.Elem())) + + fmt.Println(fmt.Sprintf("decoded event #%v", i)) + } + return nil +} + +func findEventForEventID(m types.MetadataV10, eventID types.EventID) (*types.EventMetadataV4, error) { + mi := uint8(0) + for _, mod := range m.Modules { + if !mod.HasEvents { + continue + } + if mi != eventID[0] { + mi++ + continue + } + if int(eventID[1]) >= len(mod.Events) { + return nil, fmt.Errorf("event index %v for module %v out of range", eventID[1], mod.Name) + } + return &mod.Events[eventID[1]], nil + } + return nil, fmt.Errorf("module index %v out of range", eventID[0]) +} diff --git a/chain/ethereum/account.go b/chain/ethereum/account.go new file mode 100644 index 00000000..0ef7c7c4 --- /dev/null +++ b/chain/ethereum/account.go @@ -0,0 +1,151 @@ +package ethereum + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" + "github.com/renproject/multichain/api/account" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/pack" +) + +type TxBuilder struct { + config *params.ChainConfig +} + +func NewTxBuilder(config *params.ChainConfig) TxBuilder { + return TxBuilder{config: config} +} + +func (txBuilder TxBuilder) BuildTx( + from, to address.Address, + value, nonce pack.U256, + gasPrice, gasLimit pack.U256, + payload pack.Bytes, +) (account.Tx, error) { + toAddr, err := NewAddressFromHex(string(to)) + if err != nil { + return nil, fmt.Errorf("decoding address: %v", err) + } + fromAddr, err := NewAddressFromHex(string(from)) + if err != nil { + return nil, fmt.Errorf("decoding address: %v", err) + } + + tx := types.NewTransaction(nonce.Int().Uint64(), common.Address(toAddr), value.Int(), gasLimit.Int().Uint64(), gasPrice.Int(), []byte(payload)) + + signer := types.MakeSigner(txBuilder.config, nil) + signed := false + + return &Tx{fromAddr, tx, signer, signed}, nil +} + +type Tx struct { + from Address + + tx *types.Transaction + + signer types.Signer + signed bool +} + +func (tx *Tx) Hash() pack.Bytes { + return pack.NewBytes(tx.tx.Hash().Bytes()) +} + +func (tx *Tx) From() address.Address { + return address.Address(tx.from.String()) +} + +func (tx *Tx) To() address.Address { + return address.Address(tx.tx.To().String()) +} + +func (tx *Tx) Value() pack.U256 { + return pack.NewU256FromInt(tx.tx.Value()) +} + +func (tx *Tx) Nonce() pack.U256 { + return pack.NewU256FromU64(pack.NewU64(tx.tx.Nonce())) +} + +func (tx *Tx) Payload() contract.CallData { + return contract.CallData(pack.NewBytes(tx.tx.Data())) +} + +func (tx *Tx) Sighashes() ([]pack.Bytes32, error) { + sighash := tx.signer.Hash(tx.tx) + return []pack.Bytes32{pack.NewBytes32(sighash)}, nil +} + +func (tx *Tx) Sign(signatures []pack.Bytes65, pubKey pack.Bytes) error { + if tx.signed { + return fmt.Errorf("already signed") + } + + if len(signatures) != 1 { + return fmt.Errorf("expected 1 signature, found: %v", len(signatures)) + } + + signedTx, err := tx.tx.WithSignature(tx.signer, signatures[0].Bytes()) + if err != nil { + return err + } + + tx.tx = signedTx + tx.signed = true + return nil +} + +func (tx *Tx) Serialize() (pack.Bytes, error) { + serialized, err := tx.tx.MarshalJSON() + if err != nil { + return pack.Bytes{}, err + } + + return pack.NewBytes(serialized), nil +} + +type EthClient struct { + client *ethclient.Client +} + +func NewClient(rpcURL pack.String) (*EthClient, error) { + client, err := ethclient.Dial(string(rpcURL)) + if err != nil { + return nil, fmt.Errorf("dialing RPC URL %v: %v", rpcURL, err) + } + + return &EthClient{client}, nil +} + +func (client EthClient) Tx(ctx context.Context, txId pack.Bytes) (account.Tx, pack.U64, error) { + txHash := common.BytesToHash(txId) + tx, isPending, err := client.client.TransactionByHash(ctx, txHash) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching tx: %v", err) + } + if isPending { + return nil, pack.NewU64(0), fmt.Errorf("tx not confirmed") + } + txReceipt, err := client.client.TransactionReceipt(ctx, txHash) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching tx receipt: %v", err) + } + block, err := client.client.BlockByNumber(ctx, nil) + if err != nil { + return nil, pack.NewU64(0), fmt.Errorf("fetching current block: %v", err) + } + confs := block.NumberU64() - txReceipt.BlockNumber.Uint64() + 1 + + return &Tx{tx: tx}, pack.NewU64(confs), nil +} + +func (client EthClient) SubmitTx(ctx context.Context, tx account.Tx) error { + panic("unimplemented") +} diff --git a/chain/ethereum/gas.go b/chain/ethereum/gas.go new file mode 100644 index 00000000..f22e568b --- /dev/null +++ b/chain/ethereum/gas.go @@ -0,0 +1,32 @@ +package ethereum + +import ( + "context" + + "github.com/renproject/pack" +) + +// A GasEstimator returns the gas price (in wei) that is needed in order to +// confirm transactions with an estimated maximum delay of one block. In +// distributed networks that collectively build, sign, and submit transactions, +// it is important that all nodes in the network have reached consensus on the +// gas price. +type GasEstimator struct { + wei pack.U256 +} + +// NewGasEstimator returns a simple gas estimator that always returns the given +// gas price (in wei) to be used for broadcasting an Ethereum transaction. +func NewGasEstimator(wei pack.U256) GasEstimator { + return GasEstimator{ + wei: wei, + } +} + +// EstimateGas returns the number of wei that is needed in order to confirm +// transactions with an estimated maximum delay of one block. It is the +// responsibility of the caller to know the number of bytes in their +// transaction. +func (gasEstimator GasEstimator) EstimateGas(_ context.Context) (pack.U256, pack.U256, error) { + return gasEstimator.wei, gasEstimator.wei, nil +} diff --git a/go.mod b/go.mod index cd2b81b0..d037561a 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.14 require ( github.com/btcsuite/btcd v0.21.0-beta github.com/btcsuite/btcutil v1.0.2 - github.com/centrifuge/go-substrate-rpc-client v1.1.0 + github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible github.com/codahale/blake2 v0.0.0-20150924215134-8d10d0420cbf github.com/cosmos/cosmos-sdk v0.39.1 github.com/ethereum/go-ethereum v1.9.20 @@ -18,6 +18,7 @@ require ( github.com/multiformats/go-varint v0.0.6 github.com/onsi/ginkgo v1.14.0 github.com/onsi/gomega v1.10.1 + github.com/pierrec/xxHash v0.1.5 // indirect github.com/renproject/id v0.4.2 github.com/renproject/pack v0.2.5 github.com/renproject/surge v1.2.6 diff --git a/go.sum b/go.sum index 2df88715..9bf1e8e9 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,7 @@ dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBr dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= +github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs= github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4= github.com/99designs/keyring v1.1.3 h1:mEV3iyZWjkxQ7R8ia8GcG97vCX5zQQ7n4o8R2BylwQY= github.com/99designs/keyring v1.1.3/go.mod h1:657DQuMrBZRtuL/voxVyiyb6zpMehlm5vLB9Qwrv904= @@ -78,6 +79,7 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847 h1:rtI0fD4oG/8eVokGVPYJEW1F88p1ZNgXiEIs9thEE4A= github.com/aristanetworks/goarista v0.0.0-20170210015632-ea17b1a17847/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= @@ -132,7 +134,8 @@ github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7 github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/centrifuge/go-substrate-rpc-client v1.1.0/go.mod h1:GBMLH8MQs5g4FcrytcMm9uRgBnTL1LIkNTue6lUPhZU= +github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible h1:FvPewruOgelqA/DVBdX7/Q6znUGGQ+g0ciG5tA2Fk98= +github.com/centrifuge/go-substrate-rpc-client v2.0.0+incompatible/go.mod h1:GBMLH8MQs5g4FcrytcMm9uRgBnTL1LIkNTue6lUPhZU= github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= @@ -192,6 +195,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20170701192655-dcfb0a7ac018/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= github.com/davidlazar/go-crypto v0.0.0-20190912175916-7055855a373f/go.mod h1:rQYf4tfk5sSwFsnDg3qYaBxSjsD9S8+59vW0dKUgme4= +github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea h1:j4317fAZh7X6GqbFowYdYdI0L9bwxL07jyPZIdepyZ0= github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= github.com/decred/dcrd/lru v1.0.0/go.mod h1:mxKOwFd7lFjN2GZYsiz/ecgqR6kkYAl+0pz0tEMk218= github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e h1:lj77EKYUpYXTd8CD/+QMIf8b6OIOTsfEBSXiAzuEHTU= @@ -336,9 +340,9 @@ github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNI github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus v0.0.0-20190402143921-271e53dc4968/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0= github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= @@ -439,7 +443,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= -github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c h1:6rhixN/i8ZofjG1Y75iExal34USq5p+wiN1tpie8IrU= github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c/go.mod h1:NMPJylDgVpX0MLRlPy15sqSwOFv/U1GZ2m21JhFfek0= github.com/gtank/merlin v0.1.1-0.20191105220539-8318aed1a79f/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s= github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is= @@ -1187,6 +1190,8 @@ github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9 github.com/peterh/liner v1.1.1-0.20190123174540-a2c9a5303de7/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0= github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo= +github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -1266,6 +1271,7 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v0.0.0-20160617231935-a62a804a8a00/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubrpZHtQLnOf6EwhN2hEMusxZOhcW9H3UQ= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -1278,6 +1284,7 @@ github.com/sercand/kuberesolver v2.1.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJP github.com/sercand/kuberesolver v2.4.0+incompatible/go.mod h1:lWF3GL0xptCB/vCiJPl/ZshwPsX/n4Y7u0CW9E7aQIQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v2.20.5+incompatible h1:tYH07UPoQt0OCQdgWWMgYHy3/a9bcxNpBIysykNIP7I= github.com/shirou/gopsutil v2.20.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index a988fb74..d6a685b1 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -6,6 +6,8 @@ RUN apt-get install --yes --fix-missing --no-install-recommends build-essential # Install Rust RUN curl https://sh.rustup.rs -sSf | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +RUN rustup default nightly +RUN rustup update # Clone repository RUN git clone https://github.com/AcalaNetwork/Acala.git @@ -13,13 +15,9 @@ RUN git clone https://github.com/AcalaNetwork/Acala.git RUN mv Acala /app WORKDIR /app -# TEMPORARY: use the branch that has a good reference to the submodules -# TODO: remove when the `master` branch of Acala is updated +# Make sure submodule.recurse is set to true to make life with submodule easier. RUN git fetch -RUN git checkout update-orml RUN git pull - -# Make sure submodule.recurse is set to true to make life with submodule easier. RUN git config --global submodule.recurse true # Build From b86a7b3e1fee6478027da06c41f6e1887be8e8ef Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 20 Oct 2020 00:01:25 +0530 Subject: [PATCH 02/18] feat: mint and burn renBTC on Acala --- chain/acala/acala.go | 9 ++-- chain/acala/mint_burn.go | 92 ++++++++++++++++++++++++++++++++ chain/acala/mint_burn_test.go | 98 +++++++++++++++++++++++++++++++++++ go.sum | 1 + infra/acala/run.sh | 2 +- 5 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 chain/acala/mint_burn.go create mode 100644 chain/acala/mint_burn_test.go diff --git a/chain/acala/acala.go b/chain/acala/acala.go index da233da5..f41f08f1 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -8,14 +8,13 @@ import ( gsrpc "github.com/centrifuge/go-substrate-rpc-client" "github.com/centrifuge/go-substrate-rpc-client/scale" "github.com/centrifuge/go-substrate-rpc-client/types" - "github.com/renproject/multichain" "github.com/renproject/multichain/api/address" "github.com/renproject/pack" "go.uber.org/zap" ) const ( - DefaultClientRPCURL = "http://127.0.0.1:9944" + DefaultClientRPCURL = "ws://127.0.0.1:9944" ) type ClientOptions struct { @@ -72,10 +71,8 @@ func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw) (eventBur func (client *Client) BurnEvent( ctx context.Context, - asset multichain.Asset, - nonce pack.Bytes32, blockheight pack.U64, -) (pack.U256, pack.String, pack.U64, error) { +) (pack.U256, address.Address, pack.U64, error) { // get metadata meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { @@ -135,5 +132,5 @@ func (client *Client) BurnEvent( panic(err) } - return pack.NewU256FromU128(pack.NewU128FromInt(burnEvent.Amount.Int)), pack.String(to), pack.NewU64(confs), nil + return pack.NewU256FromInt(burnEvent.Amount.Int), address.Address(pack.String(to)), pack.NewU64(confs), nil } diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go new file mode 100644 index 00000000..001a097c --- /dev/null +++ b/chain/acala/mint_burn.go @@ -0,0 +1,92 @@ +package acala + +import ( + "context" + "fmt" + + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/pack" +) + +func (client *Client) Mint(ctx context.Context, pHash, nHash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + alice, err := types.NewAddressFromHexAccountID("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") + if err != nil { + return pack.Bytes{}, fmt.Errorf("decode address: %v", err) + } + + c, err := types.NewCall(meta, "RenVmBridge.mint", alice, pHash, types.NewUCompactFromUInt(amount), nHash, sig) + if err != nil { + return pack.Bytes{}, fmt.Errorf("construct call: %v", err) + } + + hash, err := client.api.RPC.Author.SubmitExtrinsic(types.NewExtrinsic(c)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + } + + return pack.NewBytes(hash[:]), nil +} + +func (client *Client) Burn(ctx context.Context, recipient pack.Bytes, amount uint64) (pack.Bytes, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + c, err := types.NewCall(meta, "RenVmBridge.burn", recipient, types.NewUCompactFromUInt(amount)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("construct call: %v", err) + } + + ext := types.NewExtrinsic(c) + + genesisHash, err := client.api.RPC.Chain.GetBlockHash(0) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + } + + rv, err := client.api.RPC.State.GetRuntimeVersionLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) + } + + key, err := types.CreateStorageKey(meta, "System", "Account", signature.TestKeyringPairAlice.PublicKey, nil) + if err != nil { + return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + } + + var accountInfo types.AccountInfo + ok, err := client.api.RPC.State.GetStorageLatest(key, &accountInfo) + if err != nil || !ok { + return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + nonce := uint32(accountInfo.Nonce) + + o := types.SignatureOptions{ + BlockHash: genesisHash, + Era: types.ExtrinsicEra{IsMortalEra: false}, + GenesisHash: genesisHash, + Nonce: types.NewUCompactFromUInt(uint64(nonce)), + SpecVersion: rv.SpecVersion, + Tip: types.NewUCompactFromUInt(0), + } + + err = ext.Sign(signature.TestKeyringPairAlice, o) + if err != nil { + return pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) + } + + hash, err := client.api.RPC.Author.SubmitExtrinsic(ext) + if err != nil { + return pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + } + + return pack.NewBytes(hash[:]), nil +} diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go new file mode 100644 index 00000000..97c3adda --- /dev/null +++ b/chain/acala/mint_burn_test.go @@ -0,0 +1,98 @@ +package acala_test + +import ( + "context" + "fmt" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/multichain" + "github.com/renproject/multichain/chain/acala" + "github.com/renproject/multichain/chain/bitcoin" + "github.com/renproject/pack" +) + +var _ = Describe("Mint Burn", func() { + client, err := acala.NewClient(acala.DefaultClientOptions()) + Expect(err).NotTo(HaveOccurred()) + + opts := types.SerDeOptions{NoPalletIndices: true} + types.SetSerDeOptions(opts) + + Context("when minting over renbridge", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Ignore recipient + pHash, nHash, sig, amount, _ := getMintParams() + + txhash, err := client.Mint(ctx, pHash, nHash, sig, amount) + Expect(err).NotTo(HaveOccurred()) + + fmt.Printf("txhash = %v\n", txhash) + }) + }) + + Context("when burning over renbridge", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // Ignore phash, nhash, sig + _, _, _, amount, recipient := getMintParams() + + txhash, err := client.Burn(ctx, recipient, amount) + Expect(err).NotTo(HaveOccurred()) + + fmt.Printf("txhash = %v\n", txhash) + }) + }) + + Context("when reading burn info", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + // FIXME: set this appropriately + blockheight := pack.U64(uint64(1)) + amount, recipient, confs, err := client.BurnEvent(ctx, blockheight) + Expect(err).NotTo(HaveOccurred()) + + fmt.Printf("amount = %v\n", amount) + fmt.Printf("recipient = %v\n", recipient) + fmt.Printf("confs = %v\n", confs) + }) + }) +}) + +func getMintParams() (pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, pack.Bytes) { + pHashHex := "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + nHashHex := "0x1f1a01537e418859cd99eb15099dcdcb98483ad723cd20ccaa5a2677b755572b" + sigHex := "0x60930a2c1c933c30bb7f88d6183e81a71394d81ead26d68a3b12d6b4efdc3ef563f91a945375b71d78accb5860e8154bc01681577db544e2e53611aa14613a9c1b" + amount := uint64(95000) + recipient := multichain.Address(pack.String("miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6")) + + pHashBytes, err := types.HexDecodeString(pHashHex) + Expect(err).NotTo(HaveOccurred()) + nHashBytes, err := types.HexDecodeString(nHashHex) + Expect(err).NotTo(HaveOccurred()) + sigBytes, err := types.HexDecodeString(sigHex) + Expect(err).NotTo(HaveOccurred()) + + btcEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) + rawAddr, err := btcEncodeDecoder.DecodeAddress(recipient) + Expect(err).NotTo(HaveOccurred()) + + var pHash [32]byte + var nHash [32]byte + var sig [65]byte + copy(pHash[:], pHashBytes) + copy(nHash[:], nHashBytes) + copy(sig[:], sigBytes) + + return pack.Bytes32(pHash), pack.Bytes32(nHash), pack.Bytes65(sig), amount, pack.Bytes(rawAddr) +} diff --git a/go.sum b/go.sum index 9bf1e8e9..5aefbad4 100644 --- a/go.sum +++ b/go.sum @@ -1842,6 +1842,7 @@ gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= gopkg.in/olebedev/go-duktape.v3 v3.0.0-20200619000410-60c24ae608a6/go.mod h1:uAJfkITjFhyEEuUfm7bsmCZRbW5WRq8s9EY8HZ6hCns= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= diff --git a/infra/acala/run.sh b/infra/acala/run.sh index a48318f1..4836221b 100644 --- a/infra/acala/run.sh +++ b/infra/acala/run.sh @@ -3,7 +3,7 @@ ADDRESS=$1 # Start cd /app -make run +SKIP_WASM_BUILD= cargo run -- --dev --execution=native -lruntime=debug --ws-external --rpc-external sleep 10 # Print setup From 515b2556e581d1828c3fe79a382fb59285921e0a Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Wed, 21 Oct 2020 00:59:48 +0530 Subject: [PATCH 03/18] fix: acala build (temporarily downgrading nightly rust) --- infra/acala/Dockerfile | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index d6a685b1..a8c94627 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:xenial +FROM ubuntu:bionic RUN apt-get update && apt-get install --yes --fix-missing software-properties-common curl git clang RUN apt-get install --yes --fix-missing --no-install-recommends build-essential @@ -6,8 +6,6 @@ RUN apt-get install --yes --fix-missing --no-install-recommends build-essential # Install Rust RUN curl https://sh.rustup.rs -sSf | sh -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" -RUN rustup default nightly -RUN rustup update # Clone repository RUN git clone https://github.com/AcalaNetwork/Acala.git @@ -21,7 +19,10 @@ RUN git pull RUN git config --global submodule.recurse true # Build -RUN make init +RUN rustup default nightly-2020-09-27 +RUN rustup target add wasm32-unknown-unknown --toolchain nightly-2020-09-27 +RUN make submodule +RUN make build-full RUN make build WORKDIR / From 0a9e66a93bdd27e7f3c80d20357902be3438f61b Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 22 Oct 2020 00:21:20 +0530 Subject: [PATCH 04/18] feat: burn RenBTC on Acala | construct random valid RenVM sigs --- chain/acala/mint_burn.go | 35 +++++++------ chain/acala/mint_burn_test.go | 93 ++++++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 44 deletions(-) diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index 001a097c..2abc658a 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -9,18 +9,17 @@ import ( "github.com/renproject/pack" ) -func (client *Client) Mint(ctx context.Context, pHash, nHash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { +func (client *Client) Mint(ctx context.Context, minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { + opts := types.SerDeOptions{NoPalletIndices: true} + types.SetSerDeOptions(opts) + meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) } - alice, err := types.NewAddressFromHexAccountID("0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") - if err != nil { - return pack.Bytes{}, fmt.Errorf("decode address: %v", err) - } - - c, err := types.NewCall(meta, "RenVmBridge.mint", alice, pHash, types.NewUCompactFromUInt(amount), nHash, sig) + alice := types.NewAddressFromAccountID(minterKey.PublicKey) + c, err := types.NewCall(meta, "RenVmBridge.mint", alice, phash, types.NewUCompactFromUInt(amount), nhash, sig) if err != nil { return pack.Bytes{}, fmt.Errorf("construct call: %v", err) } @@ -33,7 +32,10 @@ func (client *Client) Mint(ctx context.Context, pHash, nHash pack.Bytes32, sig p return pack.NewBytes(hash[:]), nil } -func (client *Client) Burn(ctx context.Context, recipient pack.Bytes, amount uint64) (pack.Bytes, error) { +func (client *Client) Burn(ctx context.Context, burnerKey signature.KeyringPair, recipient [20]byte, amount uint64) (pack.Bytes, error) { + opts := types.SerDeOptions{NoPalletIndices: false} + types.SetSerDeOptions(opts) + meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) @@ -56,7 +58,7 @@ func (client *Client) Burn(ctx context.Context, recipient pack.Bytes, amount uin return pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) } - key, err := types.CreateStorageKey(meta, "System", "Account", signature.TestKeyringPairAlice.PublicKey, nil) + key, err := types.CreateStorageKey(meta, "System", "Account", burnerKey.PublicKey, nil) if err != nil { return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) } @@ -70,15 +72,16 @@ func (client *Client) Burn(ctx context.Context, recipient pack.Bytes, amount uin nonce := uint32(accountInfo.Nonce) o := types.SignatureOptions{ - BlockHash: genesisHash, - Era: types.ExtrinsicEra{IsMortalEra: false}, - GenesisHash: genesisHash, - Nonce: types.NewUCompactFromUInt(uint64(nonce)), - SpecVersion: rv.SpecVersion, - Tip: types.NewUCompactFromUInt(0), + BlockHash: genesisHash, + Era: types.ExtrinsicEra{IsMortalEra: false}, + GenesisHash: genesisHash, + Nonce: types.NewUCompactFromUInt(uint64(nonce)), + SpecVersion: rv.SpecVersion, + Tip: types.NewUCompactFromUInt(0), + TransactionVersion: rv.TransactionVersion, } - err = ext.Sign(signature.TestKeyringPairAlice, o) + err = ext.Sign(burnerKey, o) if err != nil { return pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) } diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 97c3adda..914f01e9 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -2,38 +2,41 @@ package acala_test import ( "context" + "encoding/hex" "fmt" + "math/rand" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/btcsuite/btcd/chaincfg" - "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/ethereum/go-ethereum/crypto" + "github.com/renproject/id" "github.com/renproject/multichain" "github.com/renproject/multichain/chain/acala" "github.com/renproject/multichain/chain/bitcoin" + "github.com/renproject/multichain/chain/ethereum" "github.com/renproject/pack" + "github.com/renproject/surge" ) var _ = Describe("Mint Burn", func() { client, err := acala.NewClient(acala.DefaultClientOptions()) Expect(err).NotTo(HaveOccurred()) - opts := types.SerDeOptions{NoPalletIndices: true} - types.SetSerDeOptions(opts) - Context("when minting over renbridge", func() { It("should succeed", func() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() // Ignore recipient - pHash, nHash, sig, amount, _ := getMintParams() + alice, phash, nhash, sig, amount, _ := constructMintParams() - txhash, err := client.Mint(ctx, pHash, nHash, sig, amount) + txhash, err := client.Mint(ctx, alice, phash, nhash, sig, amount) Expect(err).NotTo(HaveOccurred()) - fmt.Printf("txhash = %v\n", txhash) + fmt.Printf("mint tx = %v\n", hex.EncodeToString(txhash)) }) }) @@ -43,12 +46,12 @@ var _ = Describe("Mint Burn", func() { defer cancel() // Ignore phash, nhash, sig - _, _, _, amount, recipient := getMintParams() + alice, _, _, _, amount, recipient := constructMintParams() - txhash, err := client.Burn(ctx, recipient, amount) + txhash, err := client.Burn(ctx, alice, recipient, amount) Expect(err).NotTo(HaveOccurred()) - fmt.Printf("txhash = %v\n", txhash) + fmt.Printf("burn tx = %v\n", hex.EncodeToString(txhash)) }) }) @@ -69,30 +72,64 @@ var _ = Describe("Mint Burn", func() { }) }) -func getMintParams() (pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, pack.Bytes) { - pHashHex := "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" - nHashHex := "0x1f1a01537e418859cd99eb15099dcdcb98483ad723cd20ccaa5a2677b755572b" - sigHex := "0x60930a2c1c933c30bb7f88d6183e81a71394d81ead26d68a3b12d6b4efdc3ef563f91a945375b71d78accb5860e8154bc01681577db544e2e53611aa14613a9c1b" - amount := uint64(95000) - recipient := multichain.Address(pack.String("miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6")) +func constructMintParams() (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, [20]byte) { + // Get RenVM priv key. + renVmPrivKeyBytes, err := hex.DecodeString("c44700049a72c02bbacbec25551190427315f046c1f656f23884949da3fbdc3a") + Expect(err).NotTo(HaveOccurred()) + renVmPrivKey := id.PrivKey{} + err = surge.FromBinary(&renVmPrivKey, renVmPrivKeyBytes) + Expect(err).NotTo(HaveOccurred()) - pHashBytes, err := types.HexDecodeString(pHashHex) + // Get random pHash and nHash. + phashBytes := make([]byte, 32) + nhashBytes := make([]byte, 32) + _, err = rand.Read(phashBytes) Expect(err).NotTo(HaveOccurred()) - nHashBytes, err := types.HexDecodeString(nHashHex) + _, err = rand.Read(nhashBytes) Expect(err).NotTo(HaveOccurred()) - sigBytes, err := types.HexDecodeString(sigHex) + + // Amount to be minted. + amount := uint64(25000) + + // Selector for this cross-chain mint. + token, err := hex.DecodeString("0000000000000000000000000a9add98c076448cbcfacf5e457da12ddbef4a8f") + Expect(err).NotTo(HaveOccurred()) + token32 := [32]byte{} + copy(token32[:], token[:]) + + // Initialise message args + sighash32 := [32]byte{} + phash32 := [32]byte{} + nhash32 := [32]byte{} + to := [32]byte{} + rawAddr, err := hex.DecodeString("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") // Alice. Expect(err).NotTo(HaveOccurred()) - btcEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) - rawAddr, err := btcEncodeDecoder.DecodeAddress(recipient) + // Get message sighash. + copy(to[:], rawAddr) + copy(phash32[:], phashBytes) + copy(nhash32[:], nhashBytes) + copy(sighash32[:], crypto.Keccak256(ethereum.Encode( + pack.Bytes32(phash32), + pack.NewU256FromUint64(amount), + pack.Bytes32(token32), + pack.Bytes32(to), + pack.Bytes32(nhash32), + ))) + + // Sign the sighash. + hash := id.Hash(sighash32) + sig65, err := renVmPrivKey.Sign(&hash) Expect(err).NotTo(HaveOccurred()) + sig65[64] = sig65[64] + 27 - var pHash [32]byte - var nHash [32]byte - var sig [65]byte - copy(pHash[:], pHashBytes) - copy(nHash[:], nHashBytes) - copy(sig[:], sigBytes) + // Get the address of the burn recipient. + recipientAddr := multichain.Address(pack.String("miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6")) + btcEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) + rawRecipientAddr, err := btcEncodeDecoder.DecodeAddress(recipientAddr) + Expect(err).NotTo(HaveOccurred()) + recipient := [20]byte{} + copy(recipient[:], rawRecipientAddr) - return pack.Bytes32(pHash), pack.Bytes32(nHash), pack.Bytes65(sig), amount, pack.Bytes(rawAddr) + return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), amount, recipient } From 2950cdd154d7597dd8f94a8b0bb5b8bffe25083a Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 23 Oct 2020 03:36:04 +0530 Subject: [PATCH 05/18] test: fetch token balances and complete mint-burn tests --- chain/acala/mint_burn.go | 33 +++++++++++++++++++-- chain/acala/mint_burn_test.go | 56 ++++++++++++++++++++++------------- 2 files changed, 65 insertions(+), 24 deletions(-) diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index 2abc658a..565c2197 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -1,15 +1,15 @@ package acala import ( - "context" "fmt" + "math/big" "github.com/centrifuge/go-substrate-rpc-client/signature" "github.com/centrifuge/go-substrate-rpc-client/types" "github.com/renproject/pack" ) -func (client *Client) Mint(ctx context.Context, minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { +func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { opts := types.SerDeOptions{NoPalletIndices: true} types.SetSerDeOptions(opts) @@ -32,7 +32,7 @@ func (client *Client) Mint(ctx context.Context, minterKey signature.KeyringPair, return pack.NewBytes(hash[:]), nil } -func (client *Client) Burn(ctx context.Context, burnerKey signature.KeyringPair, recipient [20]byte, amount uint64) (pack.Bytes, error) { +func (client *Client) Burn(burnerKey signature.KeyringPair, recipient [20]byte, amount uint64) (pack.Bytes, error) { opts := types.SerDeOptions{NoPalletIndices: false} types.SetSerDeOptions(opts) @@ -93,3 +93,30 @@ func (client *Client) Burn(ctx context.Context, burnerKey signature.KeyringPair, return pack.NewBytes(hash[:]), nil } + +type TokenAccount struct { + Free types.U128 + Reserved types.U128 + Frozen types.U128 +} + +func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.U256{}, fmt.Errorf("get metadata: %v", err) + } + + key, err := types.CreateStorageKey(meta, "Tokens", "Accounts", user.PublicKey, []byte{0, 5}) + if err != nil { + return pack.U256{}, fmt.Errorf("create storage key: %v", err) + } + + var data TokenAccount + ok, err := client.api.RPC.State.GetStorageLatest(key, &data) + if err != nil || !ok { + return pack.U256{}, fmt.Errorf("get storage: %v", err) + } + + balance := big.NewInt(0).SetBytes(data.Free.Bytes()) + return pack.NewU256FromInt(balance), nil +} diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 914f01e9..ca94d71e 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "fmt" "math/rand" + "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -22,36 +23,48 @@ import ( ) var _ = Describe("Mint Burn", func() { + r := rand.New(rand.NewSource(GinkgoRandomSeed())) + client, err := acala.NewClient(acala.DefaultClientOptions()) Expect(err).NotTo(HaveOccurred()) - Context("when minting over renbridge", func() { + FContext("when minting over renbridge", func() { It("should succeed", func() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + // Ignore recipient and burn amount. + alice, phash, nhash, sig, mintAmount, _, _ := constructMintParams(r) + + balanceBefore, err := client.Balance(alice) + Expect(err).NotTo(HaveOccurred()) + + _, err = client.Mint(alice, phash, nhash, sig, mintAmount) + Expect(err).NotTo(HaveOccurred()) - // Ignore recipient - alice, phash, nhash, sig, amount, _ := constructMintParams() + time.Sleep(5 * time.Second) - txhash, err := client.Mint(ctx, alice, phash, nhash, sig, amount) + balanceAfter, err := client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - fmt.Printf("mint tx = %v\n", hex.EncodeToString(txhash)) + Expect(balanceBefore.Add(pack.NewU256FromUint64(mintAmount))).To(Equal(balanceAfter)) }) }) - Context("when burning over renbridge", func() { + FContext("when burning over renbridge", func() { It("should succeed", func() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() + // Ignore phash, nhash, sig and mint amount. + alice, _, _, _, _, burnAmount, recipient := constructMintParams(r) + + balanceBefore, err := client.Balance(alice) + Expect(err).NotTo(HaveOccurred()) + + _, err = client.Burn(alice, recipient, burnAmount) + Expect(err).NotTo(HaveOccurred()) - // Ignore phash, nhash, sig - alice, _, _, _, amount, recipient := constructMintParams() + time.Sleep(5 * time.Second) - txhash, err := client.Burn(ctx, alice, recipient, amount) + balanceAfter, err := client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - fmt.Printf("burn tx = %v\n", hex.EncodeToString(txhash)) + Expect(balanceBefore.Sub(pack.NewU256FromUint64(burnAmount))).To(Equal(balanceAfter)) }) }) @@ -72,7 +85,7 @@ var _ = Describe("Mint Burn", func() { }) }) -func constructMintParams() (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, [20]byte) { +func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, uint64, [20]byte) { // Get RenVM priv key. renVmPrivKeyBytes, err := hex.DecodeString("c44700049a72c02bbacbec25551190427315f046c1f656f23884949da3fbdc3a") Expect(err).NotTo(HaveOccurred()) @@ -83,13 +96,14 @@ func constructMintParams() (signature.KeyringPair, pack.Bytes32, pack.Bytes32, p // Get random pHash and nHash. phashBytes := make([]byte, 32) nhashBytes := make([]byte, 32) - _, err = rand.Read(phashBytes) + _, err = r.Read(phashBytes) Expect(err).NotTo(HaveOccurred()) - _, err = rand.Read(nhashBytes) + _, err = r.Read(nhashBytes) Expect(err).NotTo(HaveOccurred()) - // Amount to be minted. - amount := uint64(25000) + // Amount to be minted/burnt. + mintAmount := uint64(100000) + burnAmount := uint64(25000) // Selector for this cross-chain mint. token, err := hex.DecodeString("0000000000000000000000000a9add98c076448cbcfacf5e457da12ddbef4a8f") @@ -111,7 +125,7 @@ func constructMintParams() (signature.KeyringPair, pack.Bytes32, pack.Bytes32, p copy(nhash32[:], nhashBytes) copy(sighash32[:], crypto.Keccak256(ethereum.Encode( pack.Bytes32(phash32), - pack.NewU256FromUint64(amount), + pack.NewU256FromUint64(mintAmount), pack.Bytes32(token32), pack.Bytes32(to), pack.Bytes32(nhash32), @@ -131,5 +145,5 @@ func constructMintParams() (signature.KeyringPair, pack.Bytes32, pack.Bytes32, p recipient := [20]byte{} copy(recipient[:], rawRecipientAddr) - return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), amount, recipient + return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), mintAmount, burnAmount, recipient } From c1db5d3e0257a733da76fc9c3af4279b3dc22d17 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 26 Oct 2020 18:09:20 +0530 Subject: [PATCH 06/18] feat: fetch burn log --- chain/acala/acala.go | 84 +++----- chain/acala/decode.go | 322 ----------------------------- chain/acala/mint_burn.go | 48 +++-- chain/acala/mint_burn_test.go | 57 +++-- chain/acala/ren_vm_bridge_types.go | 49 +++++ infra/acala/Dockerfile | 2 +- 6 files changed, 135 insertions(+), 427 deletions(-) delete mode 100644 chain/acala/decode.go create mode 100644 chain/acala/ren_vm_bridge_types.go diff --git a/chain/acala/acala.go b/chain/acala/acala.go index f41f08f1..e2c400ef 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -1,12 +1,9 @@ package acala import ( - "bytes" - "context" "fmt" gsrpc "github.com/centrifuge/go-substrate-rpc-client" - "github.com/centrifuge/go-substrate-rpc-client/scale" "github.com/centrifuge/go-substrate-rpc-client/types" "github.com/renproject/multichain/api/address" "github.com/renproject/pack" @@ -55,82 +52,59 @@ func NewClient(opts ClientOptions) (*Client, error) { }, nil } -func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw) (eventBurnt, error) { - eventRecords := types.EventRecordsRaw(*data) - - events := EventsWithBurn{} - if err := ParseEvents(&eventRecords, meta, &events); err != nil { - return eventBurnt{}, err - } - if len(events.RenToken_Burnt) != 1 { - return eventBurnt{}, fmt.Errorf("expected burn events: %v, got: %v", 1, len(events.RenToken_Burnt)) - } - - return events.RenToken_Burnt[0], nil -} - -func (client *Client) BurnEvent( - ctx context.Context, - blockheight pack.U64, -) (pack.U256, address.Address, pack.U64, error) { - // get metadata +func (client *Client) BurnEvent(blockhash pack.Bytes32) (pack.U256, address.RawAddress, pack.U64, error) { + // Get chain metadata. meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get metadata: %v", err) } - // subscribe to system events via storage + // This key is used to read the state storage at the block of interest. key, err := types.CreateStorageKey(meta, "System", "Events", nil, nil) if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("create storage key: %v", err) } - // get the block's hash - blockhash, err := client.api.RPC.Chain.GetBlockHash(blockheight.Uint64()) + // Get the block in which the burn event was logged. + block, err := client.api.RPC.Chain.GetBlock(types.Hash(blockhash)) if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get block: %v", err) } + // Get the latest block header. This will be used to calculate number of block + // confirmations of the burn log of interest. header, err := client.api.RPC.Chain.GetHeaderLatest() if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get header: %v", err) } - // retrieve raw bytes for the stored data - data, err := client.api.RPC.State.GetStorageRaw(key, blockhash) + // Retrieve raw bytes from storage at the block and storage key of interest. + data, err := client.api.RPC.State.GetStorageRaw(key, types.Hash(blockhash)) if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get storage: %v", err) } - // decode event data to a burn event + // Decode the event data to get the burn log. burnEvent, err := decodeEventData(meta, data) if err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.U256{}, nil, pack.U64(uint64(0)), err } - // calculate block confirmations for the event - confs := uint64(header.Number) - blockheight.Uint64() + 1 + // Calculate block confirmations for the event. + confs := header.Number - block.Block.Header.Number + 1 - // get and encode destination address - dest := types.NewAddressFromAccountID(burnEvent.Dest[:]) - buf := new(bytes.Buffer) - encoder := scale.NewEncoder(buf) - if err := dest.Encode(*encoder); err != nil { - // FIXME: return err instead of panicking - panic(err) + return pack.NewU256FromInt(burnEvent.Amount.Int), address.RawAddress(burnEvent.Dest[:]), pack.NewU64(uint64(confs)), nil +} + +func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw) (eventBurnt, error) { + events := RenVmBridgeEvents{} + if err := types.EventRecordsRaw(*data).DecodeEventRecords(meta, &events); err != nil { + return eventBurnt{}, fmt.Errorf("decode event data: %v", err) } - addrEncodeDecoder := NewAddressEncodeDecoder() - to, err := addrEncodeDecoder.EncodeAddress(address.RawAddress(pack.NewBytes(buf.Bytes()))) - if err != nil { - // FIXME: return err instead of panicking - panic(err) + + if len(events.RenVmBridge_Burnt) != 1 { + return eventBurnt{}, fmt.Errorf("expected burn events: %v, got: %v", 1, len(events.RenVmBridge_Burnt)) } - return pack.NewU256FromInt(burnEvent.Amount.Int), address.Address(pack.String(to)), pack.NewU64(confs), nil + return events.RenVmBridge_Burnt[0], nil } diff --git a/chain/acala/decode.go b/chain/acala/decode.go deleted file mode 100644 index 3175503f..00000000 --- a/chain/acala/decode.go +++ /dev/null @@ -1,322 +0,0 @@ -package acala - -import ( - "bytes" - "errors" - "fmt" - "reflect" - - "github.com/centrifuge/go-substrate-rpc-client/scale" - "github.com/centrifuge/go-substrate-rpc-client/types" -) - -type eventMinted struct { - Phase types.Phase - Who types.AccountID - Currency types.U8 - Amount types.U128 - Topics []types.Hash -} - -type eventBurnt struct { - Owner types.AccountID - Dest types.AccountID - Amount types.U128 -} - -type EventsWithMint struct { - types.EventRecords - RenToken_Minted []eventMinted //nolint:stylecheck,golint -} - -type EventsWithBurn struct { - types.EventRecords - RenToken_Burnt []eventBurnt //nolint:stylecheck,golint -} - -// ParseEvents decodes the events records from an EventRecordRaw into a target -// t using the given Metadata m. If this method returns an error like `unable -// to decode Phase for event #x: EOF`, it is likely that you have defined a -// custom event record with a wrong type. For example your custom event record -// has a field with a length prefixed type, such as types.Bytes, where your -// event in reality contains a fixed width type, such as a types.U32. -func ParseEvents(e *types.EventRecordsRaw, m *types.Metadata, t interface{}) error { - // ensure t is a pointer - ttyp := reflect.TypeOf(t) - if ttyp.Kind() != reflect.Ptr { - return errors.New("target must be a pointer, but is " + fmt.Sprint(ttyp)) - } - // ensure t is not a nil pointer - tval := reflect.ValueOf(t) - if tval.IsNil() { - return errors.New("target is a nil pointer") - } - val := tval.Elem() - typ := val.Type() - // ensure val can be set - if !val.CanSet() { - return fmt.Errorf("unsettable value %v", typ) - } - // ensure val points to a struct - if val.Kind() != reflect.Struct { - return fmt.Errorf("target must point to a struct, but is " + fmt.Sprint(typ)) - } - - decoder := scale.NewDecoder(bytes.NewReader(*e)) - - // determine number of events - n, err := decoder.DecodeUintCompact() - if err != nil { - return err - } - - fmt.Println(fmt.Sprintf("found %v events", n)) - - // iterate over events - for i := uint64(0); i < n.Uint64(); i++ { - fmt.Println(fmt.Sprintf("decoding event #%v", i)) - - // decode Phase - phase := types.Phase{} - err := decoder.Decode(&phase) - if err != nil { - return fmt.Errorf("unable to decode Phase for event #%v: %v", i, err) - } - - // decode EventID - id := types.EventID{} - err = decoder.Decode(&id) - if err != nil { - return fmt.Errorf("unable to decode EventID for event #%v: %v", i, err) - } - - fmt.Println(fmt.Sprintf("event #%v has EventID %v", i, id)) - - // ask metadata for method & event name for event - moduleName, eventName, err := m.FindEventNamesForEventID(id) - // moduleName, eventName, err := "System", "ExtrinsicSuccess", nil - if err != nil { - fmt.Printf("unable to find event with EventID %v in metadata for event #%v: %s\n", id, i, err) - continue - // return fmt.Errorf("unable to find event with EventID %v in metadata for event #%v: %s", id, i, err) - } - - fmt.Println(fmt.Sprintf("event #%v is in module %v with event name %v", i, moduleName, eventName)) - - // check whether name for eventID exists in t - field := val.FieldByName(fmt.Sprintf("%v_%v", moduleName, eventName)) - if !field.IsValid() { - eventParams, err := findEventForEventID(m.AsMetadataV10, id) - if err != nil { - return fmt.Errorf("unable to find event with EventID %v in metadata for event #%v: %s", id, i, err) - } - - for j := 0; j < len(eventParams.Args); j++ { - fmt.Printf("decoding field: %v (%v)\n", j, eventParams.Args[j]) - switch eventParams.Args[j] { - case "u8": - param := types.U8(0) - err = decoder.Decode(param) - case "u16": - param := types.U16(0) - err = decoder.Decode(param) - case "u32": - param := types.U32(0) - err = decoder.Decode(param) - case "u64": - param := types.U64(0) - err = decoder.Decode(param) - case "u128": - param := types.U128{} - err = decoder.Decode(param) - case "u256": - param := types.U256{} - err = decoder.Decode(param) - case "Phase": - param := types.Phase{} - err = decoder.Decode(param) - case "DispatchInfo": - param := types.DispatchInfo{} - err = decoder.Decode(param) - case "DispatchError": - param := types.DispatchError{} - err = decoder.Decode(param) - case "AccountId": - param := types.AccountID{} - err = decoder.Decode(param) - case "AccountIndex": - param := types.AccountIndex(0) - err = decoder.Decode(param) - // case "Balance": - // param := types.Balance{} - // err = decoder.Decode(param) - // case "Status": - // param := types.Status{} - // err = decoder.Decode(param) - case "bool": - param := types.Bool(false) - err = decoder.Decode(param) - // case "CallHash": - // param := types.CallHash{} - // err = decoder.Decode(param) - // case "Timepoint": - // param := types.Timepoint{} - // err = decoder.Decode(param) - // case "ProposalIndex": - // param := types.ProposalIndex{} - // err = decoder.Decode(param) - case "Hash": - param := types.Hash{} - err = decoder.Decode(param) - // case "EraIndex": - // param := types.EraIndex{} - // err = decoder.Decode(param) - // case "SessionIndex": - // param := types.SessionIndex{} - // err = decoder.Decode(param) - // case "ElectionCompute": - // param := types.ElectionCompute{} - // err = decoder.Decode(param) - // case "MemberCount": - // param := types.MemberCount{} - // err = decoder.Decode(param) - // case "sp_std": - // param := // types.sp_std::marker::PhantomData<(AccountId, Event)>{} - // err = decoder.Decode(param) - // case "Vec": - // param := // types.Vec<(OracleKey, OracleValue)>{} - // err = decoder.Decode(param) - // case "CurrencyId": - // param := types.CurrencyId{} - // err = decoder.Decode(param) - // case "Amount": - // param := types.Amount{} - // err = decoder.Decode(param) - // case "VestingSchedule": - // param := types.VestingSchedule{} - // err = decoder.Decode(param) - case "BlockNumber": - param := types.BlockNumber(0) - err = decoder.Decode(param) - // case "DispatchId": - // param := types.DispatchId{} - // err = decoder.Decode(param) - case "StorageKey": - param := types.StorageKey{} - err = decoder.Decode(param) - // case "StorageValue": - // param := types.StorageValue{} - // err = decoder.Decode(param) - // case "AuctionId": - // param := types.AuctionId{} - // err = decoder.Decode(param) - // case "Price": - // param := types.Price{} - // err = decoder.Decode(param) - // case "DebitAmount": - // param := types.DebitAmount{} - // err = decoder.Decode(param) - // case "DebitBalance": - // param := types.DebitBalance{} - // err = decoder.Decode(param) - // case "Share": - // param := types.Share{} - // err = decoder.Decode(param) - // case "LiquidationStrategy": - // param := types.LiquidationStrategy{} - // err = decoder.Decode(param) - // case "Option": - // param := types.Option{} - // err = decoder.Decode(param) - // case "Option": - // param := types.Option{} - // err = decoder.Decode(param) - // case "Rate": - // param := types.Rate{} - // err = decoder.Decode(param) - // case "Vec": - // param := // types.Vec<(CurrencyId, Balance)>{} - // err = decoder.Decode(param) - // case "AirDropCurrencyId": - // param := types.AirDropCurrencyId{} - // err = decoder.Decode(param) - // case "Vec": - // param := // types.Vec{} - // err = decoder.Decode(param) - - case "AuthorityList": - param := []struct { - AuthorityID types.AuthorityID - AuthorityWeight types.U64 - }{} - err = decoder.Decode(param) - default: - return fmt.Errorf("unable to decode field %v_%v arg #%v %v", moduleName, - eventName, j, eventParams.Args[j]) - } - } - - fmt.Printf("unable to find field %v_%v for event #%v with EventID %v\n", moduleName, eventName, i, id) - continue - // return fmt.Errorf("unable to find field %v_%v for event #%v with EventID %v", moduleName, eventName, i, id) - } - - // create a pointer to with the correct type that will hold the decoded event - holder := reflect.New(field.Type().Elem()) - - // ensure first field is for Phase, last field is for Topics - numFields := holder.Elem().NumField() - fmt.Printf("numFields: %v\n", numFields) - if numFields < 2 { - return fmt.Errorf("expected event #%v with EventID %v, field %v_%v to have at least 2 fields "+ - "(for Phase and Topics), but has %v fields", i, id, moduleName, eventName, numFields) - } - phaseField := holder.Elem().FieldByIndex([]int{0}) - if phaseField.Type() != reflect.TypeOf(phase) { - return fmt.Errorf("expected the first field of event #%v with EventID %v, field %v_%v to be of type "+ - "types.Phase, but got %v", i, id, moduleName, eventName, phaseField.Type()) - } - topicsField := holder.Elem().FieldByIndex([]int{numFields - 1}) - if topicsField.Type() != reflect.TypeOf([]types.Hash{}) { - return fmt.Errorf("expected the last field of event #%v with EventID %v, field %v_%v to be of type "+ - "[]types.Hash for Topics, but got %v", i, id, moduleName, eventName, topicsField.Type()) - } - - // set the phase we decoded earlier - phaseField.Set(reflect.ValueOf(phase)) - - // set the remaining fields - for j := 1; j < numFields; j++ { - fmt.Printf("decoding field: %v\n", j) - err = decoder.Decode(holder.Elem().FieldByIndex([]int{j}).Addr().Interface()) - if err != nil { - return fmt.Errorf("unable to decode field %v event #%v with EventID %v, field %v_%v: %v", j, i, id, moduleName, - eventName, err) - } - } - - // add the decoded event to the slice - field.Set(reflect.Append(field, holder.Elem())) - - fmt.Println(fmt.Sprintf("decoded event #%v", i)) - } - return nil -} - -func findEventForEventID(m types.MetadataV10, eventID types.EventID) (*types.EventMetadataV4, error) { - mi := uint8(0) - for _, mod := range m.Modules { - if !mod.HasEvents { - continue - } - if mi != eventID[0] { - mi++ - continue - } - if int(eventID[1]) >= len(mod.Events) { - return nil, fmt.Errorf("event index %v for module %v out of range", eventID[1], mod.Name) - } - return &mod.Events[eventID[1]], nil - } - return nil, fmt.Errorf("module index %v out of range", eventID[0]) -} diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index 565c2197..f3dc8f40 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -3,70 +3,71 @@ package acala import ( "fmt" "math/big" + "time" "github.com/centrifuge/go-substrate-rpc-client/signature" "github.com/centrifuge/go-substrate-rpc-client/types" "github.com/renproject/pack" ) -func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes, error) { +func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes32, error) { opts := types.SerDeOptions{NoPalletIndices: true} types.SetSerDeOptions(opts) meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + return pack.Bytes32{}, fmt.Errorf("get metadata: %v", err) } alice := types.NewAddressFromAccountID(minterKey.PublicKey) c, err := types.NewCall(meta, "RenVmBridge.mint", alice, phash, types.NewUCompactFromUInt(amount), nhash, sig) if err != nil { - return pack.Bytes{}, fmt.Errorf("construct call: %v", err) + return pack.Bytes32{}, fmt.Errorf("construct call: %v", err) } hash, err := client.api.RPC.Author.SubmitExtrinsic(types.NewExtrinsic(c)) if err != nil { - return pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + return pack.Bytes32{}, fmt.Errorf("submit extrinsic: %v", err) } - return pack.NewBytes(hash[:]), nil + return pack.NewBytes32(hash), nil } -func (client *Client) Burn(burnerKey signature.KeyringPair, recipient [20]byte, amount uint64) (pack.Bytes, error) { +func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, error) { opts := types.SerDeOptions{NoPalletIndices: false} types.SetSerDeOptions(opts) meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + return pack.Bytes32{}, fmt.Errorf("get metadata: %v", err) } - c, err := types.NewCall(meta, "RenVmBridge.burn", recipient, types.NewUCompactFromUInt(amount)) + c, err := types.NewCall(meta, "RenVmBridge.burn", types.Bytes(recipient), types.NewUCompactFromUInt(amount)) if err != nil { - return pack.Bytes{}, fmt.Errorf("construct call: %v", err) + return pack.Bytes32{}, fmt.Errorf("construct call: %v", err) } ext := types.NewExtrinsic(c) genesisHash, err := client.api.RPC.Chain.GetBlockHash(0) if err != nil { - return pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + return pack.Bytes32{}, fmt.Errorf("get blockhash: %v", err) } rv, err := client.api.RPC.State.GetRuntimeVersionLatest() if err != nil { - return pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) + return pack.Bytes32{}, fmt.Errorf("get runtime version: %v", err) } key, err := types.CreateStorageKey(meta, "System", "Account", burnerKey.PublicKey, nil) if err != nil { - return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + return pack.Bytes32{}, fmt.Errorf("create storage key: %v", err) } var accountInfo types.AccountInfo ok, err := client.api.RPC.State.GetStorageLatest(key, &accountInfo) if err != nil || !ok { - return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + return pack.Bytes32{}, fmt.Errorf("get storage: %v", err) } nonce := uint32(accountInfo.Nonce) @@ -83,15 +84,26 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient [20]byte, err = ext.Sign(burnerKey, o) if err != nil { - return pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) + return pack.Bytes32{}, fmt.Errorf("sign extrinsic: %v", err) } - hash, err := client.api.RPC.Author.SubmitExtrinsic(ext) + sub, err := client.api.RPC.Author.SubmitAndWatchExtrinsic(ext) if err != nil { - return pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + return pack.Bytes32{}, fmt.Errorf("submit extrinsic: %v", err) + } + defer sub.Unsubscribe() + + timeout := time.After(10 * time.Second) + for { + select { + case status := <-sub.Chan(): + if status.IsInBlock { + return pack.NewBytes32(status.AsInBlock), nil + } + case <-timeout: + return pack.Bytes32{}, fmt.Errorf("timeout on tx confirmation") + } } - - return pack.NewBytes(hash[:]), nil } type TokenAccount struct { diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index ca94d71e..a55ed2dd 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -1,7 +1,6 @@ package acala_test import ( - "context" "encoding/hex" "fmt" "math/rand" @@ -22,70 +21,68 @@ import ( "github.com/renproject/surge" ) -var _ = Describe("Mint Burn", func() { +var _ = FDescribe("Mint Burn", func() { r := rand.New(rand.NewSource(GinkgoRandomSeed())) + blockhash := pack.Bytes32{} + balanceBefore, balanceAfter := pack.U256{}, pack.U256{} + client, err := acala.NewClient(acala.DefaultClientOptions()) Expect(err).NotTo(HaveOccurred()) - FContext("when minting over renbridge", func() { - It("should succeed", func() { - // Ignore recipient and burn amount. - alice, phash, nhash, sig, mintAmount, _, _ := constructMintParams(r) + alice, phash, nhash, sig, mintAmount, burnAmount, recipient := constructMintParams(r) - balanceBefore, err := client.Balance(alice) - Expect(err).NotTo(HaveOccurred()) + Context("when minting over renbridge", func() { + It("should succeed", func() { + balanceBefore, err = client.Balance(alice) + if err != nil { + // This means there are no tokens allocated for that address. + Expect(err).To(Equal(fmt.Errorf("get storage: "))) + balanceBefore = pack.NewU256FromUint64(uint64(0)) + } _, err = client.Mint(alice, phash, nhash, sig, mintAmount) Expect(err).NotTo(HaveOccurred()) time.Sleep(5 * time.Second) - balanceAfter, err := client.Balance(alice) + balanceAfter, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) Expect(balanceBefore.Add(pack.NewU256FromUint64(mintAmount))).To(Equal(balanceAfter)) }) }) - FContext("when burning over renbridge", func() { + Context("when burning over renbridge", func() { It("should succeed", func() { - // Ignore phash, nhash, sig and mint amount. - alice, _, _, _, _, burnAmount, recipient := constructMintParams(r) - - balanceBefore, err := client.Balance(alice) + balanceBefore, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - _, err = client.Burn(alice, recipient, burnAmount) + blockhash, err = client.Burn(alice, recipient, burnAmount) Expect(err).NotTo(HaveOccurred()) time.Sleep(5 * time.Second) - balanceAfter, err := client.Balance(alice) + balanceAfter, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) Expect(balanceBefore.Sub(pack.NewU256FromUint64(burnAmount))).To(Equal(balanceAfter)) }) }) - Context("when reading burn info", func() { + Context("when reading burn log", func() { It("should succeed", func() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - // FIXME: set this appropriately - blockheight := pack.U64(uint64(1)) - amount, recipient, confs, err := client.BurnEvent(ctx, blockheight) + amount, recipient, confs, err := client.BurnEvent(blockhash) Expect(err).NotTo(HaveOccurred()) - fmt.Printf("amount = %v\n", amount) - fmt.Printf("recipient = %v\n", recipient) - fmt.Printf("confs = %v\n", confs) + Expect(amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(recipient).To(Equal(recipient)) + Expect(confs).To(BeNumerically(">", 0)) }) }) }) -func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, uint64, [20]byte) { +func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pack.Bytes32, pack.Bytes65, uint64, uint64, pack.Bytes) { // Get RenVM priv key. renVmPrivKeyBytes, err := hex.DecodeString("c44700049a72c02bbacbec25551190427315f046c1f656f23884949da3fbdc3a") Expect(err).NotTo(HaveOccurred()) @@ -140,10 +137,8 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac // Get the address of the burn recipient. recipientAddr := multichain.Address(pack.String("miMi2VET41YV1j6SDNTeZoPBbmH8B4nEx6")) btcEncodeDecoder := bitcoin.NewAddressEncodeDecoder(&chaincfg.RegressionNetParams) - rawRecipientAddr, err := btcEncodeDecoder.DecodeAddress(recipientAddr) + recipientRawAddr, err := btcEncodeDecoder.DecodeAddress(recipientAddr) Expect(err).NotTo(HaveOccurred()) - recipient := [20]byte{} - copy(recipient[:], rawRecipientAddr) - return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), mintAmount, burnAmount, recipient + return signature.TestKeyringPairAlice, pack.Bytes32(phash32), pack.Bytes32(nhash32), pack.Bytes65(sig65), mintAmount, burnAmount, pack.Bytes(recipientRawAddr) } diff --git a/chain/acala/ren_vm_bridge_types.go b/chain/acala/ren_vm_bridge_types.go new file mode 100644 index 00000000..295f9a5a --- /dev/null +++ b/chain/acala/ren_vm_bridge_types.go @@ -0,0 +1,49 @@ +package acala + +import "github.com/centrifuge/go-substrate-rpc-client/types" + +type eventMinted struct { + Phase types.Phase + Owner types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventBurnt struct { + Phase types.Phase + Owner types.AccountID + Dest types.Bytes + Amount types.U128 + Topics []types.Hash +} + +type eventDeposited struct { + Phase types.Phase + CurrencyId [2]byte + Who types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventWithdrawn struct { + Phase types.Phase + CurrencyId [2]byte + Who types.AccountID + Amount types.U128 + Topics []types.Hash +} + +type eventTreasury struct { + Phase types.Phase + Deposit types.U128 + Topics []types.Hash +} + +type RenVmBridgeEvents struct { + types.EventRecords + Currencies_Deposited []eventDeposited + RenVmBridge_Minted []eventMinted + Currencies_Withdrawn []eventWithdrawn + RenVmBridge_Burnt []eventBurnt + AcalaTreasury_Deposit []eventTreasury +} diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index a8c94627..e275c34e 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -15,7 +15,7 @@ WORKDIR /app # Make sure submodule.recurse is set to true to make life with submodule easier. RUN git fetch -RUN git pull +RUN git pull origin master RUN git config --global submodule.recurse true # Build From 4d00357a1298d1fdde1f3af13d69bd8cb688f17f Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 27 Oct 2020 10:36:29 +0530 Subject: [PATCH 07/18] feat: support multiple extrinsics in a block (burn logs) --- chain/acala/acala.go | 76 ++++++++++++++++++++++++++++------- chain/acala/mint_burn.go | 22 +++++----- chain/acala/mint_burn_test.go | 26 +++++++++--- 3 files changed, 92 insertions(+), 32 deletions(-) diff --git a/chain/acala/acala.go b/chain/acala/acala.go index e2c400ef..30473bdf 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -1,15 +1,29 @@ package acala import ( + "context" "fmt" gsrpc "github.com/centrifuge/go-substrate-rpc-client" "github.com/centrifuge/go-substrate-rpc-client/types" "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" "github.com/renproject/pack" + "github.com/renproject/surge" "go.uber.org/zap" ) +type BurnLogInput struct { + Blockhash pack.Bytes32 + ExtSign pack.Bytes +} + +type BurnLogOutput struct { + Amount pack.U256 + Recipient address.RawAddress + Confs pack.U64 +} + const ( DefaultClientRPCURL = "ws://127.0.0.1:9944" ) @@ -52,59 +66,91 @@ func NewClient(opts ClientOptions) (*Client, error) { }, nil } -func (client *Client) BurnEvent(blockhash pack.Bytes32) (pack.U256, address.RawAddress, pack.U64, error) { +func (client *Client) ContractCall(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { + // Deserialise the calldata bytes. + input := BurnLogInput{} + if err := surge.FromBinary(&input, calldata); err != nil { + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v\n", err) + } + // Get chain metadata. meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get metadata: %v", err) + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) } // This key is used to read the state storage at the block of interest. key, err := types.CreateStorageKey(meta, "System", "Events", nil, nil) if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("create storage key: %v", err) + return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) } // Get the block in which the burn event was logged. - block, err := client.api.RPC.Chain.GetBlock(types.Hash(blockhash)) + block, err := client.api.RPC.Chain.GetBlock(types.Hash(input.Blockhash)) if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get block: %v", err) + return pack.Bytes{}, fmt.Errorf("get block: %v", err) } // Get the latest block header. This will be used to calculate number of block // confirmations of the burn log of interest. header, err := client.api.RPC.Chain.GetHeaderLatest() if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get header: %v", err) + return pack.Bytes{}, fmt.Errorf("get header: %v", err) } // Retrieve raw bytes from storage at the block and storage key of interest. - data, err := client.api.RPC.State.GetStorageRaw(key, types.Hash(blockhash)) + data, err := client.api.RPC.State.GetStorageRaw(key, types.Hash(input.Blockhash)) if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), fmt.Errorf("get storage: %v", err) + return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + // Fetch the extrinsic's index in the block. + extID := -1 + for i, ext := range block.Block.Extrinsics { + if input.ExtSign.Equal(pack.Bytes(ext.Signature.Signature.AsSr25519[:])) { + extID = i + break + } + } + if extID == -1 { + return pack.Bytes{}, fmt.Errorf("extrinsic not found in block") } // Decode the event data to get the burn log. - burnEvent, err := decodeEventData(meta, data) + burnEvent, err := decodeEventData(meta, data, uint32(extID)) if err != nil { - return pack.U256{}, nil, pack.U64(uint64(0)), err + return pack.Bytes{}, err } // Calculate block confirmations for the event. confs := header.Number - block.Block.Header.Number + 1 - return pack.NewU256FromInt(burnEvent.Amount.Int), address.RawAddress(burnEvent.Dest[:]), pack.NewU64(uint64(confs)), nil + burnLogOutput := BurnLogOutput{ + Amount: pack.NewU256FromInt(burnEvent.Amount.Int), + Recipient: address.RawAddress(burnEvent.Dest[:]), + Confs: pack.NewU64(uint64(confs)), + } + + out, err := surge.ToBinary(burnLogOutput) + if err != nil { + return pack.Bytes{}, fmt.Errorf("serialise output: %v", err) + } + + return pack.Bytes(out), nil } -func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw) (eventBurnt, error) { +func decodeEventData(meta *types.Metadata, data *types.StorageDataRaw, id uint32) (eventBurnt, error) { events := RenVmBridgeEvents{} if err := types.EventRecordsRaw(*data).DecodeEventRecords(meta, &events); err != nil { return eventBurnt{}, fmt.Errorf("decode event data: %v", err) } - if len(events.RenVmBridge_Burnt) != 1 { - return eventBurnt{}, fmt.Errorf("expected burn events: %v, got: %v", 1, len(events.RenVmBridge_Burnt)) + // Match the event to the appropriate extrinsic index. + for _, event := range events.RenVmBridge_Burnt { + if event.Phase.AsApplyExtrinsic == id { + return event, nil + } } - return events.RenVmBridge_Burnt[0], nil + return eventBurnt{}, fmt.Errorf("burn event not found") } diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index f3dc8f40..020fb411 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -33,41 +33,41 @@ func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.By return pack.NewBytes32(hash), nil } -func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, error) { +func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, pack.Bytes, error) { opts := types.SerDeOptions{NoPalletIndices: false} types.SetSerDeOptions(opts) meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return pack.Bytes32{}, fmt.Errorf("get metadata: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get metadata: %v", err) } c, err := types.NewCall(meta, "RenVmBridge.burn", types.Bytes(recipient), types.NewUCompactFromUInt(amount)) if err != nil { - return pack.Bytes32{}, fmt.Errorf("construct call: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("construct call: %v", err) } ext := types.NewExtrinsic(c) genesisHash, err := client.api.RPC.Chain.GetBlockHash(0) if err != nil { - return pack.Bytes32{}, fmt.Errorf("get blockhash: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) } rv, err := client.api.RPC.State.GetRuntimeVersionLatest() if err != nil { - return pack.Bytes32{}, fmt.Errorf("get runtime version: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) } key, err := types.CreateStorageKey(meta, "System", "Account", burnerKey.PublicKey, nil) if err != nil { - return pack.Bytes32{}, fmt.Errorf("create storage key: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("create storage key: %v", err) } var accountInfo types.AccountInfo ok, err := client.api.RPC.State.GetStorageLatest(key, &accountInfo) if err != nil || !ok { - return pack.Bytes32{}, fmt.Errorf("get storage: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get storage: %v", err) } nonce := uint32(accountInfo.Nonce) @@ -84,12 +84,12 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes err = ext.Sign(burnerKey, o) if err != nil { - return pack.Bytes32{}, fmt.Errorf("sign extrinsic: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) } sub, err := client.api.RPC.Author.SubmitAndWatchExtrinsic(ext) if err != nil { - return pack.Bytes32{}, fmt.Errorf("submit extrinsic: %v", err) + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) } defer sub.Unsubscribe() @@ -98,10 +98,10 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes select { case status := <-sub.Chan(): if status.IsInBlock { - return pack.NewBytes32(status.AsInBlock), nil + return pack.NewBytes32(status.AsInBlock), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil } case <-timeout: - return pack.Bytes32{}, fmt.Errorf("timeout on tx confirmation") + return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("timeout on tx confirmation") } } } diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index a55ed2dd..94f2dc1e 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -1,6 +1,7 @@ package acala_test import ( + "context" "encoding/hex" "fmt" "math/rand" @@ -14,6 +15,7 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/renproject/id" "github.com/renproject/multichain" + "github.com/renproject/multichain/api/contract" "github.com/renproject/multichain/chain/acala" "github.com/renproject/multichain/chain/bitcoin" "github.com/renproject/multichain/chain/ethereum" @@ -24,7 +26,7 @@ import ( var _ = FDescribe("Mint Burn", func() { r := rand.New(rand.NewSource(GinkgoRandomSeed())) - blockhash := pack.Bytes32{} + blockhash, extSign := pack.Bytes32{}, pack.Bytes{} balanceBefore, balanceAfter := pack.U256{}, pack.U256{} client, err := acala.NewClient(acala.DefaultClientOptions()) @@ -58,7 +60,7 @@ var _ = FDescribe("Mint Burn", func() { balanceBefore, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - blockhash, err = client.Burn(alice, recipient, burnAmount) + blockhash, extSign, err = client.Burn(alice, recipient, burnAmount) Expect(err).NotTo(HaveOccurred()) time.Sleep(5 * time.Second) @@ -72,12 +74,24 @@ var _ = FDescribe("Mint Burn", func() { Context("when reading burn log", func() { It("should succeed", func() { - amount, recipient, confs, err := client.BurnEvent(blockhash) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + input := acala.BurnLogInput{ + Blockhash: blockhash, + ExtSign: extSign, + } + calldata, err := surge.ToBinary(input) Expect(err).NotTo(HaveOccurred()) + outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) + Expect(err).NotTo(HaveOccurred()) + + output := acala.BurnLogOutput{} + Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) - Expect(amount).To(Equal(pack.NewU256FromUint64(burnAmount))) - Expect(recipient).To(Equal(recipient)) - Expect(confs).To(BeNumerically(">", 0)) + Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) + Expect(output.Confs).To(BeNumerically(">", 0)) }) }) }) From 5130e9397c55faaeb5bda7204f0762fb55ef9029 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 27 Oct 2020 16:57:19 +0530 Subject: [PATCH 08/18] feat: fetch burn log from storage instead of system events --- chain/acala/acala_test.go | 16 --------- chain/acala/mint_burn.go | 67 +++++++++++++++++++++++++++-------- chain/acala/mint_burn_test.go | 60 +++++++++++++++++++------------ 3 files changed, 90 insertions(+), 53 deletions(-) diff --git a/chain/acala/acala_test.go b/chain/acala/acala_test.go index 55021661..dc367890 100644 --- a/chain/acala/acala_test.go +++ b/chain/acala/acala_test.go @@ -1,17 +1 @@ package acala_test - -import ( - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - - "github.com/renproject/multichain/chain/acala" -) - -var _ = Describe("Substrate client", func() { - Context("when verifying burns", func() { - It("should verify a valid burn", func() { - _, err := acala.NewClient(acala.DefaultClientOptions()) - Expect(err).ToNot(HaveOccurred()) - }) - }) -}) diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index 020fb411..946d3db9 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -1,8 +1,8 @@ package acala import ( + "encoding/binary" "fmt" - "math/big" "time" "github.com/centrifuge/go-substrate-rpc-client/signature" @@ -33,41 +33,41 @@ func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.By return pack.NewBytes32(hash), nil } -func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, pack.Bytes, error) { +func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, pack.U32, pack.Bytes, error) { opts := types.SerDeOptions{NoPalletIndices: false} types.SetSerDeOptions(opts) meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get metadata: %v", err) } c, err := types.NewCall(meta, "RenVmBridge.burn", types.Bytes(recipient), types.NewUCompactFromUInt(amount)) if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("construct call: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("construct call: %v", err) } ext := types.NewExtrinsic(c) genesisHash, err := client.api.RPC.Chain.GetBlockHash(0) if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) } rv, err := client.api.RPC.State.GetRuntimeVersionLatest() if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get runtime version: %v", err) } key, err := types.CreateStorageKey(meta, "System", "Account", burnerKey.PublicKey, nil) if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("create storage key: %v", err) } var accountInfo types.AccountInfo ok, err := client.api.RPC.State.GetStorageLatest(key, &accountInfo) if err != nil || !ok { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("get storage: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get storage: %v", err) } nonce := uint32(accountInfo.Nonce) @@ -84,12 +84,12 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes err = ext.Sign(burnerKey, o) if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("sign extrinsic: %v", err) } sub, err := client.api.RPC.Author.SubmitAndWatchExtrinsic(ext) if err != nil { - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("submit extrinsic: %v", err) } defer sub.Unsubscribe() @@ -98,10 +98,14 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes select { case status := <-sub.Chan(): if status.IsInBlock { - return pack.NewBytes32(status.AsInBlock), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil + block, err := client.api.RPC.Chain.GetBlock(status.AsInBlock) + if err != nil { + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get block: %v", err) + } + return pack.NewBytes32(status.AsInBlock), pack.NewU32(uint32(block.Block.Header.Number)), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil } case <-timeout: - return pack.Bytes32{}, pack.Bytes{}, fmt.Errorf("timeout on tx confirmation") + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("timeout on tx confirmation") } } } @@ -112,6 +116,15 @@ type TokenAccount struct { Frozen types.U128 } +type BurnEventLogs struct { + Logs []BurnEventLog +} + +type BurnEventLog struct { + Recipient types.Bytes + Amount types.U128 +} + func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { @@ -129,6 +142,32 @@ func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { return pack.U256{}, fmt.Errorf("get storage: %v", err) } - balance := big.NewInt(0).SetBytes(data.Free.Bytes()) - return pack.NewU256FromInt(balance), nil + return pack.NewU256FromInt(data.Free.Int), nil +} + +func (client *Client) BurnEvent(blocknumber pack.U32) (BurnEventLog, error) { + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return BurnEventLog{}, fmt.Errorf("get metadata: %v", err) + } + + blocknumberBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(blocknumberBytes, uint32(blocknumber)) + + key, err := types.CreateStorageKey(meta, "Template", "BurnEvents", blocknumberBytes, nil) + if err != nil { + return BurnEventLog{}, fmt.Errorf("create storage key: %v", err) + } + + var data BurnEventLogs + ok, err := client.api.RPC.State.GetStorageLatest(key, &data) + if err != nil || !ok { + return BurnEventLog{}, fmt.Errorf("get storage: %v", err) + } + + if len(data.Logs) != 1 { + return BurnEventLog{}, fmt.Errorf("expected %v burn events, got %v", 1, len(data.Logs)) + } + + return data.Logs[0], nil } diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 94f2dc1e..e0928d04 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -23,10 +23,10 @@ import ( "github.com/renproject/surge" ) -var _ = FDescribe("Mint Burn", func() { +var _ = Describe("Mint Burn", func() { r := rand.New(rand.NewSource(GinkgoRandomSeed())) - blockhash, extSign := pack.Bytes32{}, pack.Bytes{} + blockhash, blocknumber, extSign := pack.Bytes32{}, pack.U32(0), pack.Bytes{} balanceBefore, balanceAfter := pack.U256{}, pack.U256{} client, err := acala.NewClient(acala.DefaultClientOptions()) @@ -60,7 +60,7 @@ var _ = FDescribe("Mint Burn", func() { balanceBefore, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - blockhash, extSign, err = client.Burn(alice, recipient, burnAmount) + blockhash, blocknumber, extSign, err = client.Burn(alice, recipient, burnAmount) Expect(err).NotTo(HaveOccurred()) time.Sleep(5 * time.Second) @@ -72,26 +72,40 @@ var _ = FDescribe("Mint Burn", func() { }) }) - Context("when reading burn log", func() { - It("should succeed", func() { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - input := acala.BurnLogInput{ - Blockhash: blockhash, - ExtSign: extSign, - } - calldata, err := surge.ToBinary(input) - Expect(err).NotTo(HaveOccurred()) - outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) - Expect(err).NotTo(HaveOccurred()) - - output := acala.BurnLogOutput{} - Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) + Context("when reading burn info", func() { + Context("when reading from storage", func() { + It("should succeed", func() { + // FIXME: once the Acala block number issue is addressed, we will not + // need to add 21600. + output, err := client.BurnEvent(blocknumber.Add(pack.U32(uint32(21600)))) + Expect(err).NotTo(HaveOccurred()) + + Expect(output.Amount.Uint64()).To(Equal(burnAmount)) + Expect([]byte(output.Recipient)).To(Equal([]byte(recipient))) + }) + }) - Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) - Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) - Expect(output.Confs).To(BeNumerically(">", 0)) + Context("when reading from system events", func() { + It("should succeed", func() { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + input := acala.BurnLogInput{ + Blockhash: blockhash, + ExtSign: extSign, + } + calldata, err := surge.ToBinary(input) + Expect(err).NotTo(HaveOccurred()) + outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) + Expect(err).NotTo(HaveOccurred()) + + output := acala.BurnLogOutput{} + Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) + + Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) + Expect(output.Confs).To(BeNumerically(">", 0)) + }) }) }) }) @@ -114,7 +128,7 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac // Amount to be minted/burnt. mintAmount := uint64(100000) - burnAmount := uint64(25000) + burnAmount := uint64(31000) // Selector for this cross-chain mint. token, err := hex.DecodeString("0000000000000000000000000a9add98c076448cbcfacf5e457da12ddbef4a8f") From dbfa397bd2ce8f2e4d9f93c939dd2e8c42ebd8fc Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Wed, 28 Oct 2020 14:57:25 +0530 Subject: [PATCH 09/18] feat: burn event is mapped to nonce (burn count) --- chain/acala/acala.go | 24 +++++----- chain/acala/contract.go | 88 +++++++++++++++++++++++++++++++++++ chain/acala/mint_burn.go | 37 ++++----------- chain/acala/mint_burn_test.go | 33 +++++++++---- 4 files changed, 133 insertions(+), 49 deletions(-) create mode 100644 chain/acala/contract.go diff --git a/chain/acala/acala.go b/chain/acala/acala.go index 30473bdf..46b465a9 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -13,17 +13,6 @@ import ( "go.uber.org/zap" ) -type BurnLogInput struct { - Blockhash pack.Bytes32 - ExtSign pack.Bytes -} - -type BurnLogOutput struct { - Amount pack.U256 - Recipient address.RawAddress - Confs pack.U64 -} - const ( DefaultClientRPCURL = "ws://127.0.0.1:9944" ) @@ -66,7 +55,18 @@ func NewClient(opts ClientOptions) (*Client, error) { }, nil } -func (client *Client) ContractCall(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { +type BurnLogInput struct { + Blockhash pack.Bytes32 + ExtSign pack.Bytes +} + +type BurnLogOutput struct { + Amount pack.U256 + Recipient address.RawAddress + Confs pack.U64 +} + +func (client *Client) ContractCallSystemEvents(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { // Deserialise the calldata bytes. input := BurnLogInput{} if err := surge.FromBinary(&input, calldata); err != nil { diff --git a/chain/acala/contract.go b/chain/acala/contract.go new file mode 100644 index 00000000..abeee86b --- /dev/null +++ b/chain/acala/contract.go @@ -0,0 +1,88 @@ +package acala + +import ( + "context" + "encoding/binary" + "fmt" + + "github.com/centrifuge/go-substrate-rpc-client/types" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/api/contract" + "github.com/renproject/pack" + "github.com/renproject/surge" +) + +type BurnContractCallInput struct { + Blockhash pack.Bytes32 + Nonce pack.U32 +} + +type BurnContractCallOutput struct { + Amount pack.U256 + Recipient address.RawAddress + Confs pack.U64 +} + +type BurnEventData struct { + Recipient types.Bytes + Amount types.U128 +} + +func (client *Client) ContractCall(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { + // Deserialise the calldata bytes. + input := BurnContractCallInput{} + if err := surge.FromBinary(&input, calldata); err != nil { + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v\n", err) + } + + // Get chain metadata. + meta, err := client.api.RPC.State.GetMetadataLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) + } + + // Get the block in which the burn event was logged. + block, err := client.api.RPC.Chain.GetBlock(types.Hash(input.Blockhash)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get block: %v", err) + } + + nonceBytes := make([]byte, 4) + binary.LittleEndian.PutUint32(nonceBytes, uint32(input.Nonce)) + + // This key is used to read the state storage at the block of interest. + key, err := types.CreateStorageKey(meta, "Template", "BurnEvents", nonceBytes, nil) + if err != nil { + return pack.Bytes{}, fmt.Errorf("create storage key: %v", err) + } + + // Retrieve and decode bytes from storage at the block and storage key. + burnEventData := BurnEventData{} + ok, err := client.api.RPC.State.GetStorage(key, &burnEventData, types.Hash(input.Blockhash)) + if err != nil || !ok { + return pack.Bytes{}, fmt.Errorf("get storage: %v", err) + } + + // Get the latest block header. This will be used to calculate number of block + // confirmations of the burn log of interest. + header, err := client.api.RPC.Chain.GetHeaderLatest() + if err != nil { + return pack.Bytes{}, fmt.Errorf("get header: %v", err) + } + + // Calculate block confirmations for the event. + confs := header.Number - block.Block.Header.Number + 1 + + burnLogOutput := BurnContractCallOutput{ + Amount: pack.NewU256FromInt(burnEventData.Amount.Int), + Recipient: address.RawAddress(burnEventData.Recipient), + Confs: pack.NewU64(uint64(confs)), + } + + out, err := surge.ToBinary(burnLogOutput) + if err != nil { + return pack.Bytes{}, fmt.Errorf("serialise output: %v", err) + } + + return pack.Bytes(out), nil +} diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index 946d3db9..bdb3d780 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -1,7 +1,6 @@ package acala import ( - "encoding/binary" "fmt" "time" @@ -98,11 +97,11 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes select { case status := <-sub.Chan(): if status.IsInBlock { - block, err := client.api.RPC.Chain.GetBlock(status.AsInBlock) + nonce, err := client.Nonce() if err != nil { - return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get block: %v", err) + return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("get nonce: %v", err) } - return pack.NewBytes32(status.AsInBlock), pack.NewU32(uint32(block.Block.Header.Number)), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil + return pack.NewBytes32(status.AsInBlock), nonce.Sub(pack.U32(1)), pack.Bytes(ext.Signature.Signature.AsSr25519[:]), nil } case <-timeout: return pack.Bytes32{}, pack.U32(0), pack.Bytes{}, fmt.Errorf("timeout on tx confirmation") @@ -116,15 +115,6 @@ type TokenAccount struct { Frozen types.U128 } -type BurnEventLogs struct { - Logs []BurnEventLog -} - -type BurnEventLog struct { - Recipient types.Bytes - Amount types.U128 -} - func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { @@ -145,29 +135,22 @@ func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { return pack.NewU256FromInt(data.Free.Int), nil } -func (client *Client) BurnEvent(blocknumber pack.U32) (BurnEventLog, error) { +func (client *Client) Nonce() (pack.U32, error) { meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { - return BurnEventLog{}, fmt.Errorf("get metadata: %v", err) + return pack.U32(0), fmt.Errorf("get metadata: %v", err) } - blocknumberBytes := make([]byte, 4) - binary.LittleEndian.PutUint32(blocknumberBytes, uint32(blocknumber)) - - key, err := types.CreateStorageKey(meta, "Template", "BurnEvents", blocknumberBytes, nil) + key, err := types.CreateStorageKey(meta, "Template", "NextBurnEventId", nil, nil) if err != nil { - return BurnEventLog{}, fmt.Errorf("create storage key: %v", err) + return pack.U32(0), fmt.Errorf("create storage key: %v", err) } - var data BurnEventLogs + var data types.U32 ok, err := client.api.RPC.State.GetStorageLatest(key, &data) if err != nil || !ok { - return BurnEventLog{}, fmt.Errorf("get storage: %v", err) - } - - if len(data.Logs) != 1 { - return BurnEventLog{}, fmt.Errorf("expected %v burn events, got %v", 1, len(data.Logs)) + return pack.U32(0), fmt.Errorf("get storage: %v", err) } - return data.Logs[0], nil + return pack.U32(data), nil } diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index e0928d04..ee29c600 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -26,7 +26,7 @@ import ( var _ = Describe("Mint Burn", func() { r := rand.New(rand.NewSource(GinkgoRandomSeed())) - blockhash, blocknumber, extSign := pack.Bytes32{}, pack.U32(0), pack.Bytes{} + blockhash, nonce, extSign := pack.Bytes32{}, pack.U32(0), pack.Bytes{} balanceBefore, balanceAfter := pack.U256{}, pack.U256{} client, err := acala.NewClient(acala.DefaultClientOptions()) @@ -60,7 +60,7 @@ var _ = Describe("Mint Burn", func() { balanceBefore, err = client.Balance(alice) Expect(err).NotTo(HaveOccurred()) - blockhash, blocknumber, extSign, err = client.Burn(alice, recipient, burnAmount) + blockhash, nonce, extSign, err = client.Burn(alice, recipient, burnAmount) Expect(err).NotTo(HaveOccurred()) time.Sleep(5 * time.Second) @@ -75,13 +75,24 @@ var _ = Describe("Mint Burn", func() { Context("when reading burn info", func() { Context("when reading from storage", func() { It("should succeed", func() { - // FIXME: once the Acala block number issue is addressed, we will not - // need to add 21600. - output, err := client.BurnEvent(blocknumber.Add(pack.U32(uint32(21600)))) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + input := acala.BurnContractCallInput{ + Blockhash: blockhash, + Nonce: nonce, + } + calldata, err := surge.ToBinary(input) + Expect(err).NotTo(HaveOccurred()) + outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) Expect(err).NotTo(HaveOccurred()) - Expect(output.Amount.Uint64()).To(Equal(burnAmount)) - Expect([]byte(output.Recipient)).To(Equal([]byte(recipient))) + output := acala.BurnContractCallOutput{} + Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) + + Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) + Expect(output.Recipient).To(Equal(multichain.RawAddress(recipient))) + Expect(output.Confs).To(BeNumerically(">", 0)) }) }) @@ -96,7 +107,7 @@ var _ = Describe("Mint Burn", func() { } calldata, err := surge.ToBinary(input) Expect(err).NotTo(HaveOccurred()) - outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) + outputBytes, err := client.ContractCallSystemEvents(ctx, multichain.Address(""), contract.CallData(calldata)) Expect(err).NotTo(HaveOccurred()) output := acala.BurnLogOutput{} @@ -127,8 +138,10 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac Expect(err).NotTo(HaveOccurred()) // Amount to be minted/burnt. - mintAmount := uint64(100000) - burnAmount := uint64(31000) + // Mint amount [80000, 100000] + // Burn amount [20000, 50000] + mintAmount := uint64(r.Intn(20000) + 80000) + burnAmount := uint64(r.Intn(30000) + 20000) // Selector for this cross-chain mint. token, err := hex.DecodeString("0000000000000000000000000a9add98c076448cbcfacf5e457da12ddbef4a8f") From 91ef52027c75d62d30edd7997b0d38d067380397 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Thu, 29 Oct 2020 17:54:40 +0530 Subject: [PATCH 10/18] feat: update burn event data | address API --- chain/acala/acala.go | 2 +- chain/acala/address.go | 55 ++++++++++++++++++++++++----------- chain/acala/address_test.go | 35 ++++++++++++++++++++++ chain/acala/contract.go | 28 +++++++----------- chain/acala/mint_burn.go | 4 +++ chain/acala/mint_burn_test.go | 14 ++++----- infra/acala/Dockerfile | 3 +- multichain.go | 2 +- 8 files changed, 97 insertions(+), 46 deletions(-) diff --git a/chain/acala/acala.go b/chain/acala/acala.go index 46b465a9..5fab12c5 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -66,7 +66,7 @@ type BurnLogOutput struct { Confs pack.U64 } -func (client *Client) ContractCallSystemEvents(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { +func (client *Client) CallContractSystemEvents(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { // Deserialise the calldata bytes. input := BurnLogInput{} if err := surge.FromBinary(&input, calldata); err != nil { diff --git a/chain/acala/address.go b/chain/acala/address.go index 49e5dc7d..bd8212f2 100644 --- a/chain/acala/address.go +++ b/chain/acala/address.go @@ -5,13 +5,29 @@ import ( "github.com/btcsuite/btcutil/base58" "github.com/renproject/multichain/api/address" + + "golang.org/x/crypto/blake2b" +) + +const ( + // The default address type byte used for a substrate chain. + DefaultSubstrateWildcard = byte(42) +) + +var ( + // Prefix used before hashing the address bytes for calculating checksum + Prefix = []byte("SS58PRE") ) // AddressDecoder implements the address.Decoder interface. -type AddressDecoder struct{} +type AddressDecoder struct { + addressType byte +} // AddressEncoder implements the address.Encoder interface. -type AddressEncoder struct{} +type AddressEncoder struct { + addressType byte +} // AddressEncodeDecoder implements the address.EncodeDecoder interface. type AddressEncodeDecoder struct { @@ -20,30 +36,35 @@ type AddressEncodeDecoder struct { } // NewAddressEncodeDecoder constructs a new AddressEncodeDecoder. -func NewAddressEncodeDecoder() AddressEncodeDecoder { +func NewAddressEncodeDecoder(addressType byte) AddressEncodeDecoder { return AddressEncodeDecoder{ - AddressEncoder: AddressEncoder{}, - AddressDecoder: AddressDecoder{}, + AddressEncoder: AddressEncoder{addressType}, + AddressDecoder: AddressDecoder{addressType}, } } -// DecodeAddress the string using the Bitcoin base58 alphabet. If the string -// does not a 2-byte address type, 32-byte array, and 1-byte checksum, then an -// error is returned. -func (AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) { +// DecodeAddress the string using the Bitcoin base58 alphabet. The substrate +// address is decoded and only the 32-byte public key is returned as the raw +// address. +func (decoder AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) { data := base58.Decode(string(addr)) if len(data) != 35 { return address.RawAddress([]byte{}), fmt.Errorf("expected 35 bytes, got %v bytes", len(data)) } - return address.RawAddress(data), nil + return address.RawAddress(data[1:33]), nil } -// EncodeAddress the raw bytes using the Bitcoin base58 alphabet. If the data to -// encode is not a 2-byte address type, 32-byte array, and 1-byte checksum, then -// an error is returned. -func (AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) { - if len(rawAddr) != 35 { - return address.Address(""), fmt.Errorf("expected 35 bytes, got %v bytes", len(rawAddr)) +// EncodeAddress the raw bytes using the Bitcoin base58 alphabet. We expect a +// 32-byte substrate public key as the address in its raw bytes representation. +// A checksum encoded key is then encoded in the base58 format. +func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) { + if len(rawAddr) != 32 { + return address.Address(""), fmt.Errorf("expected 32 bytes, got %v bytes", len(rawAddr)) } - return address.Address(base58.Encode(rawAddr)), nil + checksummedAddr := append([]byte{encoder.addressType}, rawAddr...) + checksum := blake2b.Sum512(append(Prefix, checksummedAddr...)) + + checksummedAddr = append(checksummedAddr, checksum[0:2]...) + + return address.Address(base58.Encode(checksummedAddr)), nil } diff --git a/chain/acala/address_test.go b/chain/acala/address_test.go index dc367890..6d915573 100644 --- a/chain/acala/address_test.go +++ b/chain/acala/address_test.go @@ -1 +1,36 @@ package acala_test + +import ( + "github.com/centrifuge/go-substrate-rpc-client/signature" + "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain/chain/acala" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Address", func() { + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultSubstrateWildcard) + + Context("when encoding raw address", func() { + It("should match the human-readable address", func() { + addr, err := addrEncodeDecoder.EncodeAddress(address.RawAddress(signature.TestKeyringPairAlice.PublicKey)) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).To(Equal(address.Address(signature.TestKeyringPairAlice.Address))) + rawAddr, err := addrEncodeDecoder.DecodeAddress(addr) + Expect(err).NotTo(HaveOccurred()) + Expect(rawAddr).To(Equal(address.RawAddress(signature.TestKeyringPairAlice.PublicKey))) + }) + }) + + Context("when decoding human-readable address", func() { + It("should match the raw address", func() { + rawAddr, err := addrEncodeDecoder.DecodeAddress(address.Address(signature.TestKeyringPairAlice.Address)) + Expect(err).NotTo(HaveOccurred()) + Expect(rawAddr).To(Equal(address.RawAddress(signature.TestKeyringPairAlice.PublicKey))) + addr, err := addrEncodeDecoder.EncodeAddress(rawAddr) + Expect(err).NotTo(HaveOccurred()) + Expect(addr).To(Equal(address.Address(signature.TestKeyringPairAlice.Address))) + }) + }) +}) diff --git a/chain/acala/contract.go b/chain/acala/contract.go index abeee86b..a5b9bbc5 100644 --- a/chain/acala/contract.go +++ b/chain/acala/contract.go @@ -12,25 +12,25 @@ import ( "github.com/renproject/surge" ) -type BurnContractCallInput struct { - Blockhash pack.Bytes32 - Nonce pack.U32 +type BurnCallContractInput struct { + Nonce pack.U32 } -type BurnContractCallOutput struct { +type BurnCallContractOutput struct { Amount pack.U256 Recipient address.RawAddress Confs pack.U64 } type BurnEventData struct { - Recipient types.Bytes - Amount types.U128 + BlockNumber types.U32 + Recipient types.Bytes + Amount types.U128 } -func (client *Client) ContractCall(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { +func (client *Client) CallContract(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { // Deserialise the calldata bytes. - input := BurnContractCallInput{} + input := BurnCallContractInput{} if err := surge.FromBinary(&input, calldata); err != nil { return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v\n", err) } @@ -41,12 +41,6 @@ func (client *Client) ContractCall(_ context.Context, _ address.Address, calldat return pack.Bytes{}, fmt.Errorf("get metadata: %v", err) } - // Get the block in which the burn event was logged. - block, err := client.api.RPC.Chain.GetBlock(types.Hash(input.Blockhash)) - if err != nil { - return pack.Bytes{}, fmt.Errorf("get block: %v", err) - } - nonceBytes := make([]byte, 4) binary.LittleEndian.PutUint32(nonceBytes, uint32(input.Nonce)) @@ -58,7 +52,7 @@ func (client *Client) ContractCall(_ context.Context, _ address.Address, calldat // Retrieve and decode bytes from storage at the block and storage key. burnEventData := BurnEventData{} - ok, err := client.api.RPC.State.GetStorage(key, &burnEventData, types.Hash(input.Blockhash)) + ok, err := client.api.RPC.State.GetStorageLatest(key, &burnEventData) if err != nil || !ok { return pack.Bytes{}, fmt.Errorf("get storage: %v", err) } @@ -71,9 +65,9 @@ func (client *Client) ContractCall(_ context.Context, _ address.Address, calldat } // Calculate block confirmations for the event. - confs := header.Number - block.Block.Header.Number + 1 + confs := types.U32(header.Number) - burnEventData.BlockNumber + 1 - burnLogOutput := BurnContractCallOutput{ + burnLogOutput := BurnCallContractOutput{ Amount: pack.NewU256FromInt(burnEventData.Amount.Int), Recipient: address.RawAddress(burnEventData.Recipient), Confs: pack.NewU64(uint64(confs)), diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index bdb3d780..c9592107 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -9,6 +9,10 @@ import ( "github.com/renproject/pack" ) +func (client *Client) To() (pack.String, pack.Bytes) { + return pack.String(signature.TestKeyringPairAlice.Address), pack.Bytes(signature.TestKeyringPairAlice.PublicKey) +} + func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes32, error) { opts := types.SerDeOptions{NoPalletIndices: true} types.SetSerDeOptions(opts) diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index ee29c600..44fb74f1 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -78,16 +78,13 @@ var _ = Describe("Mint Burn", func() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() - input := acala.BurnContractCallInput{ - Blockhash: blockhash, - Nonce: nonce, - } + input := acala.BurnCallContractInput{Nonce: nonce} calldata, err := surge.ToBinary(input) Expect(err).NotTo(HaveOccurred()) - outputBytes, err := client.ContractCall(ctx, multichain.Address(""), contract.CallData(calldata)) + outputBytes, err := client.CallContract(ctx, multichain.Address(""), contract.CallData(calldata)) Expect(err).NotTo(HaveOccurred()) - output := acala.BurnContractCallOutput{} + output := acala.BurnCallContractOutput{} Expect(surge.FromBinary(&output, outputBytes)).To(Succeed()) Expect(output.Amount).To(Equal(pack.NewU256FromUint64(burnAmount))) @@ -107,7 +104,7 @@ var _ = Describe("Mint Burn", func() { } calldata, err := surge.ToBinary(input) Expect(err).NotTo(HaveOccurred()) - outputBytes, err := client.ContractCallSystemEvents(ctx, multichain.Address(""), contract.CallData(calldata)) + outputBytes, err := client.CallContractSystemEvents(ctx, multichain.Address(""), contract.CallData(calldata)) Expect(err).NotTo(HaveOccurred()) output := acala.BurnLogOutput{} @@ -154,7 +151,8 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac phash32 := [32]byte{} nhash32 := [32]byte{} to := [32]byte{} - rawAddr, err := hex.DecodeString("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d") // Alice. + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultSubstrateWildcard) + rawAddr, err := addrEncodeDecoder.DecodeAddress(multichain.Address(signature.TestKeyringPairAlice.Address)) Expect(err).NotTo(HaveOccurred()) // Get message sighash. diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index e275c34e..db30c31d 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -14,8 +14,7 @@ RUN mv Acala /app WORKDIR /app # Make sure submodule.recurse is set to true to make life with submodule easier. -RUN git fetch -RUN git pull origin master +RUN git fetch && git checkout 59128d876ff1c246e9204ae63bb0f510340c2fec RUN git config --global submodule.recurse true # Build diff --git a/multichain.go b/multichain.go index 436a7fc0..3f84ac0c 100644 --- a/multichain.go +++ b/multichain.go @@ -254,7 +254,7 @@ func (chain Chain) ChainType() ChainType { switch chain { case Bitcoin, BitcoinCash, DigiByte, Dogecoin, Zcash: return ChainTypeUTXOBased - case BinanceSmartChain, Ethereum, Filecoin, Terra: + case Acala, BinanceSmartChain, Ethereum, Filecoin, Terra: return ChainTypeAccountBased // These chains are handled separately because they are mock chains. These From 878bdf4939384cd899f2a1a33d381530a1721e8f Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 30 Oct 2020 03:33:20 +0530 Subject: [PATCH 11/18] feat(acala): add address types for various networks --- chain/acala/address.go | 8 +++++++- chain/acala/address_test.go | 2 +- chain/acala/mint_burn_test.go | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/chain/acala/address.go b/chain/acala/address.go index bd8212f2..69daae23 100644 --- a/chain/acala/address.go +++ b/chain/acala/address.go @@ -11,7 +11,13 @@ import ( const ( // The default address type byte used for a substrate chain. - DefaultSubstrateWildcard = byte(42) + DefaultAddressType = byte(42) + // The address type used for testnet. + TestnetAddressType = byte(42) + // The address type used for canary network. + CanaryNetworkAddressType = byte(8) + // The address type used for mainnet. + MainnetAddressType = byte(10) ) var ( diff --git a/chain/acala/address_test.go b/chain/acala/address_test.go index 6d915573..cd4e575d 100644 --- a/chain/acala/address_test.go +++ b/chain/acala/address_test.go @@ -10,7 +10,7 @@ import ( ) var _ = Describe("Address", func() { - addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultSubstrateWildcard) + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultAddressType) Context("when encoding raw address", func() { It("should match the human-readable address", func() { diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 44fb74f1..68fa94fb 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -151,7 +151,7 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac phash32 := [32]byte{} nhash32 := [32]byte{} to := [32]byte{} - addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultSubstrateWildcard) + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultAddressType) rawAddr, err := addrEncodeDecoder.DecodeAddress(multichain.Address(signature.TestKeyringPairAlice.Address)) Expect(err).NotTo(HaveOccurred()) From ee5a5f3b4f601cb3c46c4adccbe02812ff75d3a3 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 30 Oct 2020 03:45:06 +0530 Subject: [PATCH 12/18] fix(acala): helper method to get address type from network --- chain/acala/address.go | 35 ++++++++++++++++++++++++----------- chain/acala/address_test.go | 2 +- chain/acala/mint_burn_test.go | 2 +- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/chain/acala/address.go b/chain/acala/address.go index 69daae23..8018a5eb 100644 --- a/chain/acala/address.go +++ b/chain/acala/address.go @@ -4,20 +4,20 @@ import ( "fmt" "github.com/btcsuite/btcutil/base58" - "github.com/renproject/multichain/api/address" + "github.com/renproject/multichain" "golang.org/x/crypto/blake2b" ) const ( // The default address type byte used for a substrate chain. - DefaultAddressType = byte(42) + AddressTypeDefault = byte(42) // The address type used for testnet. - TestnetAddressType = byte(42) + AddressTypeTestnet = byte(42) // The address type used for canary network. - CanaryNetworkAddressType = byte(8) + AddressTypeCanaryNetwork = byte(8) // The address type used for mainnet. - MainnetAddressType = byte(10) + AddressTypeMainnet = byte(10) ) var ( @@ -25,6 +25,19 @@ var ( Prefix = []byte("SS58PRE") ) +func GetAddressType(network multichain.Network) byte { + switch network { + case multichain.NetworkLocalnet, multichain.NetworkDevnet: + return AddressTypeDefault + case multichain.NetworkTestnet: + return AddressTypeTestnet + case multichain.NetworkMainnet: + return AddressTypeMainnet + default: + return AddressTypeDefault + } +} + // AddressDecoder implements the address.Decoder interface. type AddressDecoder struct { addressType byte @@ -52,25 +65,25 @@ func NewAddressEncodeDecoder(addressType byte) AddressEncodeDecoder { // DecodeAddress the string using the Bitcoin base58 alphabet. The substrate // address is decoded and only the 32-byte public key is returned as the raw // address. -func (decoder AddressDecoder) DecodeAddress(addr address.Address) (address.RawAddress, error) { +func (decoder AddressDecoder) DecodeAddress(addr multichain.Address) (multichain.RawAddress, error) { data := base58.Decode(string(addr)) if len(data) != 35 { - return address.RawAddress([]byte{}), fmt.Errorf("expected 35 bytes, got %v bytes", len(data)) + return multichain.RawAddress([]byte{}), fmt.Errorf("expected 35 bytes, got %v bytes", len(data)) } - return address.RawAddress(data[1:33]), nil + return multichain.RawAddress(data[1:33]), nil } // EncodeAddress the raw bytes using the Bitcoin base58 alphabet. We expect a // 32-byte substrate public key as the address in its raw bytes representation. // A checksum encoded key is then encoded in the base58 format. -func (encoder AddressEncoder) EncodeAddress(rawAddr address.RawAddress) (address.Address, error) { +func (encoder AddressEncoder) EncodeAddress(rawAddr multichain.RawAddress) (multichain.Address, error) { if len(rawAddr) != 32 { - return address.Address(""), fmt.Errorf("expected 32 bytes, got %v bytes", len(rawAddr)) + return multichain.Address(""), fmt.Errorf("expected 32 bytes, got %v bytes", len(rawAddr)) } checksummedAddr := append([]byte{encoder.addressType}, rawAddr...) checksum := blake2b.Sum512(append(Prefix, checksummedAddr...)) checksummedAddr = append(checksummedAddr, checksum[0:2]...) - return address.Address(base58.Encode(checksummedAddr)), nil + return multichain.Address(base58.Encode(checksummedAddr)), nil } diff --git a/chain/acala/address_test.go b/chain/acala/address_test.go index cd4e575d..1242c6b6 100644 --- a/chain/acala/address_test.go +++ b/chain/acala/address_test.go @@ -10,7 +10,7 @@ import ( ) var _ = Describe("Address", func() { - addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultAddressType) + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.AddressTypeDefault) Context("when encoding raw address", func() { It("should match the human-readable address", func() { diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 68fa94fb..76b2f801 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -151,7 +151,7 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac phash32 := [32]byte{} nhash32 := [32]byte{} to := [32]byte{} - addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.DefaultAddressType) + addrEncodeDecoder := acala.NewAddressEncodeDecoder(acala.AddressTypeDefault) rawAddr, err := addrEncodeDecoder.DecodeAddress(multichain.Address(signature.TestKeyringPairAlice.Address)) Expect(err).NotTo(HaveOccurred()) From 1f8c42e971c82c157d5c32c5b48f36229e18bd99 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Fri, 30 Oct 2020 16:49:36 +0530 Subject: [PATCH 13/18] fix: add blockhash as payload to burn --- chain/acala/contract.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/chain/acala/contract.go b/chain/acala/contract.go index a5b9bbc5..7f8437fd 100644 --- a/chain/acala/contract.go +++ b/chain/acala/contract.go @@ -20,6 +20,7 @@ type BurnCallContractOutput struct { Amount pack.U256 Recipient address.RawAddress Confs pack.U64 + Payload pack.Bytes } type BurnEventData struct { @@ -64,6 +65,11 @@ func (client *Client) CallContract(_ context.Context, _ address.Address, calldat return pack.Bytes{}, fmt.Errorf("get header: %v", err) } + blockhash, err := client.api.RPC.Chain.GetBlockHash(uint64(burnEventData.BlockNumber)) + if err != nil { + return pack.Bytes{}, fmt.Errorf("get blockhash: %v", err) + } + // Calculate block confirmations for the event. confs := types.U32(header.Number) - burnEventData.BlockNumber + 1 @@ -71,6 +77,7 @@ func (client *Client) CallContract(_ context.Context, _ address.Address, calldat Amount: pack.NewU256FromInt(burnEventData.Amount.Int), Recipient: address.RawAddress(burnEventData.Recipient), Confs: pack.NewU64(uint64(confs)), + Payload: pack.Bytes(blockhash[:]), } out, err := surge.ToBinary(burnLogOutput) From 0fc2ef6a82bd8e955d1f572f9cefb16b7d0a68ec Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 2 Nov 2020 16:47:15 +0530 Subject: [PATCH 14/18] chore: documentation and linting --- chain/acala/acala.go | 15 ++++++++++++++- chain/acala/address.go | 10 ++++++---- chain/acala/contract.go | 14 +++++++++++++- chain/acala/mint_burn.go | 12 ++++++++++++ chain/acala/ren_vm_bridge_types.go | 2 ++ infra/acala/Dockerfile | 2 +- infra/acala/run.sh | 2 +- 7 files changed, 49 insertions(+), 8 deletions(-) diff --git a/chain/acala/acala.go b/chain/acala/acala.go index 5fab12c5..cc85e94d 100644 --- a/chain/acala/acala.go +++ b/chain/acala/acala.go @@ -14,14 +14,18 @@ import ( ) const ( + // DefaultClientRPCURL is the default RPC URL used while connecting to an + // Acala node. DefaultClientRPCURL = "ws://127.0.0.1:9944" ) +// ClientOptions define the options for Acala client. type ClientOptions struct { Logger *zap.Logger rpcURL pack.String } +// DefaultClientOptions are the Acala client options used by default. func DefaultClientOptions() ClientOptions { logger, err := zap.NewDevelopment() if err != nil { @@ -33,16 +37,21 @@ func DefaultClientOptions() ClientOptions { } } +// WithRPCURL sets the RPC URL in the client options to return a new instance +// of client options. func (opts ClientOptions) WithRPCURL(rpcURL pack.String) ClientOptions { opts.rpcURL = rpcURL return opts } +// Client represents an Acala client. It interacts with a Substrate node (Acala) +// via the Go-substrate RPC API. type Client struct { opts ClientOptions api gsrpc.SubstrateAPI } +// NewClient constructs a new Acala client using the client options. func NewClient(opts ClientOptions) (*Client, error) { substrateAPI, err := gsrpc.NewSubstrateAPI(string(opts.rpcURL)) if err != nil { @@ -55,22 +64,26 @@ func NewClient(opts ClientOptions) (*Client, error) { }, nil } +// BurnLogInput defines a structure used to serialize relevant data to fetch +// Acala burn logs from RenVmBridge's system events. type BurnLogInput struct { Blockhash pack.Bytes32 ExtSign pack.Bytes } +// BurnLogOutput defines a structure used to represent burn data from Acala. type BurnLogOutput struct { Amount pack.U256 Recipient address.RawAddress Confs pack.U64 } +// CallContractSystemEvents fetches burn logs from the substrate system events. func (client *Client) CallContractSystemEvents(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { // Deserialise the calldata bytes. input := BurnLogInput{} if err := surge.FromBinary(&input, calldata); err != nil { - return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v\n", err) + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v", err) } // Get chain metadata. diff --git a/chain/acala/address.go b/chain/acala/address.go index 8018a5eb..8a478005 100644 --- a/chain/acala/address.go +++ b/chain/acala/address.go @@ -10,13 +10,13 @@ import ( ) const ( - // The default address type byte used for a substrate chain. + // AddressTypeDefault is the default address type byte for a substrate chain. AddressTypeDefault = byte(42) - // The address type used for testnet. + // AddressTypeTestnet is the address type used for testnet. AddressTypeTestnet = byte(42) - // The address type used for canary network. + // AddressTypeCanaryNetwork is the address type used for canary network. AddressTypeCanaryNetwork = byte(8) - // The address type used for mainnet. + // AddressTypeMainnet is the address type used for mainnet. AddressTypeMainnet = byte(10) ) @@ -25,6 +25,8 @@ var ( Prefix = []byte("SS58PRE") ) +// GetAddressType returns the appropriate prefix address type for a network +// type. func GetAddressType(network multichain.Network) byte { switch network { case multichain.NetworkLocalnet, multichain.NetworkDevnet: diff --git a/chain/acala/contract.go b/chain/acala/contract.go index 7f8437fd..82183b80 100644 --- a/chain/acala/contract.go +++ b/chain/acala/contract.go @@ -12,10 +12,15 @@ import ( "github.com/renproject/surge" ) +// BurnCallContractInput is the input structure that is consumed in a serialized +// byte form by the contract call API to fetch Acala's burn logs. type BurnCallContractInput struct { Nonce pack.U32 } +// BurnCallContractOutput is the output structure that is returned in a +// serialized byte form by the contract call API. It contains all the fields +// specific to the burn log at the given burn count (nonce). type BurnCallContractOutput struct { Amount pack.U256 Recipient address.RawAddress @@ -23,17 +28,24 @@ type BurnCallContractOutput struct { Payload pack.Bytes } +// BurnEventData defines the data stored as burn logs when RenBTC tokens are +// burnt on Acala. type BurnEventData struct { BlockNumber types.U32 Recipient types.Bytes Amount types.U128 } +// CallContract implements the multichain.ContractCaller interface for Acala. It +// is used specifically for fetching burn logs from Acala's storage. The input +// calldata is serialized nonce (burn count) of RenVmBridge, and it returns +// the serialized byte form of the respected burn log along with the number of +// block confirmations of that burn. func (client *Client) CallContract(_ context.Context, _ address.Address, calldata contract.CallData) (pack.Bytes, error) { // Deserialise the calldata bytes. input := BurnCallContractInput{} if err := surge.FromBinary(&input, calldata); err != nil { - return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v\n", err) + return pack.Bytes{}, fmt.Errorf("deserialise calldata: %v", err) } // Get chain metadata. diff --git a/chain/acala/mint_burn.go b/chain/acala/mint_burn.go index c9592107..e0462623 100644 --- a/chain/acala/mint_burn.go +++ b/chain/acala/mint_burn.go @@ -9,10 +9,15 @@ import ( "github.com/renproject/pack" ) +// To returns the default recipient of the newly minted tokens in Acala. We use +// Alice's address as this recipient. func (client *Client) To() (pack.String, pack.Bytes) { return pack.String(signature.TestKeyringPairAlice.Address), pack.Bytes(signature.TestKeyringPairAlice.PublicKey) } +// Mint consumes a RenVM mint signature and parameters, constructs an unsigned +// extrinsic to mint new RenBTC tokens for minterKey and broadcasts this +// extrinsic. It returns the extrinsic hash on successful execution. func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.Bytes32, sig pack.Bytes65, amount uint64) (pack.Bytes32, error) { opts := types.SerDeOptions{NoPalletIndices: true} types.SetSerDeOptions(opts) @@ -36,6 +41,9 @@ func (client *Client) Mint(minterKey signature.KeyringPair, phash, nhash pack.By return pack.NewBytes32(hash), nil } +// Burn broadcasts a signed extrinsic to burn RenBTC tokens from burnerKey on +// Acala. It returns the hash (of the block it was included in), the nonce +// (burn count) and the extrinsic's signature. func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes, amount uint64) (pack.Bytes32, pack.U32, pack.Bytes, error) { opts := types.SerDeOptions{NoPalletIndices: false} types.SetSerDeOptions(opts) @@ -113,12 +121,14 @@ func (client *Client) Burn(burnerKey signature.KeyringPair, recipient pack.Bytes } } +// TokenAccount represents the token balance information of an address. type TokenAccount struct { Free types.U128 Reserved types.U128 Frozen types.U128 } +// Balance returns the RenBTC free balance of an address. func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { @@ -139,6 +149,8 @@ func (client *Client) Balance(user signature.KeyringPair) (pack.U256, error) { return pack.NewU256FromInt(data.Free.Int), nil } +// Nonce returns the burn count in RenVmBridge. This is an identifier used to +// fetch burn logs from Acala's storage. func (client *Client) Nonce() (pack.U32, error) { meta, err := client.api.RPC.State.GetMetadataLatest() if err != nil { diff --git a/chain/acala/ren_vm_bridge_types.go b/chain/acala/ren_vm_bridge_types.go index 295f9a5a..dfdfeb95 100644 --- a/chain/acala/ren_vm_bridge_types.go +++ b/chain/acala/ren_vm_bridge_types.go @@ -39,6 +39,8 @@ type eventTreasury struct { Topics []types.Hash } +// RenVmBridgeEvents represents all of the system events that could be emitted +// along with extrinsics specific to the RenVmBridge module. type RenVmBridgeEvents struct { types.EventRecords Currencies_Deposited []eventDeposited diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index db30c31d..2322a4e4 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -14,7 +14,7 @@ RUN mv Acala /app WORKDIR /app # Make sure submodule.recurse is set to true to make life with submodule easier. -RUN git fetch && git checkout 59128d876ff1c246e9204ae63bb0f510340c2fec +RUN git fetch && git checkout c71dd4e6a5675a0a58a61d7d877a7f02daa3d99f RUN git config --global submodule.recurse true # Build diff --git a/infra/acala/run.sh b/infra/acala/run.sh index 4836221b..789f8132 100644 --- a/infra/acala/run.sh +++ b/infra/acala/run.sh @@ -3,7 +3,7 @@ ADDRESS=$1 # Start cd /app -SKIP_WASM_BUILD= cargo run -- --dev --execution=native -lruntime=debug --ws-external --rpc-external +SKIP_WASM_BUILD= cargo run --features with-acala-runtime -- --dev --execution=native -lruntime=debug --ws-external --rpc-external sleep 10 # Print setup From 866532c7919da6405690923baed9cec512577443 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 2 Nov 2020 16:47:29 +0530 Subject: [PATCH 15/18] fix: use appropriate selector hash --- chain/acala/mint_burn_test.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/chain/acala/mint_burn_test.go b/chain/acala/mint_burn_test.go index 76b2f801..67d088e9 100644 --- a/chain/acala/mint_burn_test.go +++ b/chain/acala/mint_burn_test.go @@ -141,10 +141,9 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac burnAmount := uint64(r.Intn(30000) + 20000) // Selector for this cross-chain mint. - token, err := hex.DecodeString("0000000000000000000000000a9add98c076448cbcfacf5e457da12ddbef4a8f") - Expect(err).NotTo(HaveOccurred()) - token32 := [32]byte{} - copy(token32[:], token[:]) + selector := []byte("BTC/toAcala") + shash32 := [32]byte{} + copy(shash32[:], crypto.Keccak256(selector)) // Initialise message args sighash32 := [32]byte{} @@ -162,7 +161,7 @@ func constructMintParams(r *rand.Rand) (signature.KeyringPair, pack.Bytes32, pac copy(sighash32[:], crypto.Keccak256(ethereum.Encode( pack.Bytes32(phash32), pack.NewU256FromUint64(mintAmount), - pack.Bytes32(token32), + pack.Bytes32(shash32), pack.Bytes32(to), pack.Bytes32(nhash32), ))) From 79ca230272f55c6e6a98cf753a7e61325a4f23bb Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Mon, 2 Nov 2020 20:43:35 +0530 Subject: [PATCH 16/18] ci: cover acala mint-burn tests in CI --- .github/workflows/test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0aa033f5..5a0e7441 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,6 +63,7 @@ jobs: cd $GITHUB_WORKSPACE/infra source .env docker-compose up -d --build \ + acala \ bitcoin \ bitcoincash \ dogecoin \ @@ -87,6 +88,7 @@ jobs: source ./infra/.env cd $GITHUB_WORKSPACE CI=true go test -timeout 1500s + CI=true go test ./chain/acala/... build: runs-on: ubuntu-latest From 4942b455976ab9031bdb454e1ab06ef673923bc4 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 3 Nov 2020 09:49:38 +0530 Subject: [PATCH 17/18] ci: use acala image from docker hub --- .github/workflows/test.yml | 5 +++-- infra/acala/Dockerfile | 4 +++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5a0e7441..4af48add 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,14 +63,15 @@ jobs: cd $GITHUB_WORKSPACE/infra source .env docker-compose up -d --build \ - acala \ bitcoin \ bitcoincash \ dogecoin \ terra \ zcash - docker run -d -p 1234:1234 -h 0.0.0.0 \ + docker run -d -p 1234:1234 -h 0.0.0.0 \ --name infra_filecoin_1 rohitnarurkar/multichain_filecoin:latest + docker run -d -p 9933:9933 -p 9944:9944 -h 0.0.0.0 \ + --name infra_acala_1 rohitnarurkar/multichain_acala:latest - name: Sleep until the nodes are up uses: jakejarvis/wait-action@master diff --git a/infra/acala/Dockerfile b/infra/acala/Dockerfile index 2322a4e4..ed3498ee 100644 --- a/infra/acala/Dockerfile +++ b/infra/acala/Dockerfile @@ -14,7 +14,9 @@ RUN mv Acala /app WORKDIR /app # Make sure submodule.recurse is set to true to make life with submodule easier. -RUN git fetch && git checkout c71dd4e6a5675a0a58a61d7d877a7f02daa3d99f +RUN git fetch && \ + git pull origin master && \ + git checkout a92256960d27ff9c9428b07b947c7e5ef132fedd RUN git config --global submodule.recurse true # Build From 50fe49f03ae312b381e1fd1728f2c41507193898 Mon Sep 17 00:00:00 2001 From: Rohit Narurkar Date: Tue, 3 Nov 2020 10:10:45 +0530 Subject: [PATCH 18/18] ci: remove acala test (no storage space left) --- .github/workflows/test.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4af48add..90b332a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -70,8 +70,6 @@ jobs: zcash docker run -d -p 1234:1234 -h 0.0.0.0 \ --name infra_filecoin_1 rohitnarurkar/multichain_filecoin:latest - docker run -d -p 9933:9933 -p 9944:9944 -h 0.0.0.0 \ - --name infra_acala_1 rohitnarurkar/multichain_acala:latest - name: Sleep until the nodes are up uses: jakejarvis/wait-action@master @@ -88,8 +86,7 @@ jobs: export PATH=$PATH:$(go env GOPATH)/bin source ./infra/.env cd $GITHUB_WORKSPACE - CI=true go test -timeout 1500s - CI=true go test ./chain/acala/... + go test -timeout 1500s build: runs-on: ubuntu-latest