Skip to content

Commit

Permalink
Implement relayer ticket allocation workflow. (#34)
Browse files Browse the repository at this point in the history
Implement relayer ticket allocation workflow. 

* Relayer ticket allocation workflow
* Owner ticket initial allocation and recovery
  • Loading branch information
dzmitryhil authored Oct 27, 2023
1 parent dc118b5 commit 52ec0d1
Show file tree
Hide file tree
Showing 41 changed files with 2,235 additions and 610 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/relayer-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
wasm-cache: true
linter-cache: true
docker-cache: false
- ci_step: "lint"
- ci_step: "test"
command: "make test-relayer"
go-cache: true
wasm-cache: true
Expand Down
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ build-contract:
mkdir -p $(BUILD_DIR)
cp $(CONTRACT_DIR)/artifacts/coreumbridge_xrpl.wasm $(BUILD_DIR)/coreumbridge_xrpl.wasm

.PHONY: build-dev-contract
build-dev-contract:
rustup target add wasm32-unknown-unknown
cargo install wasm-opt --locked
# Those RUSTFLAGS reduce binary size from 2MB to 400 KB
cd $(CONTRACT_DIR) && RUSTFLAGS='-C link-arg=-s' cargo wasm
mkdir -p $(BUILD_DIR)
cp $(CONTRACT_DIR)/target/wasm32-unknown-unknown/release/coreumbridge_xrpl.wasm $(BUILD_DIR)/coreumbridge_xrpl_not_opt.wasm
wasm-opt -Os --signext-lowering $(BUILD_DIR)/coreumbridge_xrpl_not_opt.wasm -o $(BUILD_DIR)/coreumbridge_xrpl.wasm
rm $(BUILD_DIR)/coreumbridge_xrpl_not_opt.wasm

###############################################################################
### Development ###
###############################################################################
Expand Down Expand Up @@ -78,4 +89,4 @@ restart-dev-env:

.PHONY: rebuild-dev-env
rebuild-dev-env:
crust build/crust images/cored
crust build images
10 changes: 8 additions & 2 deletions contract/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,9 @@ fn register_xrpl_token(
}

// We generate a denom creating a Sha256 hash of the issuer, currency and current time
let to_hash = format!("{}{}{}", issuer, currency, env.block.time.seconds()).into_bytes();
let to_hash = format!("{}{}{}", issuer, currency,
env.block.time.seconds()
).into_bytes();

// We encode the hash in hexadecimal and take the first 10 characters
let hex_string = hash_bytes(to_hash)
Expand Down Expand Up @@ -435,7 +437,11 @@ fn recover_tickets(
//If we don't provide a number of tickets to recover we will recover the ones that we already used.
let number_to_allocate = number_of_tickets.unwrap_or(used_tickets);

if number_to_allocate == 0 || number_to_allocate > MAX_TICKETS {
let config = CONFIG.load(deps.storage)?;
//we check that number_to_allocate > config.used_tickets_threshold in order to cover the
//reallocation with just one XRPL transaction, otherwise the relocation might cause the
//additional reallocation.
if number_to_allocate <= config.used_tickets_threshold || number_to_allocate > MAX_TICKETS {
return Err(ContractError::InvalidTicketNumberToAllocate {});
}

Expand Down
2 changes: 1 addition & 1 deletion contract/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub enum ContractError {
#[error("SignatureAlreadyProvided: There is already a signature provided for this relayer and this operation")]
SignatureAlreadyProvided {},

#[error("InvalidTicketNumberToAllocate: The number of tickets to recover must be more than 0")]
#[error("InvalidTicketNumberToAllocate: The number of tickets to recover must be greater than used ticket threshold and less than or equal to max allowed")]
InvalidTicketNumberToAllocate {},

#[error("InvalidXRPLIssuer: The issuer must be a valid XRPL address")]
Expand Down
32 changes: 26 additions & 6 deletions contract/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ mod tests {
},
state::{Config, Operation, OperationType, Relayer, Signature},
};

const FEE_DENOM: &str = "ucore";
const XRP_SYMBOL: &str = "XRP";
const XRP_SUBUNIT: &str = "drop";
Expand All @@ -45,7 +46,7 @@ mod tests {
used_tickets_threshold: u32,
issue_fee: Vec<Coin>,
) -> String {
let wasm_byte_code = std::fs::read("./artifacts/coreumbridge_xrpl.wasm").unwrap();
let wasm_byte_code = std::fs::read("../build/coreumbridge_xrpl.wasm").unwrap();
let code_id = wasm
.store_code(&wasm_byte_code, None, &signer)
.unwrap()
Expand Down Expand Up @@ -882,7 +883,7 @@ mod tests {
&query_issue_fee(&asset_ft),
signer,
)
.unwrap();
.unwrap();

let query_xrpl_tokens = wasm
.query::<QueryMsg, XRPLTokensResponse>(
Expand Down Expand Up @@ -1824,7 +1825,7 @@ mod tests {
Addr::unchecked(signer.address()),
vec![relayers[0].clone(), relayers[1].clone()],
2,
50,
4,
query_issue_fee(&asset_ft),
);

Expand All @@ -1847,13 +1848,32 @@ mod tests {
assert_eq!(query_available_tickets.tickets, Vec::<u64>::new());

let sequence_number = 1;
//Trying to recover 0 tickets will fail
//Trying to recover tickets with the value less than used_tickets_threshold
let recover_ticket_error = wasm
.execute::<ExecuteMsg>(
&contract_addr,
&ExecuteMsg::RecoverTickets {
sequence_number,
number_of_tickets: Some(1),
},
&vec![],
&signer,
)
.unwrap_err();

assert!(recover_ticket_error.to_string().contains(
ContractError::InvalidTicketNumberToAllocate {}
.to_string()
.as_str()
));

//Trying to recover more than max tickets will fail
let recover_ticket_error = wasm
.execute::<ExecuteMsg>(
&contract_addr,
&ExecuteMsg::RecoverTickets {
sequence_number,
number_of_tickets: Some(0),
number_of_tickets: Some(300),
},
&vec![],
&signer,
Expand Down Expand Up @@ -2304,7 +2324,7 @@ mod tests {
Addr::unchecked(signer.address()),
vec![relayer],
1,
50,
4,
query_issue_fee(&asset_ft),
);

Expand Down
6 changes: 3 additions & 3 deletions integration-tests/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/CoreumFoundation/coreumbridge-xrpl/relayer/coreum"
)

const compiledContractFilePath = "../../contract/artifacts/coreumbridge_xrpl.wasm"
const compiledContractFilePath = "../../build/coreumbridge_xrpl.wasm"

// DeployAndInstantiateContract deploys and instantiates the contract.
func DeployAndInstantiateContract(
Expand All @@ -29,9 +29,9 @@ func DeployAndInstantiateContract(
issueFee := chains.Coreum.QueryAssetFTParams(ctx, t).IssueFee
owner := chains.Coreum.GenAccount()

// fund with issuance fee and some coins on to cover fees
// fund with issuance fee and some coins to cover fees
chains.Coreum.FundAccountWithOptions(ctx, t, owner, coreumintegration.BalancesOptions{
Amount: issueFee.Amount.AddRaw(10_000_000),
Amount: issueFee.Amount.AddRaw(1_000_000),
})

contractClient := coreum.NewContractClient(coreum.DefaultContractClientConfig(sdk.AccAddress(nil)), chains.Log, chains.Coreum.ClientContext)
Expand Down
12 changes: 0 additions & 12 deletions integration-tests/coreum.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/pkg/errors"

"github.com/CoreumFoundation/coreum/v3/app"
Expand Down Expand Up @@ -64,17 +63,6 @@ func (c CoreumChain) Config() CoreumChainConfig {
return c.cfg
}

// GenAccountWithKeyName generates new coreum account saves it to the keyring and returns the account address and its key name.
func (c CoreumChain) GenAccountWithKeyName() (sdk.AccAddress, string) {
address := c.GenAccount()
keyRecord, err := c.ClientContext.Keyring().KeyByAddress(address)
if err != nil {
panic(errors.Wrapf(err, "faild to get key by address from the keyring"))
}

return address, keyRecord.Name
}

func getTestContextConfig() client.ContextConfig {
cfg := client.DefaultContextConfig()
cfg.TimeoutConfig.TxStatusPollInterval = 100 * time.Millisecond
Expand Down
32 changes: 17 additions & 15 deletions integration-tests/coreum/contract_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ func TestRegisterCoreumToken(t *testing.T) {
recipientAcc := chains.XRPL.GenAccount(ctx, t, 10)

// allow to receive the currency
currency, err := xrpl.StringToHexXRPLCurrency(registeredToken.XRPLCurrency)
currency, err := xrpl.ConvertStringToHexXRPLCurrency(registeredToken.XRPLCurrency)
require.NoError(t, err)
amountToSend, err := rippledata.NewValue("10000000000000000", false)
require.NoError(t, err)
Expand Down Expand Up @@ -605,49 +605,50 @@ func TestRecoverTickets(t *testing.T) {
rejectedTxHash := "FC7B3043C73998C6696C788D73621D55D7C05BEBBA0A14C186AF43F6B12AE8E3"
rejectedTxEvidence := coreum.XRPLTransactionResultTicketsAllocationEvidence{
XRPLTransactionResultEvidence: coreum.XRPLTransactionResultEvidence{
TxHash: rejectedTxHash,
SequenceNumber: &firstBridgeAccountSeqNumber,
TxHash: rejectedTxHash,
SequenceNumber: &firstBridgeAccountSeqNumber,
TransactionResult: coreum.TransactionResultRejected,
},
Tickets: nil,
}

// try to send with not existing sequence
invalidRejectedTxEvidence := rejectedTxEvidence
invalidRejectedTxEvidence.SequenceNumber = lo.ToPtr(uint32(999))
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidRejectedTxEvidence, false)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidRejectedTxEvidence)
require.True(t, coreum.IsPendingOperationNotFoundError(err), err)

// try to send with not existing ticket
invalidRejectedTxEvidence = rejectedTxEvidence
invalidRejectedTxEvidence.SequenceNumber = nil
invalidRejectedTxEvidence.TicketNumber = lo.ToPtr(uint32(999))
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidRejectedTxEvidence, false)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidRejectedTxEvidence)
require.True(t, coreum.IsPendingOperationNotFoundError(err), err)

// try to send from not relayer
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, owner, rejectedTxEvidence, false)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, owner, rejectedTxEvidence)
require.True(t, coreum.IsUnauthorizedSenderError(err), err)

// send evidence from first relayer
txRes, err := contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence, false)
txRes, err := contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence)
require.NoError(t, err)
thresholdReached, err := event.FindStringEventAttribute(txRes.Events, wasmtypes.ModuleName, eventAttributeThresholdReached)
require.NoError(t, err)
require.Equal(t, "false", thresholdReached)

// try to send evidence from second relayer one more time
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence, false)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence)
require.True(t, coreum.IsEvidenceAlreadyProvidedError(err), err)

// send evidence from second relayer
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer2, rejectedTxEvidence, false)
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer2, rejectedTxEvidence)
require.NoError(t, err)
thresholdReached, err = event.FindStringEventAttribute(txRes.Events, wasmtypes.ModuleName, eventAttributeThresholdReached)
require.NoError(t, err)
require.Equal(t, "true", thresholdReached)

// try to send the evidence one more time
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence, false)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, rejectedTxEvidence)
require.True(t, coreum.IsOperationAlreadyExecutedError(err), err)

pendingOperations, err = contractClient.GetPendingOperations(ctx)
Expand All @@ -671,27 +672,28 @@ func TestRecoverTickets(t *testing.T) {
acceptedTxHash := "D5F78F452DFFBE239EFF668E4B34B1AF66CD2F4D5C5D9E54A5AF34121B5862C8"
acceptedTxEvidence := coreum.XRPLTransactionResultTicketsAllocationEvidence{
XRPLTransactionResultEvidence: coreum.XRPLTransactionResultEvidence{
TxHash: acceptedTxHash,
SequenceNumber: &secondBridgeAccountSeqNumber,
TxHash: acceptedTxHash,
SequenceNumber: &secondBridgeAccountSeqNumber,
TransactionResult: coreum.TransactionResultAccepted,
},
Tickets: []uint32{3, 5, 6, 7},
}

// try to send with already used txHash
invalidAcceptedTxEvidence := acceptedTxEvidence
invalidAcceptedTxEvidence.TxHash = rejectedTxHash
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidAcceptedTxEvidence, true)
_, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, invalidAcceptedTxEvidence)
require.True(t, coreum.IsOperationAlreadyExecutedError(err), err)

// send evidence from first relayer
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, acceptedTxEvidence, true)
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer1, acceptedTxEvidence)
require.NoError(t, err)
thresholdReached, err = event.FindStringEventAttribute(txRes.Events, wasmtypes.ModuleName, eventAttributeThresholdReached)
require.NoError(t, err)
require.Equal(t, "false", thresholdReached)

// send evidence from second relayer
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer2, acceptedTxEvidence, true)
txRes, err = contractClient.SendXRPLTicketsAllocationTransactionResultEvidence(ctx, relayer2, acceptedTxEvidence)
require.NoError(t, err)
thresholdReached, err = event.FindStringEventAttribute(txRes.Events, wasmtypes.ModuleName, eventAttributeThresholdReached)
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/google/uuid v1.3.0
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
Expand Down
9 changes: 7 additions & 2 deletions integration-tests/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"context"
"flag"
"testing"
"time"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/CoreumFoundation/coreumbridge-xrpl/relayer/logger"
Expand Down Expand Up @@ -62,8 +64,11 @@ func init() {

// NewTestingContext returns the configured coreum and xrpl chains and new context for the integration tests.
func NewTestingContext(t *testing.T) (context.Context, Chains) {
testCtx, testCtxCancel := context.WithCancel(context.Background())
t.Cleanup(testCtxCancel)
testCtx, testCtxCancel := context.WithTimeout(context.Background(), 5*time.Minute)
t.Cleanup(func() {
require.NoError(t, testCtx.Err())
testCtxCancel()
})

return testCtx, chains
}
Loading

0 comments on commit 52ec0d1

Please sign in to comment.